Initial commit

This commit is contained in:
Gregg 2020-07-18 21:47:39 -05:00
commit cccb61f946
22 changed files with 4299 additions and 0 deletions

0
.development Normal file
View File

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# HomeSpan
HomeKit for the Arduino-ESP32

View File

@ -0,0 +1,103 @@
////////////////////////////////////////////////////////////
// //
// HomeSpan: A HomeKit implementation for the ESP32 //
// ------------------------------------------------ //
// //
// Example 1: A non-functioning on/off light bulb //
// constructed from basic HomeSpan components //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h" // Always start by including the HomeSpan library
void setup() {
Serial.begin(115200); // Start a serial connection - this is needed for you to type in your WiFi credentials
// Begin a HomeSpan Session. Required parameters are Category and Name.
// These are used by HomeKit to configure the icon and name of the device shown when initially pairing.
// There are no other effects of these settings and they are ignored by HomeKit after pairing is complete.
// You can even specify a Lighting Category for a Faucet. This effects nothing but the initial incon.
// A complete list of Categories can be found in Settings.h, which is based on Section 13 of Apple's
// HomeKit Accessory Protocol (HAP) Specifications Document.
homeSpan.begin(Category::Lighting,"HomeSpan LightBulb");
// Every HomeKit device consists of one or more Accessories. Each Accessory contains one or more Services.
// Every Service contains one or more Characteristics. HAP defines all allowable Services and Characteristics,
// including those that are required and those that are optional. An Accessory is typically a complete appliance,
// such as a table lamp or ceiling fan. Services are the main components of the appliance - a ceiling fan Accessory will
// typically have a fan Service and a light bulb Service. Characteristics define how each Service operates.
// Some Characteristics are read-only and describe the name or properties of a Service. Other Characteristics
// can be both written and read by HomeKit - these are the interesting ones since they enable actions to occur,
// such as turning on or off a light, or setting its brightness.
// HAP also requires various informational Services that describe the overall Accessory.
// HAP calls the entirety of all Accessories, Services, and Characteristics the "Accessory Attributes Database."
// A complete list of HAP Services and Characteristics implemented in HomeSpan can be found in Services.h, which is
// based on HAP Section 8 (Services) and HAP Section 9 (Characteristics).
// Users construct the Accessories database in HomeSpan by using a combination of new SpanAccessory, new Services (which point to underlying
// SpanServices), and new Characteristics (which point to underlying SpanCharacteristics). The database is assembled in the
// order in which components are defined. A new Service will be implemented in the last new Accessory defined, and a new
// Characteristic will be implemented in the last new Service defined. Indention helps convey this structure though is
// of course not required.
/////////////////////////////////
// For this example, our Database will comprise a single Accessory containing 3 Services, each with their own required Characteristics
new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments
new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics
new Characteristic::Name("My Table Lamp"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories
// The next 4 Characteristics serve no function except for being displayed in HomeKit's setting panel for each Accessory. They are nevertheless required by HAP:
new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory)
new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory)
new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory)
new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory)
// The last required Characteristic for the Accessory Information Service allows the user to identify the Characteristic and requires
// some implementation code (such as blinking an LED, or flashing the light). HomeSpan defaults to take no action if there is no
// implementation code, so we can simply create the Identify Characteristic for now and let HomeSpan default to no action.
new Characteristic::Identify(); // Create the required Identify
// HAP requires every Accessory (with the exception of those in Bridges) to implement the HAP Protocol Information Service.
// This Serrvice supports a single required Characteristic that defined the version number of HAP used by the device.
// HAP Release R2 requires this version to be set to "1.1.0"
new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service
new Characteristic::Version("1.1.0"); // Set the Version Characteristicto "1.1.0" as required by HAP
// Now that the required "informational" Services have been defined, we can finally create the Light Bulb Service
// NOTE: The order of the Services is not important - we could have created the LightBulb first.
new Service::LightBulb(); // Create the Light Bulb Service
new Characteristic::On(); // This Service requires the "On" Characterstic to turn the light on and off
// That's all that's needed to define a database from scratch, including all required HAP elements, to control a single lightbulb.
// Of course the database itself does not contain any code to implement the actual operation of the light - there is nothing to
// turn on and off. But you'll still see a Light Bulb tile show up in HomeKit with an ability to toggle it on and off. In the next
// example we will add the code that turns on and off an LED. For now, upload this sketch to your ESP32, pair with HomeKit, and
// verify everything works.
} // end of setup()
//////////////////////////////////////
void loop(){
// The code in setup above implements the Accessory Attribute Database, but performs no operations. HomeSpan itself must be
// continuously polled to look for requests from Controllers, such as an iOS or MacOS device. The poll() method below is all that
// is needed to perform this continuously in each iteration of loop()
homeSpan.poll(); // run HomeSpan!
} // end of loop()

9
library.properties Normal file
View File

@ -0,0 +1,9 @@
name=HomeSpan
version=1.0.0
author=HomeSpan
maintainer=none
sentence=HomeKit for the Espressif ESP32.
paragraph=This library provides a microcontroller-focused implementation of Apple's HomeKit Accessory Protocol (HAP - Release R2) designed specifically for the ESP32. It allows a user to pair an iOS or MacOS HomeKit-compatiable application directly to the controller via their home WiFi network without the need for any external bridge or components. The user can then use the full power of the ESP32's I/O functionality to create custom control software and/or hardware to operate external devices.
url=none
architectures=*
includes=HomeSpan.h

1518
src/HAP.cpp Normal file

File diff suppressed because it is too large Load Diff

144
src/HAP.h Normal file
View File

@ -0,0 +1,144 @@
#include <WiFi.h>
#include <nvs.h>
#include "TLV.h"
#include "HAPConstants.h"
#include "HKDF.h"
#include "SRP.h"
/////////////////////////////////////////////////
// NONCE Structure (HAP used last 64 of 96 bits)
struct Nonce {
uint8_t x[12];
Nonce();
void zero();
uint8_t *get();
void inc();
};
/////////////////////////////////////////////////
// Paired Controller Structure for Permanently-Stored Data
struct Controller {
boolean allocated=false; // slot is allocated with Controller data
boolean admin; // Controller has admin privileges
uint8_t ID[36]; // Pairing ID
uint8_t LTPK[32]; // Long Term Ed2519 Public Key
};
/////////////////////////////////////////////////
// Accessory Structure for Permanently-Stored Data
struct Accessory {
uint8_t ID[17]; // Pairing ID in form "XX:XX:XX:XX:XX:XX"
uint8_t LTSK[64]; // secret key for Ed25519 signatures
uint8_t LTPK[32]; // public key for Ed25519 signatures
};
/////////////////////////////////////////////////
// HAPClient Structure
// Reads and Writes from each HAP Client connection
struct HAPClient {
// common structures and data shared across all HAP Clients
static const int MAX_HTTP=8095; // max number of bytes in HTTP message buffer
static const int MAX_CONTROLLERS=16; // maximum number of paired controllers (HAP requires at least 16)
static TLV<kTLVType,10> tlv8; // TLV8 structure (HAP Section 14.1) with space for 10 TLV records of type kTLVType (HAP Table 5-6)
static nvs_handle nvsHandle; // handle for non-volatile-storage of HAP data
static uint8_t httpBuf[MAX_HTTP+1]; // buffer to store HTTP messages (+1 to leave room for storing an extra 'overflow' character)
static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string
static pairState pairStatus; // tracks pair-setup status
static SRP6A srp; // stores all SRP-6A keys used for Pair-Setup
static Accessory accessory; // Accessory ID and Ed25519 public and secret keys- permanently stored
static Controller controllers[MAX_CONTROLLERS]; // 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
WiFiClient client=NULL; // handle to client
Controller *cPair; // pointer to info on current, session-verified Paired Controller (NULL=un-verified, and therefore un-encrypted, connection)
// These 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[32]; // public key for Curve25519 encryption
uint8_t sharedCurveKey[32]; // Pair-Verfied Shared Secret key derived from Accessory's epehmeral secretCurveKey and Controller's iosCurveKey
uint8_t sessionKey[32]; // shared Session Key (derived with various HKDF calls)
uint8_t iosCurveKey[32]; // Curve25519 public key for associated paired controller
// CurveKey and CurveKey Nonces are created once each new session is verified in /pair-verify. Keys persist for as long as connection is open
uint8_t a2cKey[32]; // AccessoryToControllerKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2)
uint8_t c2aKey[32]; // ControllerToAccessoryKey derived from HKDF-SHA-512 of sharedCurveKey (HAP Section 6.5.2)
Nonce a2cNonce; // encryption nonce (starts at zero at end of each Pair-Verify and increment every encryption - NOT DOCUMENTED)
Nonce c2aNonce; // decryption nonce (starts at zero at end of each Pair-Verify and increment every encryption - NOT DOCUMENTED)
// define member methods
void processRequest(); // process HAP request
int postPairSetupURL(); // POST /pair-setup (HAP Section 5.6)
int postPairVerifyURL(); // POST /pair-verify (HAP Section 5.7)
int getAccessoriesURL(); // GET /accessories (HAP Section 6.6)
int postPairingsURL(); // POST /pairings (HAP Sections 5.10-5.12)
int getCharacteristicsURL(char *urlBuf); // GET /characteristics (HAP Section 6.7.4)
int putCharacteristicsURL(char *json); // PUT /characteristics (HAP Section 6.7.2)
void tlvRespond(); // respond to client with HTTP OK header and all defined TLV data records (those with length>0)
void sendEncrypted(char *body, uint8_t *dataBuf, int dataLen); // send client complete ChaCha20-Poly1305 encrypted HTTP mesage comprising a null-terminated 'body' and 'dataBuf' with 'dataLen' bytes
int receiveEncrypted(); // decrypt HTTP request (HAP Section 6.5)
int notFoundError(); // return 404 error
int badRequestError(); // return 400 error
int unauthorizedError(); // return 470 error
// define static methods
static void init(); // initialize HAP after start-up
static void hexPrintColumn(uint8_t *buf, int n); // prints 'n' bytes of *buf as HEX, one byte per row. For diagnostics/debugging only
static void hexPrintRow(uint8_t *buf, int n); // prints 'n' bytes of *buf as HEX, all on one row
static void charPrintRow(uint8_t *buf, int n); // prints 'n' bytes of *buf as CHAR, all on one row
static Controller *findController(uint8_t *id); // returns pointer to controller with mathching ID (or NULL if no match)
static Controller *getFreeController(); // return pointer to next free controller slot (or NULL if no free slots)
static Controller *addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns pointer to Controller slot on success, else NULL
static int nAdminControllers(); // returns number of admin Controllers stored
static void removeControllers(); // removes all Controllers (sets allocated flags to false for all slots)
static void removeController(uint8_t *id); // removes specific Controller. If no remaining admin Controllers, remove all others (if any) as per HAP requirements.
static void printControllers(); // prints IDs of all allocated (paired) Controller
static void checkNotifications(); // checks for notifications and reports to controllers as needed (HAP Section 6.8)
};
/////////////////////////////////////////////////
// Creates a temporary buffer that is freed after
// going out of scope
template <class bufType>
struct TempBuffer {
bufType *buf;
int nBytes;
TempBuffer(size_t len){
nBytes=len*sizeof(bufType);
buf=(bufType *)heap_caps_malloc(nBytes,MALLOC_CAP_8BIT);
}
~TempBuffer(){
heap_caps_free(buf);
}
int len(){
return(nBytes);
}
};
/////////////////////////////////////////////////
// Extern Variables
extern HAPClient hap[];

47
src/HAPConstants.h Normal file
View File

@ -0,0 +1,47 @@
// HAP TLV Types (HAP Table 5-6)
typedef enum {
kTLVType_Method=0x00,
kTLVType_Identifier=0x01,
kTLVType_Salt=0x02,
kTLVType_PublicKey=0x03,
kTLVType_Proof=0x04,
kTLVType_EncryptedData=0x05,
kTLVType_State=0x06,
kTLVType_Error=0x07,
kTLVType_RetryDelay=0x08,
kTLVType_Certificate=0x09,
kTLVType_Signature=0x0A,
kTLVType_Permissions=0x0B,
kTLVType_FragmentData=0x0C,
kTLVType_FragmentLast=0x0D,
kTLVType_Flags=0x13,
kTLVType_Separator=0xFF
} kTLVType;
// HAP Error Codes (HAP Table 5-5)
typedef enum {
tagError_Unknown=0x01,
tagError_Authentication=0x02,
tagError_Backoff=0x03,
tagError_MaxPeers=0x04,
tagError_MaxTries=0x05,
tagError_Unavailable=0x06,
tagError_Busy=0x07
} tagError;
// Pair-Setup and Pair-Verify States
typedef enum {
pairState_M0=0,
pairState_M1=1,
pairState_M2=2,
pairState_M3=3,
pairState_M4=4,
pairState_M5=5,
pairState_M6=6
} pairState;

186
src/HKDF.cpp Normal file
View File

@ -0,0 +1,186 @@
#include <mbedtls/hkdf.h>
#include <mbedtls/platform_util.h>
#include "HKDF.h"
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
// Wrapper function to call mbedtls_hkdf, below, with
// HAP-specific parameters and assumptions
int HKDF::create(uint8_t *outputKey, uint8_t *inputKey, int inputLen, char *salt, char *info){
return(mbedtls_hkdf( mbedtls_md_info_from_type(MBEDTLS_MD_SHA512),
(uint8_t *) salt, (size_t) strlen(salt),
inputKey, (size_t) inputLen,
(uint8_t *) info, (size_t) strlen(info),
outputKey, 32 ));
}
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
// CODE FOR HKDF COPIED FROM MBEDTLS GITHUB SINCE IT IS NOT INCLUDED
// IN STANDARD ARDUIO-ESP32 LIBRARY.
int mbedtls_hkdf( const mbedtls_md_info_t *md, const unsigned char *salt,
size_t salt_len, const unsigned char *ikm, size_t ikm_len,
const unsigned char *info, size_t info_len,
unsigned char *okm, size_t okm_len )
{
int ret;
unsigned char prk[MBEDTLS_MD_MAX_SIZE];
ret = mbedtls_hkdf_extract( md, salt, salt_len, ikm, ikm_len, prk );
if( ret == 0 )
{
ret = mbedtls_hkdf_expand( md, prk, mbedtls_md_get_size( md ),
info, info_len, okm, okm_len );
}
mbedtls_platform_zeroize( prk, sizeof( prk ) );
return( ret );
}
int mbedtls_hkdf_extract( const mbedtls_md_info_t *md,
const unsigned char *salt, size_t salt_len,
const unsigned char *ikm, size_t ikm_len,
unsigned char *prk )
{
unsigned char null_salt[MBEDTLS_MD_MAX_SIZE] = { '\0' };
if( salt == NULL )
{
size_t hash_len;
if( salt_len != 0 )
{
return MBEDTLS_ERR_HKDF_BAD_INPUT_DATA;
}
hash_len = mbedtls_md_get_size( md );
if( hash_len == 0 )
{
return MBEDTLS_ERR_HKDF_BAD_INPUT_DATA;
}
salt = null_salt;
salt_len = hash_len;
}
return( mbedtls_md_hmac( md, salt, salt_len, ikm, ikm_len, prk ) );
}
int mbedtls_hkdf_expand( const mbedtls_md_info_t *md, const unsigned char *prk,
size_t prk_len, const unsigned char *info,
size_t info_len, unsigned char *okm, size_t okm_len )
{
size_t hash_len;
size_t where = 0;
size_t n;
size_t t_len = 0;
size_t i;
int ret = 0;
mbedtls_md_context_t ctx;
unsigned char t[MBEDTLS_MD_MAX_SIZE];
if( okm == NULL )
{
return( MBEDTLS_ERR_HKDF_BAD_INPUT_DATA );
}
hash_len = mbedtls_md_get_size( md );
if( prk_len < hash_len || hash_len == 0 )
{
return( MBEDTLS_ERR_HKDF_BAD_INPUT_DATA );
}
if( info == NULL )
{
info = (const unsigned char *) "";
info_len = 0;
}
n = okm_len / hash_len;
if( okm_len % hash_len != 0 )
{
n++;
}
/*
* Per RFC 5869 Section 2.3, okm_len must not exceed
* 255 times the hash length
*/
if( n > 255 )
{
return( MBEDTLS_ERR_HKDF_BAD_INPUT_DATA );
}
mbedtls_md_init( &ctx );
if( ( ret = mbedtls_md_setup( &ctx, md, 1 ) ) != 0 )
{
goto exit;
}
memset( t, 0, hash_len );
/*
* Compute T = T(1) | T(2) | T(3) | ... | T(N)
* Where T(N) is defined in RFC 5869 Section 2.3
*/
for( i = 1; i <= n; i++ )
{
size_t num_to_copy;
unsigned char c = i & 0xff;
ret = mbedtls_md_hmac_starts( &ctx, prk, prk_len );
if( ret != 0 )
{
goto exit;
}
ret = mbedtls_md_hmac_update( &ctx, t, t_len );
if( ret != 0 )
{
goto exit;
}
ret = mbedtls_md_hmac_update( &ctx, info, info_len );
if( ret != 0 )
{
goto exit;
}
/* The constant concatenated to the end of each T(n) is a single octet.
* */
ret = mbedtls_md_hmac_update( &ctx, &c, 1 );
if( ret != 0 )
{
goto exit;
}
ret = mbedtls_md_hmac_finish( &ctx, t );
if( ret != 0 )
{
goto exit;
}
num_to_copy = i != n ? hash_len : okm_len - where;
memcpy( okm + where, t, num_to_copy );
where += hash_len;
t_len = hash_len;
}
exit:
mbedtls_md_free( &ctx );
mbedtls_platform_zeroize( t, sizeof( t ) );
return( ret );
}

15
src/HKDF.h Normal file
View File

@ -0,0 +1,15 @@
#include <Arduino.h>
/////////////////////////////////////////////////
// HKDF-SHA-512 Structure
//
// This is a wrapper around mbedtls_hkdf, which is NOT
// included in the normal Arduino-ESP32 library.
// Code was instead downloaded from MBED GitHub directly and
// incorporated under hkdf.cpp, with a wrapper to always
// use SHA-512 with 32 bytes of output as required by HAP.
struct HKDF {
int create(uint8_t *outputKey, uint8_t *inputKey, int inputLen, char *salt, char *info); // output of HKDF is always a 32-byte key derived from an input key, a salt string, and an info string
};

1057
src/HomeSpan.cpp Normal file

File diff suppressed because it is too large Load Diff

222
src/HomeSpan.h Normal file
View File

@ -0,0 +1,222 @@
#include <Arduino.h>
#include "Settings.h"
using std::vector;
enum statusCode { // HAP Table 6-11
SC_OK=0,
SC_Unable=-70402,
SC_Busy=-70403,
SC_ReadOnly=-70404,
SC_WriteOnly=-70405,
SC_NotifyNotAllowed=-70406,
SC_UnknownResource=-70409,
SC_InvalidValue=-70410,
SC_TBD=-1 // status To-Be-Determined (TBD) once service.update() called
};
enum {
GET_AID=1,
GET_META=2,
GET_PERMS=4,
GET_TYPE=8,
GET_EV=16,
GET_DESC=32,
GET_ALL=255
};
// Forward-Declarations
struct Span;
struct SpanAccessory;
struct SpanService;
struct SpanCharacteristic;
struct SpanRange;
struct SpanPut;
struct SpanPBList;
///////////////////////////////
struct SpanConfig {
int configNumber=0; // configuration number - broadcast as Bonjour "c#" (computed automatically)
uint8_t hashCode[48]={0}; // SHA-384 hash of Span Database stored as a form of unique "signature" to know when to update the config number upon changes
};
///////////////////////////////
struct Span{
char *displayName; // display name for this device - broadcast as part of Bonjour MDNS
char *hostNameBase; // base of host name of this device - full host name broadcast by Bonjour MDNS will have 6-byte accessoryID as well as '.local' automatically appended
char *modelName; // model name of this device - broadcast as Bonjour field "md"
char category[3]=""; // category ID of primary accessory - broadcast as Bonjour field "ci" (HAP Section 13)
int resetPin=21; // drive this pin low to "factory" reset NVS data on start-up
SpanPBList *pbHead=NULL; // head of linked-list of characteristics to auto-turnoff after they are turned on (to emulate a single-shot PushButton)
SpanConfig hapConfig; // track configuration changes to the HAP Accessory database; used to increment the configuration number (c#) when changes found
vector<SpanAccessory *> Accessories; // vector of pointers to all Accessories
void begin(Category catID,
char *displayName="HomeSpan Server",
char *hostNameBase="homespan",
char *modelName="HS-ESP32");
void poll(); // 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 initWifi(); // initialize and connect to WiFi network
void processSerialCommand(char *c); // process command 'c' (typically from readSerial, though can be called with any 'c')
int sprintfAttributes(char *cBuf); // prints Attributes JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
void prettyPrint(char *buf, int nsp=2); // print arbitrary JSON from buf to serial monitor, formatted with indentions of 'nsp' spaces
SpanCharacteristic *find(int aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found)
int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request
int updateCharacteristics(char *buf, SpanPut *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail
int sprintfAttributes(SpanPut *pObj, int nObj, char *cBuf); // prints SpanPut object into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
int sprintfAttributes(char **ids, int numIDs, int flags, char *cBuf); // prints accessory.characteristic ids into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics
int sprintfNotify(SpanPut *pObj, int nObj, char *cBuf, int conNum, int &numNotify); // prints notification JSON into buf based on SpanPut objects and specified connection number
void setResetPin(int pin){resetPin=pin;} // sets new pin to be used for factory reset
};
///////////////////////////////
struct SpanAccessory{
int aid=0; // Accessory Instance ID (HAP Table 6-1)
int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory
vector<SpanService *> Services; // vector of pointers to all Services in this Accessory
SpanAccessory();
int sprintfAttributes(char *cBuf); // prints Accessory JSON database into buf, unless buf=NULL; return number of characters printed, excluding null terminator, even if buf=NULL
};
///////////////////////////////
struct SpanService{
int iid=0; // Instance ID (HAP Table 6-2)
const char *type; // Service Type
boolean hidden=false; // optional property indicating service is hidden
boolean primary=false; // optional property indicating service is primary
vector<SpanCharacteristic *> Characteristics; // vector of pointers to all Characteristics in this Service
SpanService(const char *type, ServiceType mod=ServiceType::Regular);
int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator
virtual statusCode update() {return(SC_OK);} // update Service and return final statusCode based on updated Characteristics - should be overridden by DEVICE-SPECIFIC Services
};
///////////////////////////////
struct SpanCharacteristic{
enum { // create bitflags based on HAP Table 6-4
PR=1,
PW=2,
EV=4,
AA=8,
TW=16,
HD=32,
WR=64
};
enum FORMAT { // HAP Table 6-5
BOOL,
UINT8,
UINT16,
UINT32,
UINT64,
INT,
FLOAT,
STRING
};
union UVal {
boolean BOOL;
uint8_t UINT8;
uint16_t UINT16;
uint32_t UINT32;
uint64_t UINT64;
int32_t INT;
double FLOAT;
const char *STRING;
};
int iid=0; // Instance ID (HAP Table 6-3)
char *type; // Characteristic Type
UVal value; // Characteristic Value
uint8_t perms; // Characteristic Permissions
FORMAT format; // Characteristic Format
char *desc=NULL; // Characteristic Description (optional)
SpanRange *range=NULL; // Characteristic min/max/step; NULL = default values (optional)
boolean ev[MAX_CONNECTIONS]={false}; // Characteristic Event Notify Enable (per-connection)
int aid=0; // Accessory ID - passed through from Service containing this Characteristic
boolean isUpdated=false; // set to true when new value has been requested by PUT /characteristic
UVal newValue; // the updated value requested by PUT /characteristic
SpanService *service=NULL; // pointer to Service containing this Characteristic
SpanCharacteristic(char *type, uint8_t perms);
SpanCharacteristic(char *type, uint8_t perms, boolean value);
SpanCharacteristic(char *type, uint8_t perms, uint8_t value);
SpanCharacteristic(char *type, uint8_t perms, uint16_t value);
SpanCharacteristic(char *type, uint8_t perms, uint32_t value);
SpanCharacteristic(char *type, uint8_t perms, uint64_t value);
SpanCharacteristic(char *type, uint8_t perms, int32_t value);
SpanCharacteristic(char *type, uint8_t perms, double value);
SpanCharacteristic(char *type, uint8_t perms, const char* value);
int sprintfAttributes(char *cBuf, int flags); // prints Characteristic JSON records into buf, according to flags mask; return number of characters printed, excluding null terminator
statusCode loadUpdate(char *val, char *ev); // load updated val/ev from PUT /characteristic JSON request. Return intiial HAP status code (checks to see if characteristic is found, is writable, etc.)
void autoOff(int waitTime=250); // turns Characteristic off (false) automatically after waitTime milliseconds; only applicable to BOOL characteristics
};
///////////////////////////////
struct SpanRange{
int min;
int max;
int step;
SpanRange(int _min, int _max, int _step) : min{_min}, max{_max}, step{_step} {};
};
///////////////////////////////
struct SpanPut{ // storage to process PUT /characteristics request
int aid; // aid to update
int iid; // iid to update
char *val=NULL; // updated value (optional, though either at least 'val' or 'ev' must be specified)
char *ev=NULL; // updated event notification flag (optional, though either at least 'val' or 'ev' must be specified)
statusCode status; // return status (HAP Table 6-11)
SpanCharacteristic *characteristic=NULL; // Characteristic to update (NULL if not found)
};
///////////////////////////////
struct SpanPBList{
SpanPBList *next=NULL; // next item in linked-list
SpanCharacteristic *characteristic; // characteristic to auto-turnoff whenever activated
int waitTime; // time to wait until auto-turnoff (in milliseconds)
unsigned long alarmTime; // alarm time for trigger to auto-turnoff
boolean start=false; // alarm timer started
boolean trigger=false; // alarm timer triggered
};
/////////////////////////////////////////////////
// Extern Variables
extern Span homeSpan;
/////////////////////////////////////////////////
#include "Services.h"

83
src/RFControl.cpp Normal file
View File

@ -0,0 +1,83 @@
#include <Arduino.h>
#include <soc/rmt_reg.h>
#include <soc/dport_reg.h>
#include "RFControl.h"
///////////////////
RFControl::RFControl(int pin){
if(!configured){ // configure RMT peripheral
DPORT_REG_SET_BIT(DPORT_PERIP_CLK_EN_REG,1<<9); // enable RMT clock by setting bit 9
DPORT_REG_CLR_BIT(DPORT_PERIP_RST_EN_REG,1<<9); // set RMT to normal ("un-reset") mode by clearing bit 9
REG_SET_BIT(RMT_APB_CONF_REG,3); // enables access to RMT memory and enables wraparound mode (though the latter does not seem to be needed to set continuous TX)
REG_WRITE(RMT_INT_ENA_REG,1<<RMT_CH0_TX_END_INT_ENA_S); // enable end-transmission interrupt so that interrupt vector is called
REG_WRITE(RMT_CH0CONF0_REG,0x08000000); // disable carrier wave; set channel 0 to use all 8 blocks of RMT memory
esp_intr_alloc(ETS_RMT_INTR_SOURCE,0,eot_int,NULL,NULL); // set RMT general interrupt vector
configured=true;
}
this->pin=pin;
pinMode(pin,OUTPUT);
REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG+4*pin,87); // set GPIO OUTPUT of pin in GPIO_MATRIX to use RMT Channel-0 Output (=signal 87)
REG_SET_FIELD(GPIO_FUNC0_OUT_SEL_CFG_REG+4*pin,GPIO_FUNC0_OEN_SEL,1); // use GPIO_ENABLE_REG of pin (not RMT) to enable this channel
REG_WRITE(GPIO_ENABLE_W1TC_REG,1<<pin); // disable output on pin - enable only when started
}
///////////////////
void RFControl::start(int _numCycles, int tickTime){
pRMT[pCount]=0; // load end-marker (zero bytes) into RMT memory
REG_WRITE(GPIO_ENABLE_W1TS_REG,1<<pin); // enable output on pin
numCycles=_numCycles; // set number of cycles to repeat transmission
REG_SET_FIELD(RMT_CH0CONF0_REG,RMT_DIV_CNT_CH0,tickTime); // set one tick = 1 microsecond * tickTime (RMT will be set to use 1 MHz REF_TICK, not 80 MHz APB_CLK)
REG_WRITE(RMT_CH0CONF1_REG,0x0000000D); // use REF_TICK clock; reset xmit and receive memory address to start of channel; START TRANSMITTING!
while(numCycles); // wait while transmission in progress
REG_WRITE(GPIO_ENABLE_W1TC_REG,1<<pin); // disable output on pin
}
///////////////////
void RFControl::clear(){
pCount=0;
}
///////////////////
void RFControl::add(uint16_t onTime, uint16_t offTime){
if(pCount==511){ // maximum number of pulses reached (saving one space for end-marker)
Serial.println("\n*** ERROR: Can't add more than 511 pulses to RF Control Module\n\n");
} else
if(offTime>32767 || onTime>32767){
Serial.println("\n*** ERROR: Request to add RF Control pulse with ON or OFF time exceeds 32767 maximum allowed number of ticks\n\n");
} else {
pRMT[pCount++]=(offTime<<16)+onTime+(1<<15); // load pulse information into RMT memory and increment pointer
}
}
///////////////////
void RFControl::eot_int(void *arg){
numCycles--;
REG_WRITE(RMT_INT_CLR_REG,~0); // interrupt MUST be cleared first; transmission re-started after (clearing after restart crestes havoc)
if(numCycles)
REG_WRITE(RMT_CH0CONF1_REG,0x0000000D); // use REF_TICK clock; reset xmit and receive memory address to start of channel; re-start transmission
}
///////////////////
boolean RFControl::configured=false;
volatile int RFControl::numCycles;
uint32_t *RFControl::pRMT=(uint32_t *)RMT_CHANNEL_MEM(0);
int RFControl::pCount=0;
RFControl RF433(RF433_PIN);
RFControl RF315(RF315_PIN);

29
src/RFControl.h Normal file
View File

@ -0,0 +1,29 @@
////////////////////////////////////
// RF Control Module //
////////////////////////////////////
#define RF433_PIN 22 // pin used for 433MHz transmitter
#define RF315_PIN 23 // pin used for 315MHz transmitter
class RFControl {
private:
int pin;
static volatile int numCycles;
static boolean configured;
static uint32_t *pRMT;
static int pCount;
static void eot_int(void *arg);
public:
RFControl(int pin); // creates transmitter on pin
static void clear(); // clears transmitter memory
static void add(uint16_t onTime, uint16_t offTime); // adds pulse of onTime ticks HIGH followed by offTime ticks LOW
void start(int _numCycles, int tickTime=1); // starts transmission of pulses, repeated for numCycles, where each tick in pulse is tickTime microseconds long
};
// Two transmitters are defined
extern RFControl RF433;
extern RFControl RF315;

266
src/SRP.cpp Normal file
View File

@ -0,0 +1,266 @@
#include <sodium.h>
#include "HAP.h"
/////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////
SRP6A::SRP6A(){
uint8_t tBuf[768]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
char N3072[]="FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74"
"020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437"
"4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED"
"EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF05"
"98DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB"
"9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B"
"E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF695581718"
"3995497CEA956AE515D2261898FA051015728E5A8AAAC42DAD33170D04507A33"
"A85521ABDF1CBA64ECFB850458DBEF0A8AEA71575D060C7DB3970F85A6E1E4C7"
"ABF5AE8CDB0933D71E8C94E04A25619DCEE3D2261AD2EE6BF12FFA06D98A0864"
"D87602733EC86A64521F2B18177B200CBBE117577A615D6C770988C0BAD946E2"
"08E24FA074E5AB3143DB5BFCE0FD108E4B82D120A93AD2CAFFFFFFFFFFFFFFFF";
// initialize MPI structures
mbedtls_mpi_init(&N);
mbedtls_mpi_init(&g);
mbedtls_mpi_init(&s);
mbedtls_mpi_init(&x);
mbedtls_mpi_init(&v);
mbedtls_mpi_init(&A);
mbedtls_mpi_init(&b);
mbedtls_mpi_init(&B);
mbedtls_mpi_init(&S);
mbedtls_mpi_init(&k);
mbedtls_mpi_init(&u);
mbedtls_mpi_init(&K);
mbedtls_mpi_init(&M1);
mbedtls_mpi_init(&M1V);
mbedtls_mpi_init(&M2);
mbedtls_mpi_init(&_rr);
mbedtls_mpi_init(&t1);
mbedtls_mpi_init(&t2);
mbedtls_mpi_init(&t3);
// load N and g into mpi structures
mbedtls_mpi_read_string(&N,16,N3072);
mbedtls_mpi_lset(&g,5);
// compute k = SHA512( N | PAD(g) )
mbedtls_mpi_write_binary(&N,tBuf,384); // write N into first half of staging buffer
mbedtls_mpi_write_binary(&g,tBuf+384,384); // write g into second half of staging buffer (fully padded with leading zeros)
mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&k,tHash,64); // load hash result into mpi structure k
}
//////////////////////////////////////
void SRP6A::createPublicKey(){
uint8_t tBuf[80]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
char icp[22]; // storage for I:P
getSalt(); // create and load s (random 16 bytes)
getPrivateKey(); // create and load b (random 32 bytes)
getSetupCode(icp); // I="Pair-Setup" and P=Pair-Setup Code (in form XXX-XX-XXX)
// compute x = SHA512( s | SHA512( I | ":" | P ) )
mbedtls_mpi_write_binary(&s,tBuf,16); // write s into first 16 bytes of staging buffer
mbedtls_sha512_ret((uint8_t *)icp,strlen(icp),tBuf+16,0); // create hash of username:password and write into last 64 bytes of staging buffer
mbedtls_sha512_ret(tBuf,80,tHash,0); // create second hash of salted, hashed username:password
mbedtls_mpi_read_binary(&x,tHash,64); // load hash result into mpi structure x
// compute v = g^x % N
mbedtls_mpi_exp_mod(&v,&g,&x,&N,&_rr); // create verifier, v (_rr is an internal "helper" structure that mbedtls uses to speed up subsequent exponential calculations)
// compute B = kv + g^b %N
mbedtls_mpi_mul_mpi(&t1,&k,&v); // t1 = k*v
mbedtls_mpi_exp_mod(&t2,&g,&b,&N,&_rr); // t2 = g^b %N
mbedtls_mpi_add_mpi(&t3,&t1,&t2); // t3 = t1 + t2
mbedtls_mpi_mod_mpi(&B,&t3,&N); // B = t3 %N = ACCESSORY PUBLIC KEY
}
//////////////////////////////////////
void SRP6A::getSalt(){
uint8_t salt[16];
randombytes_buf(salt,16); // generate 16 random bytes using libsodium (which uses the ESP32 hardware-based random number generator)
mbedtls_mpi_read_binary(&s,salt,16);
}
//////////////////////////////////////
void SRP6A::getPrivateKey(){
uint8_t privateKey[32];
randombytes_buf(privateKey,16); // generate 32 random bytes using libsodium (which uses the ESP32 hardware-based random number generator)
mbedtls_mpi_read_binary(&b,privateKey,32);
}
//////////////////////////////////////
void SRP6A::getSetupCode(char *c){
sprintf(c,"Pair-Setup:%d%d%d-%d%d-%d%d%d",
randombytes_uniform(10),
randombytes_uniform(10),
randombytes_uniform(10),
randombytes_uniform(10),
randombytes_uniform(10),
randombytes_uniform(10),
randombytes_uniform(10),
randombytes_uniform(10)
);
Serial.print("\n\n");
Serial.print("SET-UP CODE: ");
Serial.print(c+11);
Serial.print("\n\n");
}
//////////////////////////////////////
void SRP6A::createSessionKey(){
uint8_t tBuf[768]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
// compute u = SHA512( PAD(A) | PAD(B) )
mbedtls_mpi_write_binary(&A,tBuf,384); // write A into first half of staging buffer
mbedtls_mpi_write_binary(&B,tBuf+384,384); // write B into second half of staging buffer
mbedtls_sha512_ret(tBuf,768,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&u,tHash,64); // load hash result into mpi structure u
// compute S = (Av^u)^b %N
mbedtls_mpi_exp_mod(&t1,&v,&u,&N,&_rr); // t1 = v^u %N
mbedtls_mpi_mul_mpi(&t2,&A,&t1); // t2 = A*t1
mbedtls_mpi_exp_mod(&S,&t2,&b,&N,&_rr); // S = t2^b %N
// compute K = SHA512( S )
mbedtls_mpi_write_binary(&S,tBuf,384); // write S into staging buffer (only first half of buffer will be used)
mbedtls_sha512_ret(tBuf,384,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&K,tHash,64); // load hash result into mpi structure K. This is the SRP SHARED SECRET KEY
mbedtls_mpi_write_binary(&K,sharedSecret,64); // store SHARED SECRET in easy-to-use binary (uint8_t) format
}
//////////////////////////////////////
int SRP6A::verifyProof(){
uint8_t tBuf[976]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
size_t count=0; // total number of bytes for final hash
size_t sLen;
mbedtls_mpi_write_binary(&N,tBuf,384); // write N into staging buffer
mbedtls_sha512_ret(tBuf,384,tHash,0); // create hash of data
mbedtls_sha512_ret((uint8_t *)g3072,1,tBuf,0); // create hash of g, but place output directly into staging buffer
for(int i=0;i<64;i++) // H(g) -> H(g) XOR H(N), with results in first 64 bytes of staging buffer
tBuf[i]^=tHash[i];
mbedtls_sha512_ret((uint8_t *)I,strlen(I),tBuf+64,0); // create hash of userName and concatenate result to end of staging buffer
mbedtls_mpi_write_binary(&s,tBuf+128,16); // concatenate s to staging buffer
sLen=mbedtls_mpi_size(&A); // get actual size of A
mbedtls_mpi_write_binary(&A,tBuf+144,sLen); // concatenate A to staging buffer. Note A is NOT padded with leading zeros (so may be less than 384 bytes)
count=144+sLen; // total bytes written to staging buffer so far
sLen=mbedtls_mpi_size(&B); // get actual size of B
mbedtls_mpi_write_binary(&B,tBuf+count,sLen); // concatenate B to staging buffer. Note B is NOT padded with leading zeros (so may be less than 384 bytes)
count+=sLen; // increment total bytes written to staging buffer
mbedtls_mpi_write_binary(&K,tBuf+count,64); // concatenate K to staging buffer (should always be 64 bytes since it is a hashed value)
count+=64; // final total of bytes written to staging buffer
mbedtls_sha512_ret(tBuf,count,tHash,0); // create hash of data
mbedtls_mpi_read_binary(&M1V,tHash,64); // load hash result into mpi structure M1V
if(!mbedtls_mpi_cmp_mpi(&M1,&M1V)) // cmp_mpi uses same logic as strcmp: returns 0 if EQUAL, otherwise +/- 1
return(1); // success - proof from HAP Client is verified
return(0);
}
//////////////////////////////////////
void SRP6A::createProof(){
uint8_t tBuf[512]; // temporary buffer for staging
uint8_t tHash[64]; // temporary buffer for storing SHA-512 results
// compute M2 = H( A | M1 | K )
mbedtls_mpi_write_binary(&A,tBuf,384); // write A into staging buffer
mbedtls_mpi_write_binary(&M1,tBuf+384,64); // concatenate M1 (now verified) to staging buffer
mbedtls_mpi_write_binary(&K,tBuf+448,64); // concatenate K to staging buffer
mbedtls_sha512_ret(tBuf,512,tBuf,0); // create hash of data
mbedtls_mpi_read_binary(&M2,tBuf,64); // load hash results into mpi structure M2
}
//////////////////////////////////////
int SRP6A::loadTLV(kTLVType tag, mbedtls_mpi *mpi){
int nBytes=mbedtls_mpi_size(mpi);
uint8_t *buf=HAPClient::tlv8.buf(tag,nBytes);
if(!buf)
return(0);
mbedtls_mpi_write_binary(mpi,buf,nBytes);
return(1);
}
//////////////////////////////////////
int SRP6A::writeTLV(kTLVType tag, mbedtls_mpi *mpi){
int nBytes=HAPClient::tlv8.len(tag);
if(nBytes>0){
mbedtls_mpi_read_binary(mpi,HAPClient::tlv8.buf(tag),nBytes);
return(1);
};
return(0);
}
//////////////////////////////////////
void SRP6A::print(mbedtls_mpi *mpi){
char sBuf[1000];
size_t sLen;
mbedtls_mpi_write_string(mpi,16,sBuf,1000,&sLen);
Serial.print((sLen-1)/2); // subtract 1 for null-terminator, and then divide by 2 to get number of bytes (e.g. 4F = 2 characters, but represents just one mpi byte)
Serial.print(" ");
Serial.println(sBuf);
}
//////////////////////////////////////

56
src/SRP.h Normal file
View File

@ -0,0 +1,56 @@
#include <mbedtls/sha512.h>
#include <mbedtls/bignum.h>
/////////////////////////////////////////////////
// SRP-6A Structure from RFC 5054 (Nov 2007)
// ** HAP uses N=3072-bit Group specified in RFC 5054
// ** HAP replaces H=SHA-1 with H=SHA-512 (HAP Section 5.5)
//
// I = SRP-6A username, defined by HAP to be the word "Pair-Setup"
// P = SRP-6A password, defined to be equal to the accessory's 8-digit setup code in the format "XXX-XX-XXX"
struct SRP6A {
mbedtls_mpi N; // N - 3072-bit Group pre-defined prime used for all SRP-6A calculations (384 bytes)
mbedtls_mpi g; // g - pre-defined generator for the specified 3072-bit Group (g=5)
mbedtls_mpi k; // k = H(N | PAD(g)) - SRP-6A multiplier (which is different from versions SRP-6 or SRP-3)
mbedtls_mpi s; // s - randomly-generated salt (16 bytes)
mbedtls_mpi x; // x = H(s | H(I | ":" | P)) - salted, double-hash of username and password (64 bytes)
mbedtls_mpi v; // v = g^x %N - SRP-6A verifier (max 384 bytes)
mbedtls_mpi b; // b - randomly-generated private key for this HAP accessory (i.e. the SRP Server) (32 bytes)
mbedtls_mpi B; // B = k*v + g^b %N - public key for this accessory (max 384 bytes)
mbedtls_mpi A; // A - public key RECEIVED from HAP Client (max 384 bytes)
mbedtls_mpi u; // u = H(PAD(A) | PAB(B)) - "u-factor" (64 bytes)
mbedtls_mpi S; // S = (A*v^u)^b %N - SRP shared "premaster" key, based on accessory private key and client public key (max 384 bytes)
mbedtls_mpi K; // K = H( S ) - SRP SHARED SECRET KEY (64 bytes)
mbedtls_mpi M1; // M1 - proof RECEIVED from HAP Client (64 bytes)
mbedtls_mpi M1V; // M1V - accessory's independent computation of M1 to verify proof (see code for details of computation)
mbedtls_mpi M2; // M2 - accessory's counter-proof to send to HAP Client after M1=M1V has been verified (64 bytes)
mbedtls_mpi t1; // temporary mpi structures for intermediate results
mbedtls_mpi t2;
mbedtls_mpi t3;
mbedtls_mpi _rr; // _rr - temporary "helper" for large exponential modulus calculations
char I[11]="Pair-Setup"; // I - userName pre-defined by HAP pairing setup protocol
char g3072[2]="\x05"; // g - 3072-bit Group generator
uint8_t sharedSecret[64]; // permanent storage for binary version of SHARED SECRET KEY for ease of use upstream
SRP6A(); // initializes N, G, and computes k
void getSalt(); // generates and stores random 16-byte salt, s
void getPrivateKey(); // generates and stores random 32-byte private key, b
void getSetupCode(char *c); // generates and displays random 8-digit Pair-Setup code, P, in format XXX-XX-XXX
void createPublicKey(); // computes x, v, and B from random s, P, and b
void createSessionKey(); // computes u from A and B, and then S from A, v, u, and b
int loadTLV(kTLVType tag, mbedtls_mpi *mpi); // load binary contents of mpi into a TLV record and set its length
int writeTLV(kTLVType tag, mbedtls_mpi *mpi); // write binary contents of a TLV record into an mpi
int verifyProof(); // verify M1 SRP6A Proof received from HAP client (return 1 on success, 0 on failure)
void createProof(); // create M2 server-side SRP6A Proof based on M1 as received from HAP Client
void print(mbedtls_mpi *mpi); // prints size of mpi (in bytes), followed by the mpi itself (as a hex charcter string) - for diagnostic purposes only
};

108
src/Services.h Normal file
View File

@ -0,0 +1,108 @@
//////////////////////////////////
// HAP SERVICES (HAP Chapter 8) //
//////////////////////////////////
namespace Service {
struct AccessoryInformation : SpanService { AccessoryInformation(ServiceType mod=ServiceType::Regular) : SpanService{"3E", mod}{} };
struct AirPurifier : SpanService { AirPurifier(ServiceType mod=ServiceType::Regular) : SpanService{"BB", mod}{} };
struct AirQualitySensor : SpanService { AirQualitySensor(ServiceType mod=ServiceType::Regular) : SpanService{"8D", mod}{} };
struct BatteryService : SpanService { BatteryService(ServiceType mod=ServiceType::Regular) : SpanService{"96", mod}{} };
struct CarbonDioxideSensor : SpanService { CarbonDioxideSensor(ServiceType mod=ServiceType::Regular) : SpanService{"97", mod}{} };
struct CarbonMonoxideSensor : SpanService { CarbonMonoxideSensor(ServiceType mod=ServiceType::Regular) : SpanService{"7F", mod}{} };
struct ContactSensor : SpanService { ContactSensor(ServiceType mod=ServiceType::Regular) : SpanService{"80", mod}{} };
struct Doorbell : SpanService { Doorbell(ServiceType mod=ServiceType::Regular) : SpanService{"121", mod}{} };
struct Fan : SpanService { Fan(ServiceType mod=ServiceType::Regular) : SpanService{"B7", mod}{} };
struct Faucet : SpanService { Faucet(ServiceType mod=ServiceType::Regular) : SpanService{"D7", mod}{} };
struct FilterMaintenance : SpanService { FilterMaintenance(ServiceType mod=ServiceType::Regular) : SpanService{"BA", mod}{} };
struct GarageDoorOpener : SpanService { GarageDoorOpener(ServiceType mod=ServiceType::Regular) : SpanService{"41", mod}{} };
struct HAPProtocolInformation : SpanService { HAPProtocolInformation(ServiceType mod=ServiceType::Regular) : SpanService{"A2", mod}{} };
struct HeaterCooler : SpanService { HeaterCooler(ServiceType mod=ServiceType::Regular) : SpanService{"BC", mod}{} };
struct HumidifierDehumidifier : SpanService { HumidifierDehumidifier(ServiceType mod=ServiceType::Regular) : SpanService{"BD", mod}{} };
struct HumiditySensor : SpanService { HumiditySensor(ServiceType mod=ServiceType::Regular) : SpanService{"82", mod}{} };
struct IrrigationSystem : SpanService { IrrigationSystem(ServiceType mod=ServiceType::Regular) : SpanService{"CF", mod}{} };
struct LeakSensor : SpanService { LeakSensor(ServiceType mod=ServiceType::Regular) : SpanService{"83", mod}{} };
struct LightBulb : SpanService { LightBulb(ServiceType mod=ServiceType::Regular) : SpanService{"43", mod}{} };
struct LightSensor : SpanService { LightSensor(ServiceType mod=ServiceType::Regular) : SpanService{"84", mod}{} };
struct MotionSensor : SpanService { MotionSensor(ServiceType mod=ServiceType::Regular) : SpanService{"85", mod}{} };
struct OccupancySensor : SpanService { OccupancySensor(ServiceType mod=ServiceType::Regular) : SpanService{"86", mod}{} };
struct Outlet : SpanService { Outlet(ServiceType mod=ServiceType::Regular) : SpanService{"47", mod}{} };
struct ServiceLabel : SpanService { ServiceLabel(ServiceType mod=ServiceType::Regular) : SpanService{"47", mod}{} };
struct Slat : SpanService { Slat(ServiceType mod=ServiceType::Regular) : SpanService{"B9", mod}{} };
struct StatelessProgrammableSwitch : SpanService { StatelessProgrammableSwitch(ServiceType mod=ServiceType::Regular) : SpanService{"89", mod}{} };
struct Switch : SpanService { Switch(ServiceType mod=ServiceType::Regular) : SpanService{"49", mod}{} };
struct TemperatureSensor : SpanService { TemperatureSensor(ServiceType mod=ServiceType::Regular) : SpanService{"8A", mod}{} };
struct Thermostat : SpanService { Thermostat(ServiceType mod=ServiceType::Regular) : SpanService{"4A", mod}{} };
struct Valve : SpanService { Valve(ServiceType mod=ServiceType::Regular) : SpanService{"D0", mod}{} };
struct Window : SpanService { Window(ServiceType mod=ServiceType::Regular) : SpanService{"8B", mod}{} };
struct WindowCovering : SpanService { WindowCovering(ServiceType mod=ServiceType::Regular) : SpanService{"8C", mod}{} };
}
/////////////////////////////////////////
// HAP CHARACTERISTICS (HAP Chapter 9) //
/////////////////////////////////////////
namespace Characteristic {
struct FirmwareRevision : SpanCharacteristic { FirmwareRevision(char *value) : SpanCharacteristic{"52",PR,(char *)value}{} };
struct Identify : SpanCharacteristic { Identify() : SpanCharacteristic{"14",PW,(boolean)false}{} };
struct Manufacturer : SpanCharacteristic { Manufacturer(char *value) : SpanCharacteristic{"20",PR,(char *)value}{} };
struct Model : SpanCharacteristic { Model(char *value) : SpanCharacteristic{"21",PR,(char *)value}{} };
struct Name : SpanCharacteristic { Name(char *value) : SpanCharacteristic{"23",PR,(char *)value}{} };
struct SerialNumber : SpanCharacteristic { SerialNumber(char *value) : SpanCharacteristic{"30",PR,(char *)value}{} };
struct On : SpanCharacteristic { On(boolean value=false) : SpanCharacteristic{"25",PR+PW+EV,(boolean)value}{} };
struct Active : SpanCharacteristic { Active(uint8_t value=0) : SpanCharacteristic{"B0",PR+PW+EV,(uint8_t)value}{} };
struct Brightness : SpanCharacteristic { Brightness(int value=0) : SpanCharacteristic{"8",PR+PW+EV,(int)value}{} };
struct Hue : SpanCharacteristic { Hue(double value=0) : SpanCharacteristic{"13",PR+PW+EV,(double)value}{} };
struct Saturation : SpanCharacteristic { Saturation(double value=0) : SpanCharacteristic{"2F",PR+PW+EV,(double)value}{} };
struct ColorTemperature : SpanCharacteristic { ColorTemperature(uint32_t value=50) : SpanCharacteristic{"CE",PR+PW+EV,(uint32_t)value}{} };
struct OutletInUse : SpanCharacteristic { OutletInUse(boolean value=false) : SpanCharacteristic{"26",PR+EV,(boolean)value}{} };
struct Version : SpanCharacteristic { Version(char *value) : SpanCharacteristic{"37",PR,(char *)value}{} };
}

72
src/Settings.h Normal file
View File

@ -0,0 +1,72 @@
// USER-DEFINED SETTINGS AND REFERENCE ENUMERATION CLASSES
#pragma once
//////////////////////////////////////////////////////
// Maximum number of simultaenous IP connections //
// HAP requires at least 8 //
const int MAX_CONNECTIONS=8;
/////////////////////////////////////////////////////
// Debug level -- controls message output //
// 0=Minimal, 1=Informative, 2=All //
#define DEBUG_LEVEL 1
//-------------------------------------------------//
#if DEBUG_LEVEL>1
#define LOG2(x) Serial.print(x)
#else
#define LOG2(x)
#endif
#if DEBUG_LEVEL>0
#define LOG1(x) Serial.print(x)
#else
#define LOG1(x)
#endif
//////////////////////////////////////////////////////
// Types of Services (default is Regular) //
// Reference: HAP Table 6-2 //
enum ServiceType {
Regular,
Hidden,
Primary
};
//////////////////////////////////////////////////////
// Types of Accessory Categories //
// Reference: HAP Section 13 //
enum class Category {
Other=1,
Bridges=2,
Fans=3,
GarageDoorOpeners=4,
Lighting=5,
Locks=6,
Outlets=7,
Switches=8,
Thermostats=9,
Sensors=10,
SecuritySystems=11,
Doors=12,
Windows=13,
WindowCoverings=14,
ProgrammableSwitches=15,
IPCameras=17,
VideoDoorbells=18,
AirPurifiers=19,
Heaters=20,
AirConditioners=21,
Humidifiers=22,
Dehumidifiers=23,
Sprinklers=28,
Faucets=29,
ShowerSystems=30
};

317
src/TLV.h Normal file
View File

@ -0,0 +1,317 @@
#include "Settings.h"
template <class tagType, int maxTags>
class TLV {
int cLen; // total number of bytes in all defined TLV records, including TAG andf LEN (suitable for use as Content-Length in HTTP Body)
int numTags; // actual number of tags defined
struct tlv_t {
tagType tag; // TAG
int len; // LENGTH
uint8_t *val; // VALUE buffer
int maxLen; // maximum length of VALUE buffer
char *name; // abbreviated name of this TAG
};
tlv_t tlv[maxTags]; // pointer to array of TLV record structures
tlv_t *find(tagType tag); // returns pointer to TLV record with matching TAG (or NULL if no match)
public:
TLV();
int create(tagType tag, int maxLen, char *name); // creates a new TLV record of type 'tag' with 'maxLen' bytes and display 'name'
void clear(); // clear all TLV structures
int val(tagType tag); // returns VAL for TLV with matching TAG (or -1 if no match)
int val(tagType tag, uint8_t val); // sets and returns VAL for TLV with matching TAG (or -1 if no match)
uint8_t *buf(tagType tag); // returns VAL Buffer for TLV with matching TAG (or NULL if no match)
uint8_t *buf(tagType tag, int len); // set length and returns VAL Buffer for TLV with matching TAG (or NULL if no match or if LEN>MAX)
int len(tagType tag); // returns LEN for TLV matching TAG (or 0 if TAG is found but LEN not yet set; -1 if no match at all)
void print(); // prints all defined TLVs (those with length>0). For diagnostics/debugging only
int unpack(uint8_t *tlvBuf, int nBytes); // unpacks nBytes of TLV content from single byte buffer into individual TLV records (return 1 on success, 0 if fail)
int pack(uint8_t *tlvBuf); // if tlvBuf!=NULL, packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte chunks. Returns number of bytes (that would be) stored in buffer
int pack_old(uint8_t *buf); // packs all defined TLV records (LEN>0) into a single byte buffer, spitting large TLVs into separate 255-byte records. Returns number of bytes stored in buffer
}; // TLV
//////////////////////////////////////
// TLV contructor()
template<class tagType, int maxTags>
TLV<tagType, maxTags>::TLV(){
numTags=0;
}
//////////////////////////////////////
// TLV create(tag, maxLen, name)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::create(tagType tag, int maxLen, char *name){
if(numTags==maxTags){
Serial.print("\n*** ERROR: Can't create new TLC tag type with name='");
Serial.print(name);
Serial.print("' - exceeded number of records reserved\n\n");
return(0);
}
tlv[numTags].tag=tag;
tlv[numTags].maxLen=maxLen;
tlv[numTags].name=name;
tlv[numTags].len=-1;
tlv[numTags].val=(uint8_t *)malloc(maxLen);
numTags++;
}
//////////////////////////////////////
// TLV find(tag)
template<class tagType, int maxTags>
typename TLV<tagType, maxTags>::tlv_t *TLV<tagType, maxTags>::find(tagType tag){
for(int i=0;i<numTags;i++){
if(tlv[i].tag==tag)
return(tlv+i);
}
return(NULL);
}
//////////////////////////////////////
// TLV clear()
template<class tagType, int maxTags>
void TLV<tagType, maxTags>::clear(){
cLen=0;
for(int i=0;i<numTags;i++)
tlv[i].len=-1;
}
//////////////////////////////////////
// TLV val(tag)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::val(tagType tag){
tlv_t *tlv=find(tag);
if(tlv && tlv->len>0)
return(tlv->val[0]);
return(-1);
}
//////////////////////////////////////
// TLV val(tag, val)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::val(tagType tag, uint8_t val){
tlv_t *tlv=find(tag);
if(tlv){
tlv->val[0]=val;
tlv->len=1;
cLen+=tlv->len+2;
return(val);
}
return(-1);
}
//////////////////////////////////////
// TLV buf(tag)
template<class tagType, int maxTags>
uint8_t *TLV<tagType, maxTags>::buf(tagType tag){
tlv_t *tlv=find(tag);
if(tlv)
return(tlv->val);
return(NULL);
}
//////////////////////////////////////
// TLV buf(tag, len)
template<class tagType, int maxTags>
uint8_t *TLV<tagType, maxTags>::buf(tagType tag, int len){
tlv_t *tlv=find(tag);
if(tlv && len<=tlv->maxLen){
tlv->len=len;
cLen+=tlv->len;
for(int i=0;i<tlv->len;i+=255)
cLen+=2;
return(tlv->val);
}
return(NULL);
}
//////////////////////////////////////
// TLV print()
template<class tagType, int maxTags>
void TLV<tagType, maxTags>::print(){
if(DEBUG_LEVEL<2)
return;
char buf[3];
for(int i=0;i<numTags;i++){
if(tlv[i].len>0){
Serial.print(tlv[i].name);
Serial.print("(");
Serial.print(tlv[i].len);
Serial.print(") ");
for(int j=0;j<tlv[i].len;j++){
sprintf(buf,"%02X",tlv[i].val[j]);
Serial.print(buf);
}
Serial.print("\n");
} // len>0
} // loop over all TLVs
}
//////////////////////////////////////
// TLV pack_old(buf)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::pack_old(uint8_t *buf){
int n=0;
for(int i=0;i<numTags;i++){
if(tlv[i].len>0){
*buf++=tlv[i].tag;
*buf++=tlv[i].len;
memcpy(buf,tlv[i].val,tlv[i].len);
buf+=tlv[i].len;
n+=tlv[i].len+2;
} // len>0
} // loop over all TLVs
return(n);
}
//////////////////////////////////////
// TLV pack(tlvBuf)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::pack(uint8_t *tlvBuf){
int n=0;
int nBytes;
for(int i=0;i<numTags;i++){
if((nBytes=tlv[i].len)>0){
for(int j=0;j<tlv[i].len;j+=255,nBytes-=255){
if(tlvBuf!=NULL){
*tlvBuf++=tlv[i].tag;
*tlvBuf++=nBytes>255?255:nBytes;
memcpy(tlvBuf,tlv[i].val+j,nBytes>255?255:nBytes);
tlvBuf+=nBytes>255?255:nBytes;
}
n+=(nBytes>255?255:nBytes)+2;
} // j-loop
} // len>0
} // loop over all TLVs
return(n);
}
//////////////////////////////////////
// TLV len(tag)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::len(tagType tag){
tlv_t *tlv=find(tag);
if(tlv)
return(tlv->len>0?tlv->len:0);
return(-1);
}
//////////////////////////////////////
// TLV unpack(tlvBuf, nBytes)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::unpack(uint8_t *tlvBuf, int nBytes){
clear();
tagType tag;
int tagLen;
uint8_t *val;
int currentLen;
int state=0;
for(int i=0;i<nBytes;i++){
switch(state){
case 0: // ready to read next tag
if((tag=(tagType)tlvBuf[i])==-1){ // read TAG; return with error if not found
clear();
return(0);
}
state=1;
break;
case 1: // ready to read tag length
tagLen=tlvBuf[i]; // read LEN
currentLen=len(tag); // get current length of existing tag
if(!(val=buf(tag,tagLen+currentLen))){ // get VAL Buffer for TAG and set LEN (returns NULL if LEN > maxLen)
clear();
return(0);
}
val+=currentLen; // move val to end of current length (tag repeats to load more than 255 bytes)
if(tagLen==0) // no bytes to read
state=0;
else // move to next state
state=2;
break;
case 2: // ready to read another byte into VAL
*val=tlvBuf[i]; // copy byte into VAL buffer
val++; // increment VAL buffer (already checked for sufficient length above)
tagLen--; // decrement number of bytes to continue copying
if(tagLen==0) // no more bytes to copy
state=0;
break;
} // switch
} // for-loop
if(state==0) // should always end back in state=0
return(1); // return success
clear();
return(0); // return fail
}

49
src/Utils.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "Utils.h"
char *Utils::readSerial(char *c, int max){
int i=0;
char buf;
long sTime=millis();
while(1){
while(!Serial.available()){ // wait until there is a new character
digitalWrite(LED_BUILTIN,((millis()-sTime)/200)%2);
}
buf=Serial.read();
if(buf=='\n'){ // exit upon newline
if(i>0) // characters have been typed
c[i]='\0'; // replace newline with string terminator
return(c); // return updated string
}
c[i]=buf; // store new character
if(i<max) // do not store more than max characters (excluding string terminator)
i++;
} // while(1)
digitalWrite(LED_BUILTIN,LOW);
} // readSerial
//////////////////////////////////////
String Utils::mask(char *c, int n){
String s="";
int len=strlen(c);
for(int i=0;i<len;i++){
if(i<n || i>=len-n)
s+=c[i];
else
s+='*';
}
return(s);
} // mask

9
src/Utils.h Normal file
View File

@ -0,0 +1,9 @@
#include <Arduino.h>
namespace Utils {
char *readSerial(char *c, int max); // read serial port into 'c' until <newline>, but storing only first 'max' characters (the rest are discarded)
String mask(char *c, int n); // simply utility that creates a String from 'c' with all except the first and last 'n' characters replaced by '*'
}

5
src/src.ino Normal file
View File

@ -0,0 +1,5 @@
#error THIS IS NOT COMPILABLE CODE
This is a dummy .ino file that allows you to easily edit the contents of this library using the Arduino IDE.
The code is NOT designed to be compiled from this point. Compile and test the library using one of the examples.