commit
7843ce49a5
|
|
@ -15,7 +15,7 @@ HomeSpan requires version 2.0.0 or later of the [Arduino-ESP32 Board Manager](ht
|
|||
* Utilizes a unique *Service-Centric* approach to creating HomeKit devices
|
||||
* Takes full advantage of the widely-popular Arduino IDE
|
||||
* 100% HAP-R2 compliance
|
||||
* 38 integrated HomeKit Services
|
||||
* Dozens of integrated HomeKit Services
|
||||
* Operates in either Accessory or Bridge mode
|
||||
* Supports pairing with Setup Codes or QR Codes
|
||||
|
||||
|
|
@ -94,6 +94,7 @@ HomeSpan includes the following documentation:
|
|||
* [HomeSpan SpanPoint](docs/NOW.md) - facilitates point-to-point, bi-directional communication between ESP32 Devices using ESP-NOW
|
||||
* [HomeSpan Television Services](docs/TVServices.md) - how to use HomeKit's undocumented Television Services and Characteristics
|
||||
* [HomeSpan Message Logging](docs/Logging.md) - how to generate log messages for display on the Arduino Serial Monitor as well as optionally posted to an integrated Web Log page
|
||||
* [HomeSpan TLV8 Characteristics](docs/TLV8.md) - classes and methods for creating TLV8 objects to use with TLV8-based Characteristics
|
||||
* [HomeSpan Device Cloning](docs/Cloning.md) - seamlessly swap a broken device for a new one without needing to re-pair and lose HomeKit automations
|
||||
* [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library
|
||||
* [HomeSpan FAQ](docs/FAQ.md) - answers to frequently-asked questions
|
||||
|
|
|
|||
|
|
@ -181,7 +181,13 @@ The following **optional** `homeSpan` methods enable additional features and pro
|
|||
* this one-time call to *func* is provided for users that would like to trigger additional actions when the device is first paired, or the device is later unpaired
|
||||
* note this *func* is **not** called upon start-up and should not be used to simply check whether a device is paired or unpaired. It is only called when pairing status changes
|
||||
* the function *func* must be of type *void* and accept one *boolean* argument
|
||||
|
||||
|
||||
* `Span& setControllerCallback(void (*func)())`
|
||||
* sets an optional user-defined callback function, *func*, to be called by HomeSpan every time a new controller is added, removed, or updated, even if the pairing status does not change
|
||||
* note this method differs from `setPairCallback()`, which is only called if the device's pairing status changes, such as when the first controller is added during initial pairing, or the last controller is removed when unpairing
|
||||
* the function *func* must be of type *void* and have no arguments
|
||||
* see the `controllerListBegin()` and `controllerListEnd()` methods for details on how to read the pairing data for each paired controller (*only needed to support certain advanced use cases*)
|
||||
|
||||
* `Span& setStatusCallback(void (*func)(HS_STATUS status))`
|
||||
* sets an optional user-defined callback function, *func*, to be called by HomeSpan whenever its running state (e.g. WiFi Connecting, Pairing Needed...) changes in way that would alter the blinking pattern of the (optional) Status LED
|
||||
* if *func* is set, it will be called regardless of whether or not a Status LED has actually been defined
|
||||
|
|
@ -281,7 +287,7 @@ The following **optional** `homeSpan` methods provide additional run-time functi
|
|||
* allows for dynamically changing the Accessory database during run-time (i.e. changing the configuration *after* the Arduino `setup()` has finished)
|
||||
* deleting an Accessory automatically deletes all Services, Characteristics, and any other resources it contains
|
||||
* outputs Level-1 Log Messages listing all deleted components
|
||||
* note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see updateDatabase() below
|
||||
* note: though deletions take effect immediately, HomeKit Controllers, such as the Home App, will not be aware of these changes until the database configuration number is updated and rebroadcast - see `updateDatabase()` below
|
||||
|
||||
* `boolean updateDatabase()`
|
||||
* recomputes the database configuration number and, if changed, rebroadcasts the new number via MDNS so all connected HomeKit Controllers, such as the Home App, can request a full refresh to accurately reflect the new configuration
|
||||
|
|
@ -290,6 +296,43 @@ The following **optional** `homeSpan` methods provide additional run-time functi
|
|||
* use anytime after dynamically adding one or more Accessories (with `new SpanAccessory(aid)`) or deleting one or more Accessories (with `homeSpan.deleteAccessory(aid)`)
|
||||
* **important**: once you delete an Accessory, you cannot re-use the same *aid* when adding a new Accessory (on the same device) unless the new Accessory is configured with the exact same Services and Characteristics as the deleted Accessory
|
||||
* note: this method is **not** needed if you have a static Accessory database that is fully defined in the Arduino `setup()` function of a sketch
|
||||
|
||||
* `Span& resetIID(uint32_t newIID)`
|
||||
* resets the IID count for the current Accessory to *newIID*, which must be greater than 0
|
||||
* throws an error and halts program if called before at least one Accessory is created
|
||||
* example: `homeSpan.resetIID(100)` causes HomeSpan to set the IID to 100 for the very next Service or Characteristic defined within the current Accessory, and then increment the IID count going forward so that any Services or Characteristics subsequently defined (within the same Accessory) have IID=101, 102, etc.
|
||||
* note: calling this function only affects the IID generation for the current Accessory (the count will be reset to IID=1 upon instantiation of a new Accessory)
|
||||
|
||||
* `const_iterator controllerListBegin()` and `const_iterator controllerListEnd()`
|
||||
* returns a *constant iterator* pointing to either the *beginning*, or the *end*, of an opaque linked list that stores all controller data
|
||||
* iterators should be defined using the `auto` keyword as follows: `auto myIt=homeSpan.controllerListBegin();`
|
||||
* controller data can be read from a de-referenced iterator using the following methods:
|
||||
* `const uint8_t *getID()` returns pointer to the 36-byte ID of the controller
|
||||
* `const uint8_t *getLTPK()` returns pointer to the 32-byte Long Term Public Key of the controller
|
||||
* `boolean isAdmin()` returns true if controller has admin permissions, else returns false
|
||||
* <details><summary>click here for example code</summary><br>
|
||||
|
||||
```C++
|
||||
// Extract and print the same data about each controller that HomeSpan prints to the Serial Monitor when using the 's' CLI command
|
||||
|
||||
Serial.printf("\nController Data\n");
|
||||
|
||||
for(auto it=homeSpan.controllerListBegin(); it!=homeSpan.controllerListEnd(); ++it){ // loop over each controller
|
||||
|
||||
Serial.printf("Admin=%d",it->isAdmin()); // indicate if controller has admin permissions
|
||||
|
||||
Serial.printf(" ID="); // print the 36-byte Device ID of the controller
|
||||
for(int i=0;i<36;i++)
|
||||
Serial.printf("%02X",it->getID()[i]);
|
||||
|
||||
Serial.printf(" LTPK="); // print the 32-byte Long-Term Public Key of the controller)
|
||||
for(int i=0;i<32;i++)
|
||||
Serial.printf("%02X",it->getLTPK()[i]);
|
||||
|
||||
Serial.printf("\n");
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -371,6 +414,9 @@ The following methods are supported:
|
|||
* 0=single press (SpanButton::SINGLE)
|
||||
* 1=double press (SpanButton::DOUBLE)
|
||||
* 2=long press (SpanButton::LONG)
|
||||
|
||||
* `uint32_t getIID()`
|
||||
* returns the IID of the Service
|
||||
|
||||
## *SpanCharacteristic(value [,boolean nvsStore])*
|
||||
|
||||
|
|
@ -395,16 +441,16 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
|
|||
|
||||
* `type T getNewVal<T>()`
|
||||
* a template method that returns the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated. Same casting rules as for `getVal<>()`
|
||||
* only applicable when called from within the `update()` loop of a **SpanService** (if called outside of the `update()` loop, the return value is that same as calling `getVal<>()`)
|
||||
|
||||
* `void setVal(value [,boolean notify])`
|
||||
* sets the value of a numerical-based Characteristic to *value*, and, if *notify* is set to true, notifies all HomeKit Controllers of the change. The *notify* flag is optional and will be set to true if not specified. Setting the *notify* flag to false allows you to update a Characateristic without notifying any HomeKit Controllers, which is useful for Characteristics that HomeKit automatically adjusts (such as a countdown timer) but will be requested from the Accessory if the Home App closes and is then re-opened
|
||||
* works with any integer, boolean, or floating-based numerical *value*, though HomeSpan will convert *value* into the appropriate type for each Characteristic (e.g. calling `setValue(5.5)` on an integer-based Characteristic results in *value*=5)
|
||||
* throws a runtime warning if any of the conditions hold:
|
||||
* the Characteristic is not configured with Event Notification (EV) permission enabled; or
|
||||
* this method is being called from within the `update()` routine of a **SpanService** and `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App); or
|
||||
* *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new min/max range set via a prior call to `setRange()`
|
||||
* the first two restrictions above do not apply to the use of `setVal()` from within the `update()` method of a **SpanService** if you are changing the value of a Characteristic in response to a *write-response* request from HomeKit
|
||||
* *value* is **not** restricted to being an increment of the step size; for example it is perfectly valid to call `setVal(43.5)` after calling `setRange(0,100,5)` on a floating-based Characteristic even though 43.5 does does not align with the step size specified. The Home App will properly retain the value as 43.5, though it will round to the nearest step size increment (in this case 45) when used in a slider graphic (such as setting the temperature of a thermostat)
|
||||
* throws a runtime warning if *value* is outside of the min/max range for the Characteristic, where min/max is either the HAP default, or any new min/max range set via a prior call to `setRange()`
|
||||
* note that *value* is **not** restricted to being an increment of the step size; for example it is perfectly valid to call `setVal(43.5)` after calling `setRange(0,100,5)` on a floating-based Characteristic even though 43.5 does does not align with the step size specified. The Home App will properly retain the value as 43.5, though it will round to the nearest step size increment (in this case 45) when used in a slider graphic (such as setting the temperature of a thermostat)
|
||||
* throws a runtime warning if called from within the `update()` routine of a **SpanService** *and* `isUpdated()` is *true* for the Characteristic (i.e. it is being updated at the same time via the Home App), *unless* you are changing the value of a Characteristic in response to a *write-response* request from HomeKit (typically used only for certain TLV-based Characteristics)
|
||||
* note this method can be used to update the value of a Characteristic even if the Characteristic is not permissioned for event notifications (EV), in which case the value stored by HomeSpan will be updated but the Home App will *not* be notified of the change
|
||||
|
||||
|
||||
* `SpanCharacteristic *setRange(min, max, step)`
|
||||
* overrides the default HAP range for a Characteristic with the *min*, *max*, and *step* parameters specified
|
||||
|
|
@ -431,10 +477,10 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
|
|||
* `char *getNewString()`
|
||||
* equivalent to `getNewVal()`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters)
|
||||
|
||||
* `void setString(const char *value)`
|
||||
* `void setString(const char *value [,boolean notify])`
|
||||
* equivalent to `setVal(value)`, but used exclusively for string-characteristics (i.e. a null-terminated array of characters)
|
||||
|
||||
#### The following methods are supported for DATA (i.e. byte-array) Characteristics:
|
||||
#### The following methods are supported for DATA (i.e. byte-array) Characteristics:
|
||||
|
||||
* `size_t getData(uint8_t *data, size_t len)`
|
||||
* similar to `getVal()`, but exclusively used for byte-array Characteristics
|
||||
|
|
@ -442,14 +488,34 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
|
|||
* returns the total number of bytes encoded in the Characteristic
|
||||
* if *len* is less than the total number of bytes encoded, no data is extracted (i.e. *data* is unmodified) and a warning message is thrown indicating that the size of the *data* array is insufficient to extract all the bytes encoded in the Characteristic
|
||||
* setting *data* to NULL returns the total number of bytes encoded without extracting any data. This can be used to help create a *data* array of sufficient size in advance of extracting the data
|
||||
* note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor using the 'i' CLI command, the value of byte-array Characteristics are displayed in their base-64 string format (only the first 32 characters are shown), since base-64 is the representation that is actually transmitted to and from HomeKit
|
||||
* a warning message is thrown if the value stored in the Characteristic is not in base-64 format
|
||||
|
||||
* `size_t getNewData(uint8_t *data, size_t len)`
|
||||
* similar to `getData()`, but fills byte array *data*, of specified size *len*, with bytes based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated
|
||||
|
||||
* `void setData(uint8_t *data, size_t len)`
|
||||
* `void setData(uint8_t *data, size_t len [,boolean notify])`
|
||||
* similar to `setVal()`, but exclusively used for byte-array Characteristics
|
||||
* updates the Characteristic by "filling" it with *len* bytes from bytes array *data*
|
||||
* note: byte-array Characteristics are encoded and transmitted as base-64 strings. HomeSpan automatically peforms all encoding and decoding between this format and the specified byte arrays. But when output to the Serial Monitor, the value of byte-array Characteristics are displayed in their base-64 format (as opposed to being shown as a byte array), since base-64 is the representation that is actually transmitted to and from HomeKit
|
||||
|
||||
#### The following methods are supported for TLV8 (structured-data) Characteristics:
|
||||
|
||||
* `size_t getTLV(TLV8 &tlv)`
|
||||
* similar to `getVal()`, but exclusively used for TLV8 Characteristics
|
||||
* fills TLV8 structure *tlv* with TLV8 records from the current value of the Characteristic
|
||||
* returns the total number of bytes encoded in the Characteristic
|
||||
* if *tlv8* is not empty, TLV8 records from the Characteristic will be appended to any existing records
|
||||
* similar to DATA Characteristics, TLV8 Characteristics are stored and transmitted as base-64 strings
|
||||
* a warning message is thrown if the value stored in the Characteristic is not in base-64 format, or does not appear to contain TLV8 records
|
||||
|
||||
* `size_t getNewTLV(TLV8 &tlv)`
|
||||
* similar to `getTLV()`, but fills TLV8 structure *tlv* with TLV8 records based on the desired **new** value to which a HomeKit Controller has requested the Characteristic be updated
|
||||
|
||||
* `void setTLV(TLV8 &tlv [,boolean notify])`
|
||||
* similar to `setVal()`, but exclusively used for TLV8 Characteristics
|
||||
* updates the Characteristic by packing the TLV8 structure *tlv* into a byte array and then encoding it in base-64 for storage as a string
|
||||
|
||||
* see the [TLV8 Characteristics](TLV8.md) page for complete details on how to read/process/create TLV8 Objects using HomeSpan's TLV8 Library.
|
||||
|
||||
#### The following methods are supported for all Characteristics:
|
||||
|
||||
|
|
@ -485,6 +551,9 @@ This is a **base class** from which all HomeSpan Characteristics are derived, an
|
|||
* returns a pointer to the Characteristic itself so that the method can be chained during instantiation
|
||||
* example: `(new Characteristic::RotationSpeed())->setUnit("percentage");`
|
||||
|
||||
* `uint32_t getIID()`
|
||||
* returns the IID of the Characteristic
|
||||
|
||||
### *SpanButton(int pin, uint16_t longTime, uint16_t singleTime, uint16_t doubleTime, boolean (\*triggerType)(int))*
|
||||
|
||||
Creating an instance of this **class** attaches a pushbutton handler to the ESP32 *pin* specified.
|
||||
|
|
@ -595,8 +664,9 @@ To create more than one user-defined command, simply create multiple instances o
|
|||
### *CUSTOM_CHAR(name,uuid,perms,format,defaultValue,minValue,maxValue,staticRange)*
|
||||
### *CUSTOM_CHAR_STRING(name,uuid,perms,defaultValue)*
|
||||
### *CUSTOM_CHAR_DATA(name,uuid,perms)*
|
||||
### *CUSTOM_CHAR_TLV8(name,uuid,perms)*
|
||||
|
||||
Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...). The second form is used to STRING-based Characteristics. The third form is used for DATA-based (i.e. byte-array) Characteristics. Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for *defaultValue* when applied to a STRING-based Characteristic):
|
||||
Creates a custom Characteristic that can be added to any Service. Custom Characteristics are generally ignored by the Home App but may be used by other third-party applications (such as *Eve for HomeKit*). The first form should be used create numerical Characterstics (e.g., UINT8, BOOL...); the second form is used to STRING-based Characteristics; the third form is used for DATA-based (i.e. byte-array) Characteristics; and the fourth form is used for TLV8-based (i.e. *structured* byte-array) Characteristics Parameters are as follows (note that quotes should NOT be used in any of the macro parameters, except for *defaultValue* when applied to a STRING-based Characteristic):
|
||||
|
||||
* *name* - the name of the custom Characteristic. This will be added to the Characteristic namespace so that it is accessed the same as any HomeSpan Characteristic. Use UTF-8 coded string for non-ASCII characters.
|
||||
* *uuid* - the UUID of the Characteristic as defined by the manufacturer. Must be *exactly* 36 characters in the form XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where *X* represent a valid hexidecimal digit. Leading zeros are required if needed as described more fully in HAP-R2 Section 6.6.1
|
||||
|
|
|
|||
|
|
@ -0,0 +1,313 @@
|
|||
# TLV8 Characteristics
|
||||
|
||||
Most HomeKit Characteristics store a single numerical value or simple string. However, HomeKit supports two additional storage formats - a simple list of bytes (the **DATA** format) and a structured list of tags and values (the **TLV8** format). The DATA format is not used by any Apple-defined Characterstics but it is included in HomeSpan for use when creating Custom Characteristics for non-Apple applications.
|
||||
|
||||
In contrast, the TLV8 format is used extensively by HomeKit during the initial pairing process as well as whenever a new secure (verified) connection is established between HomeKit and a HomeSpan device. There are also a variety of Apple-defined Characteristics that use the TLV8 format to store and transmit multiple sets of values, each represented as byte-arrays of arbitrary length.
|
||||
|
||||
## Overview of TLV8 Format
|
||||
|
||||
The TLV8 format itself is quite simple. A TLV8 object comprises one or more TLV8 *records*, where the first byte in a record represents an identifying TAG (from 0-255), the second byte represents the LENGTH of the value, and the remaining LENGTH-bytes represent the VALUE itself, which is always in the form of a *byte-array* (i.e. an array of 0 or more *uint8_t* elements). Notable points about the TLV8 format are as follows:
|
||||
|
||||
* since the LENGTH is stored only as a single byte, VALUES requiring more than 255 bytes must be represented as sequential TLV8 records *with the same TAG*
|
||||
* it is fine (and in fact common) for a TLV8 object to include multiple records with the same TAG, except that they must be *separated by a record with a different TAG*, otherwise the parser reading the data will concatenate the VALUES from sequential records having the same TAG into a single record (as per above)
|
||||
* records representing a zero-LENGTH VALUE are allowed, and consist of only two bytes: a TAG and a zero (indicating a zero-length VALUE). TAGS with a zero-LENGTH VALUE are often used to separate multiple records having the same TAG
|
||||
* if the VALUE's byte-array is supposed to represent an single, unsigned integer, it should be arranged in little endian format (i.e. least-significant byte first) and padded with trailing zeros as needed to bring the total LENGTH of the VALUE to either 1, 2, 4, or 8 bytes (representing uint8_t, uint16_t, uint32_t, and uint64_t values)
|
||||
* if the VALUE's byte-array is supposed to represent a string, it should not include the terminating null since the LENGTH tells you have many characters are in the string
|
||||
* the bytes that make up a VALUE can themselves represent a separate, complete TLV8 object. There is no limit on the number of "sub-TLVs" that can be recursively nested in a "parent" TLV8 object
|
||||
* a parser reading TLV8 records should silently ignore any TAG it is not expecting. It may be an error to omit a TAG that the parser requires, but it will be not an error to include a TAG it does not recognize
|
||||
* it is **not** possible to unambigously determine whether the VALUE byte-array in a TLV8 record is supposed to represent an unsigned integer, a string, an arbitrary series of bytes, a sub-TLV object, or something else entirely. The only identifying information for any given TLV8 record is its TAG number, which ranges from 0-255. There is no general schema or TLV8 protocol that maps TAG types to VALUE types. Rather, the TAG numbers are arbitrary and users must consult the documentation for each Characteristic to learn what TAG numbers are expected, and what their VALUEs are supposed to represent for that specific Characteristic
|
||||
* since HomeKit data transmissions are often represented in JSON, and JSON is a text-only format, HomeKit requires that TLV8 records are first encoded in base-64 when transmitting JSON to and from Controllers to Accessories.
|
||||
|
||||
Fortunately, HomeSpan includes a dedicated TLV8 library (see below) that automatically takes care of many of the above points, which enables you to read, create, and process TLV8 data without worrying about parsing TLV8 records with more than 255 bytes, converting numerical values to little-endian, or encoding/decoding records into base-64.
|
||||
|
||||
## *TLV8()*
|
||||
|
||||
Creating an instance of HomeSpan's TLV8 **class** using the above constructor builds an empty TLV8 object into which you can add and process TLV8 records. TLV8 objects are instantiated as standard C++ linked-list containers derived from `std::list<tlv8_t>`, where *tlv8_t* is an opaque structure used to store individual TLV8 records.[^opaque]
|
||||
|
||||
Also, as shown below, many of the TLV8 methods utilize linked-list iterators. These are represented by the typedef *TLV8_it*.[^iterators]
|
||||
|
||||
[^opaque]:The *tlv8_t* structure is opaque because in general you will not have to create or interact directly with the structure or its data. Note that in addition to the above TLV8-specific methods, you can use any `std::list` method with a TLV8 object if desired.
|
||||
|
||||
[^iterators]:You do not need expert knowledge of C++ containers and iterators in order to use the TLV8 library, but a basic understanding of containers and iterators will make the library much easier to learn and enjoyable to use.
|
||||
|
||||
The method for adding a generic TLV8 record to a TLV8 object is as follows:
|
||||
|
||||
* `TLV8_it add(uint8_t tag, size_t len, const uint8_t *val)`
|
||||
|
||||
* where *tag* is the TAG identifier for the record to add and *val* is a pointer to a byte-array containing *len* elements
|
||||
* example: `TLV8 myTLV; uint8_t v[]={0x01, 0x05, 0xE3, 0x4C}; tlv.add(1, sizeof(v), v);
|
||||
* setting *val* to NULL reserves *len* bytes of space for the TLV8 record within the TLV8 object, but does not copy any data
|
||||
* this method returns a TLV8 *iterator* to the resulting TLV8 record so you can reference the record at a later time if needed
|
||||
|
||||
In addition to the above generic method suitable for any type of data, the following methods make it easier to add TLV8 records with specific, frequently-used types of data:
|
||||
|
||||
* `TLV8_it add(uint8_t tag, uintXX_t val)`
|
||||
* adds a TLV8 record containing a single, unsigned numeric value, *val* (i.e. uint8_t, uint16_t, uint32_t, or uint64_t)
|
||||
|
||||
* `TLV8_it add(uint8_t tag, const char *val)`
|
||||
* adds a TLV8 record containing all the non-null bytes of a null-terminated character string, *val*
|
||||
|
||||
* `TLV8_it add(uint8_t tag, TLV8 &subTLV)`
|
||||
* adds a TLV8 record containing all the bytes of an entire TLV8 object, *subTLV*
|
||||
|
||||
* `TLV8_it add(uint8_t tag)`
|
||||
* adds a zero-length TLV8 record containing nothing but a TAG identifer
|
||||
|
||||
Note that if you *add* consecutive records with the same TAG identifier, the TLV8 library will concatenate their data and combine into a single record. For example, `myTLV.add(1,13); myTLV.add(1,300)` will be combined to produce a single 3-byte recording containing the data 0x0D2C01, where the first byte represents from the number 13 and the second two bytes represent the number 300. This may have been your desired outcome, but likely not what you wanted to happen.
|
||||
|
||||
Instead, to create two distinct records with the same tag value, simply interpose a zero-length record with a different TAG identifier between the two as a "separator" like this: `myTLV.add(1,13); myTLV.add(255); myTLV.add(1,300);` Here we used a TAG identifer of 255 to represent the separator, but that choice is arbitrary, unless that TAG happens to be used by the Characteristic for something else (TAG identifiers of 0 or 255 are commonly used as separators).
|
||||
|
||||
The method for finding a TLV8 record within a TLV8 object that contains a specific TAG identifer is as follows:
|
||||
|
||||
* `TLV8_it find(uint8_t tag)`
|
||||
|
||||
* where *tag* is the TAG identifier you are seeking
|
||||
* returns a TLV8 iterator to *first* record that matches; returns *end()* if no records match
|
||||
|
||||
To restrict the search range to a limited set of records, add optional starting and ending iterators *it1* and *it2*:
|
||||
|
||||
* `TLV8_it find(uint8_t tag [, TLV8_it it1 [, TLV8_it it2]])`
|
||||
|
||||
* returns a TLV8 iterator to the *first* record within the range of iterators from *it1* to *it2* that matches the specified *tag*
|
||||
* search range is inclusive of *it1* but exclusive of *it2*
|
||||
* returns *it2* if no records match
|
||||
* if *it2* is unspecified, default is *end()*; if *it1* is unspecified, default is *begin()*
|
||||
* note `myTLV.find(tag)` is equivalent to `myTLV.find(tag, myTLV.begin(), myTLV.end())`
|
||||
|
||||
Use of the C++ `auto` keyword is generally the best way to save the TVL8_it iterator that is returned from the `find()` and `add()` methods. For example, `auto myIT = myTLV.find(6)` sets *myIT* to an iterator pointing to the first TLV8 record in *myTLV* that has a TAG identifer of 6.
|
||||
|
||||
The method for finding the LENGTH of the data VALUE stored in a particular TLV8 record is as follows:
|
||||
|
||||
* `int len(TLV8_it it)`
|
||||
* where *it* is an iterator pointing to a specific TLV8 record
|
||||
* returns the length of the data VALUE stored in the associated record, which may be zero for a zero-LENGTH record
|
||||
* returns -1 if *it* points to the *end()* of the TLV8 object
|
||||
|
||||
A typical use of the `len()` method is to simultaneously check whether a TLV8 object contains a particular TAG identifier, and that the LENGTH of the TAG matches an expected value. For example, if a certain Characteristic requires a TLV8 record with a TAG identifer of 6 to contain a 32-byte registration number, you can perform the following check:
|
||||
|
||||
```C++
|
||||
auto myIT = myTLV.find(6);
|
||||
if(myTLV.len(myIT)!=32)
|
||||
Serial.printf("Error: TAG 6 is either missing or of improper length\n");
|
||||
else
|
||||
Serial.printf("TAG 6 containing 32 bytes of data has been found\n");
|
||||
```
|
||||
|
||||
The method for printing all of the records in a TLV8 object to the Serial Monitor is as follows:
|
||||
|
||||
* `void print()`
|
||||
|
||||
* prints all TLV8 records, one per line, to the Serial Monitor
|
||||
* format of the output is: TAG(LENGTH) VALUE [NUMERIC], where
|
||||
* TAG = the TAG identifer (0-255)
|
||||
* LENGTH = length of the VALUE byte-array (may be zero)
|
||||
* VALUE = a sequential list, in hexadecimal, of all the bytes in the VALUE byte-array (only displayed if LENGTH>0)
|
||||
* NUMERIC = an unsigned-integer interpretation of the bytes in VALUE, assuming little-endian ordering
|
||||
* this decimal value is only displayed if LENGTH<=8
|
||||
* if LENGTH=0, the word "null" is displayed instead
|
||||
|
||||
To restrict the the printing range to a limited set of records, add optional starting and ending iterators *it1* and *it2*:
|
||||
|
||||
* `void print(TLV8_it it1 [, TLV8_it it2])`
|
||||
|
||||
* prints all TLV8 records between iterators *it1* and *it2*
|
||||
* print range is inclusive of *it1* but exclusive of *it2*
|
||||
* if *it2* is unspecified, prints only the record pointed to by *it1*
|
||||
* note `myTLV.print()` is equivalent to `myTLV.print(myTLV.begin(), myTLV.end())`
|
||||
|
||||
The output generated by `print()` can contain some very long lines, especially if the VALUE of some of the TLV8 records represents other complete TLV8 objects (known as sub-TLVs or "nested" TLVs). To recursively print all sub-TLV objects, use the following method:
|
||||
|
||||
* `void printAll()`
|
||||
|
||||
* recursively prints all TLV8 records, one per line, to the Serial Monitor
|
||||
* inspects each TLV8 record and tries to parse as if the record represented a sub-TLV object
|
||||
* if parsing is successful, prints the record and then calls `printAll()` on the sub-TLV
|
||||
* if not, prints the record and ends this branch of the recursion
|
||||
* the format of each line is the same as that of `print()` except that TAG displays the full path of all TAGs through the branch
|
||||
* note that the output can be very voluminous if your TLV8 object contains many levels of nested sub-TLVs
|
||||
* warning: some care is required when interpretating the output[^subTLVs]
|
||||
|
||||
[^subTLVs]:The `printAll()` method assumes that any VALUE that is consistent with the format of a sub-TLV must be a sub-TLV, even if its just a simple numeric value. For example, `add(10,65536)` yields a record with a TAG identifer of 10 and a 4-byte VALUE of 0x00000100. The `printAll()` method will display this record along with NUMERIC=65536, but it will also then interpret (and thus display) this VALUE as a sub-TLV containing one zero-length record with TAG identifier=0 and another zero-length record with TAG identifer=1, since the VALUE can be successfully parsed as such.
|
||||
|
||||
TLV8 objects manage all of their internal memory requirements, and free up all resources and memory when they go out of scope or are otherwise deleted. However, if you need to "erase" all the contents of a TLV8 object but stil retain the object so you can re-fill with new data, use the following method:
|
||||
|
||||
* `void wipe()`
|
||||
* erases all TLV8 records and frees all associated memory
|
||||
* leaves an empty TLV8 object ready for re-use
|
||||
|
||||
## *TLV8_it()*
|
||||
|
||||
Objects of type *TLV8_it* are iterators that point to specific records in a TLV8 object (or to *end()*). TLV8 iterators are used to access, read from, and write to, the data elements in any given TLV8 record, and are thus a critical part of the TLV8 library. However, if you are using the TLV8 library correctly you should rarely, if ever, need to directly instantiate a *TLV8_it* using its constructor. Instead, simply use the C++ `auto` keyword as noted above.
|
||||
|
||||
TLV8_it supports the following methods:
|
||||
|
||||
* `uint8_t getTag()`
|
||||
|
||||
* returns the TAG identifier (0-255) of the TLV8 record
|
||||
* example: `uint8_t tag = myIT->getTag()` or, equivalently, `uint8_t tag = (*myIT).getTag()`
|
||||
|
||||
* `size_t getLen()`
|
||||
|
||||
* returns the LENGTH of the VALUE byte-array of the TLV8 record
|
||||
* example: `size_t len = myIT->getLen()` or, equivalently, `size_t len = (*myIT).getLen()`
|
||||
|
||||
* `uint8_t *get()`
|
||||
|
||||
* returns `uint8_t *` pointing to the first element of the VALUE byte-array of the TLV8 record
|
||||
* for zero-LENGTH TLV8 records, the return value is NULL
|
||||
* example: `uint8_t *v = myIT->get();` or, equivalently, `uint8_t *v = (*myIT).get();`
|
||||
* the `(uint8_t *)` casting operator has been overloaded so you can also obtain this same `uint8_t *` pointer by simply dereferencing the iterator
|
||||
* example: `auto myIT = myTLV.find(6); uint8_t *v = *myIT;`
|
||||
* note this only works if the compiler can determine the need to auto-cast into a `uint8_t *` pointer based on the context of the code
|
||||
|
||||
* `uint8_t get()[i]`
|
||||
* returns the *i<sup>th</sup>* element of the VALUE byte-array
|
||||
* example: `uint8_t n = myIT->get()[i]` or, equivalently, `uint8_t n = (*myIT).get()[i]`
|
||||
* the subscript operator has also been overloaded so you can obtain the *i<sup>th</sup>* element by simply dereferencing the iterator
|
||||
* example: `uint8_t n = (*myIT)[i]`
|
||||
* note there is no range-checking so make sure *i* does not try to reference an element beyond the end of the VALUE byte-array
|
||||
|
||||
* `T getVal<class T>()`
|
||||
* this template function returns a single numeric value of type *T* on the assumption that the VALUE byte-array is storing an unsigned integer in little endian format
|
||||
* *T* can be *uint8_t*, *uint16_t*, *uint32_t*, or *uint64_t* (if unspecified *T* defaults to *uint32_t*)
|
||||
* example: `auto myIT = myTLV.add(50,60000); uint16_t n = myIT->getVal<uint16_t>();`
|
||||
* this method returns the correct numeric value as long as sizeof(*T*) >= LENGTH of the byte-array. For example:
|
||||
* setting *T=uint64_t* with a VALUE byte-array containing 2 bytes returns the *correct* numeric value
|
||||
* setting *T=uint16_t* with a VALUE byte-array containing 4 bytes return an *incorrect* numeric value
|
||||
* this function returns zero for all zero-LENGTH TLV8 records
|
||||
|
||||
### A detailed example using the above methods
|
||||
|
||||
The following code:
|
||||
|
||||
```C++
|
||||
TLV8 myTLV; // instantiates an empty TLV8 object
|
||||
|
||||
myTLV.add(1,8700); // add a TLV8 record with TAG=1 and VALUE=8700
|
||||
auto it_A = myTLV.add(2,180); // add a TLV8 record with TAG=2 and VALUE=180, and save the iterator that is returned
|
||||
|
||||
uint8_t v[32]; // create a 32-byte array, v, and fill it with some data
|
||||
for(int i=0;i<32;i++)
|
||||
v[i]=i;
|
||||
|
||||
myTLV.add(200,32,v); // add a TLV8 record with TAG=200 and copy all 32 bytes of array v into its VALUE
|
||||
|
||||
myTLV.add(50,60000); // add a TLV8 record with TAG=50 and VALUE=60000
|
||||
myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator
|
||||
myTLV.add(50,120000); // add a TLV8 record with TAG=50 and VALUE=120000
|
||||
myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator
|
||||
myTLV.add(50,180000); // add a TLV8 record with TAG=50 and VALUE=180000
|
||||
myTLV.add(255); // add a zero-length TLV8 record with TAG=255 to act as separator
|
||||
auto it_B = myTLV.add(50,240000); // add a TLV8 record with TAG=50 and VALUE=240000, and save the iterator that is returned
|
||||
|
||||
auto it_C = myTLV.find(50); // find an iterator to the first TLV8 record with TAG=50;
|
||||
auto it_D = myTLV.find(50,std::next(it_C)); // find an iterator to the first TLV8 record with TAG=50 that occurs AFTER it_C;
|
||||
|
||||
auto it_E = myTLV.find(200); // find an iterator to first TLV8 record with TAG=200;
|
||||
|
||||
Serial.printf("results of myTLV.print():\n\n");
|
||||
|
||||
myTLV.print(); // print the contents of myTLV to the Serial Monitor
|
||||
|
||||
Serial.printf("\n");
|
||||
|
||||
// print content of it_A:
|
||||
|
||||
Serial.printf("it_A: TAG=%d, LENGTH=%d, Value=%d\n", it_A->getTag(), it_A->getLen(), it_A->getVal());
|
||||
|
||||
// print content of it_B using alternative syntax:
|
||||
|
||||
Serial.printf("it_B: TAG=%d, LENGTH=%d, Value=%d\n", (*it_B).getTag(), (*it_B).getLen(), (*it_B).getVal());
|
||||
|
||||
// print contents of it_C and it_D, based on previous find() above:
|
||||
|
||||
Serial.printf("it_C TAG=%d, LENGTH=%d, Value=%d\n", (*it_C).getTag(), (*it_C).getLen(), (*it_C).getVal());
|
||||
Serial.printf("it_D TAG=%d, LENGTH=%d, Value=%d\n", (*it_D).getTag(), (*it_D).getLen(), (*it_D).getVal());
|
||||
|
||||
// you can also use the results of find() directly without saving as a separate iterator, though this is computationally inefficient:
|
||||
|
||||
if(myTLV.find(1)!=myTLV.end()) // check for match
|
||||
Serial.printf("Found: TAG=%d, LENGTH=%d, Value=%d\n", myTLV.find(1)->getTag(), myTLV.find(1)->getLen(), myTLV.find(1)->getVal());
|
||||
|
||||
// sum up all the bytes in it_E:
|
||||
|
||||
int sum=0;
|
||||
for(int i=0; i < it_E->getLen(); i++)
|
||||
sum+= (*it_E)[i];
|
||||
|
||||
Serial.printf("it_E TAG=%d, LENGTH=%d, Sum of all bytes = %d\n", (*it_E).getTag(), (*it_E).getLen(), sum);
|
||||
|
||||
// create a "blank" TLV8 record with TAG=90 and space for 16 bytes:
|
||||
|
||||
auto it_F = myTLV.add(90,16,NULL);
|
||||
|
||||
// copy the first 16 bytes of it_E into it_F and print the record:
|
||||
|
||||
memcpy(*it_F,*it_E,16);
|
||||
myTLV.print(it_F);
|
||||
```
|
||||
|
||||
produces the following output:
|
||||
|
||||
```C++
|
||||
results of myTLV.print():
|
||||
|
||||
1(2) FC21 [8700]
|
||||
2(1) B4 [180]
|
||||
200(32) 000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F
|
||||
50(2) 60EA [60000]
|
||||
255(0) [null]
|
||||
50(4) C0D40100 [120000]
|
||||
255(0) [null]
|
||||
50(4) 20BF0200 [180000]
|
||||
255(0) [null]
|
||||
50(4) 80A90300 [240000]
|
||||
|
||||
it_A: TAG=2, LENGTH=1, Value=180
|
||||
it_B: TAG=50, LENGTH=4, Value=240000
|
||||
it_C TAG=50, LENGTH=2, Value=60000
|
||||
it_D TAG=50, LENGTH=4, Value=120000
|
||||
Found: TAG=1, LENGTH=2, Value=8700
|
||||
it_E TAG=200, LENGTH=32, Sum of all bytes = 496
|
||||
90(16) 000102030405060708090A0B0C0D0E0F
|
||||
```
|
||||
|
||||
## Reading and Writing TLV8 Characteristics
|
||||
|
||||
As fully documented in the [API Reference](Reference.md), the following *SpanCharacteristic* methods are used to read and write TLV8 objects to TLV8 Characteristics:
|
||||
|
||||
* `getTLV(TLV8 &tlv)`
|
||||
* `getNewTLV(TLV8 &tlv)`
|
||||
* `setTLV(TLV8 &tlv)`
|
||||
|
||||
These are analagous to the `getVal()`, `getNewVal()` and `setVal()` methods used for numerical-based Characteristics.
|
||||
|
||||
Note that using the above methods *do not* require you to create a separate byte-array that splits records into chunks of 255 bytes, nor does it require you to encode or decode anything into base-64. Rather, you directly read and write to and from the Characteristic into a TLV8 object.[^getString]
|
||||
|
||||
For a detailed example of how TLV8 Characteristics are used in practice, see [Tutorial Example 22 - TLV8_Characteristics](../examples/22-TLV8_Characteristics) demonstrating how the **DisplayOrder** TLV8 Charactersitic can be used to set the order in which Input Sources for a TV Service are displayed in the Home App.
|
||||
|
||||
[^getString]:Since TLV8 Characteristics are stored as base-64 encoded strings, you can always use `getString()` to read the base-64 text, or `getData()` to decode the string into the full byte-array that represents the entire TLV8 object, if you desire. Also, if you really don't want to use HomeSpan's TLV8 library to produce TLV8 objects, but instead prefer to use your own methods to create a TLV8-compliant byte-array, you can do so and then use `setData()` to save the byte-array you produced to the TLV8 Characteristic, which will perform the base-64 encoding for you. Or, if you want to additionally perform your own base-64 encoding (why?), you can do so and then simply use `setString()` to save the resulting encoded text to the TLV8 Characteristic.
|
||||
|
||||
### Write-Response Requests
|
||||
|
||||
For most Characteristics, when the Home App sends HomeSpan a request to update a value, it is instructing HomeSpan to perform some sort of action, such as "change the brightness of a lightbulb to 30%" or "change the target state of the door to open." The only feedback the Home App expects to receive in response to such requests is basically an "OK" or "NOT OKAY" message, which is the purpose of the boolean return value in the `update()` method for every Service.
|
||||
|
||||
However, sometimes the Home App sends HomeSpan a request for information, rather than a direct instruction to perform a task. In such instances, rather than sending back just an OK/NOT-OKAY message, the Home App expects the Accessory device to update the value of the Characteristic *not* with the new value that the Home App sent, but rather with the information it requested. It then expects this information to be transmitted back to the Home App at the conclusion of the update.
|
||||
|
||||
This procedure is known as a "Write-Response Request", and it is the primary purpose for having TLV8 Characteristics, since TLV8 objects are ideal for storing structured information.
|
||||
|
||||
Though the procedure is complex, HomeSpan handles all of the protocol details. You only need to focus on reading the TLV8 Characteristic and updating it with the required TLV8 response as follows:
|
||||
|
||||
* first, from within the `update()` loop of the applicable Service, check to see if the Home App has requested an update to the TLV8 Characteristic;
|
||||
* if so, create a new TLV8 object and use `getNewTLV()` to load the contents of the updated Characteristic into that TLV8 object;
|
||||
* then, use the TLV8 library methods described above to read through the TAGS and VALUES in the TLV8 object to determine what data the Home App is conveying and what information it wants returned (based on the specs for the Characteristic);
|
||||
* next, create a *second* TLV8 object and use the TLV8 library methods above to create the appropriate TAGS and VALUES needed to respond to the information request (again, based on the on the specs for the Characteristic);
|
||||
* finally, use `setVal()` to update the TLV8 Characteristic with the second TLV8 object
|
||||
|
||||
HomeSpan will automatically send the new TLV8 data you placed in the TLV8 Characterstic back to the Home App in its response at the conclusion of the `update()` loop.
|
||||
|
||||
---
|
||||
|
||||
[↩️](../README.md) Back to the Welcome page
|
||||
|
||||
|
||||
|
|
@ -73,7 +73,6 @@ Example 13 demonstrates the simultaneous use of both the `update()` and `loop()`
|
|||
|
||||
* using Enumerated Constants to set the values of Characteristics that represent discrete states (e.g. "raising", "closing")
|
||||
|
||||
|
||||
### [Example 14 - EmulatedPushButtons](../examples/14-EmulatedPushButtons)
|
||||
Example 14 demonstrates how you can use the `setVal()` and `timeVal()` methods inside a Service's `loop()` method to create a tile in the Home App that emulates a pushbutton switch. In this example pressing the tile in the Home App will cause it to turn on, blink an LED 3 times, and then turn off (just like a real pushbutton might do).
|
||||
|
||||
|
|
@ -113,6 +112,13 @@ Example 20 illustrates a number of advanced techniques through the implementatio
|
|||
|
||||
### [Example 21 - AccessoryIdentifier](../examples/21-AccessoryIdentifier)
|
||||
Example 21 shows how the Identifier Characteristic that is always present in each Accessory's required AccessoryInformation Service can be used to create a custom "identification routine" that can be triggered from within the Home App when pairing a device. This example does not use any new HomeSpan methods.
|
||||
|
||||
### [Example 22 - TLV8 Characteristics](../examples/22-TLV8_Characteristics)
|
||||
Example 22 demonstrates how to create and utilize TLV8-based Characteristics through the implementation of the DisplayOrder Characteristic used to set the order in which input sources for a Television Service are presented in the Home App. New HomeSpan API topics covered in this example include:
|
||||
|
||||
* creating TLV8 objects using HomeSpan's TLV8 class
|
||||
* updating TLV8 Characteristics using `setTLV()`
|
||||
|
||||
|
||||
## Other Examples
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,162 @@
|
|||
/*********************************************************************************
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-2024 Gregg E. Berman
|
||||
*
|
||||
* https://github.com/HomeSpan/HomeSpan
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*
|
||||
********************************************************************************/
|
||||
|
||||
////////////////////////////////////////////////////////////////
|
||||
// //
|
||||
// HomeSpan: A HomeKit implementation for the ESP32 //
|
||||
// ------------------------------------------------ //
|
||||
// //
|
||||
// Example 24: Demonstrates the use of the TLV8 Library //
|
||||
// by implementing DisplayOrder, an optional //
|
||||
// TLV8 Characteristic used with the TV Service //
|
||||
// to set the order in which TV Inputs are //
|
||||
// displayed for selection in the Home App //
|
||||
// //
|
||||
////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "HomeSpan.h"
|
||||
|
||||
// NOTE: Please see the "Other Examples -> Television" sketch for complete details on how to implement a Television Service. The focus
|
||||
// of this sketch is solely to demonstrate how to use the TLV8 Library to create TLV8 data for use with the DisplayOrder Characteristic.
|
||||
|
||||
// First we define a simple Television Input Source Service
|
||||
|
||||
struct TVInput : Service::InputSource {
|
||||
|
||||
SpanCharacteristic *inputID;
|
||||
SpanCharacteristic *inputName;
|
||||
|
||||
TVInput(uint32_t id, const char *name) : Service::InputSource() {
|
||||
|
||||
inputID = new Characteristic::Identifier(id);
|
||||
inputName = new Characteristic::ConfiguredName(name);
|
||||
new Characteristic::IsConfigured(Characteristic::IsConfigured::CONFIGURED);
|
||||
new Characteristic::CurrentVisibilityState(Characteristic::CurrentVisibilityState::VISIBLE);
|
||||
}
|
||||
};
|
||||
|
||||
// Next we define a simple Television Service
|
||||
|
||||
struct HomeSpanTV : Service::Television {
|
||||
|
||||
SpanCharacteristic *active = new Characteristic::Active(0);
|
||||
SpanCharacteristic *activeID = new Characteristic::ActiveIdentifier(10);
|
||||
|
||||
SpanCharacteristic *displayOrder = new Characteristic::DisplayOrder(); // <-- This is the new TLV8 Characteristic. Note the constructor has no argument
|
||||
|
||||
HomeSpanTV() : Service::Television() {
|
||||
|
||||
// Unlike the constructors for numerical and string-based Characteristics (such as ActiveIdentifier and ConfiguredName),
|
||||
// we cannot set the initial value of TLV8 Characteristics during construction, but must instead first instantiate the
|
||||
// Characteristic (as we did above), then build a TLV8 object with the information required by the TLV8 Characteristic, and
|
||||
// then use setTLV() to load the completed TLV8 object into the Characteristic's value.
|
||||
|
||||
// The (undocumented by Apple!) TLV8 specifications for the DisplayOrder Characteristic are as follows:
|
||||
|
||||
// TAG NAME FORMAT DESCRIPTION
|
||||
// ---- ------------- ------ --------------------------------------------
|
||||
// 0x01 inputSourceID uint32 ID of the Input Source to be displayed first
|
||||
// 0x00 separator none Empty element to separate the inputSourceIDs
|
||||
// 0x01 inputSourceID uint32 ID of the Input Source to be displayed second
|
||||
// 0x00 separator none Empty element to separate the inputSourceIDs
|
||||
// 0x01 inputSourceID uint32 ID of the Input Source to be displayed third
|
||||
// 0x00 separator none Empty element to separate the inputSourceIDs
|
||||
// etc...
|
||||
|
||||
// To start, instantiate a new TLV8 object
|
||||
|
||||
TLV8 orderTLV; // creates an empty TLV8 object
|
||||
|
||||
// Next, fill it with TAGS and VALUES based on the above specification. The easiest, though
|
||||
// not necessarily most elegant, way to do this is by simply adding each TAG/VALUE as follows:
|
||||
|
||||
orderTLV.add(1,10); // TAG=1, VALUE=ID of first Input Source to be displayed
|
||||
orderTLV.add(0); // TAG=0 (no value)
|
||||
orderTLV.add(1,20); // TAG=1, VALUE=ID of the second Input Source to be displayed
|
||||
orderTLV.add(0); // TAG=0 (no value)
|
||||
orderTLV.add(1,50); // TAG=1, VALUE=ID of the third Input Source to be displayed
|
||||
orderTLV.add(0); // TAG=0 (no value)
|
||||
orderTLV.add(1,30); // TAG=1, VALUE=ID of the fourth Input Source to be displayed
|
||||
orderTLV.add(0); // TAG=0 (no value)
|
||||
orderTLV.add(1,40); // TAG=1, VALUE=ID of the fifth Input Source to be displayed
|
||||
|
||||
// Based on the above structure, we expect the Home App to display our input sources based on their IDs
|
||||
// in the following order: 10, 20, 50, 30, 40. These IDs must of course match the IDs you choose
|
||||
// for your input sources when you create them at the end of this sketch in setup()
|
||||
|
||||
// The final step is to load this TLV8 object into the DisplayOrder Characteristic
|
||||
|
||||
displayOrder->setTLV(orderTLV); // set the "value" of DisplayOrder to be the orderTLV object we just created
|
||||
|
||||
// That's it - you've created your first TLV8 Characteristic!
|
||||
}
|
||||
|
||||
// Below we define the usual update() loop. There is nothing "TLV-specific" about this part of the code
|
||||
|
||||
boolean update() override {
|
||||
|
||||
if(active->updated()){
|
||||
LOG0("Set TV Power to: %s\n",active->getNewVal()?"ON":"OFF");
|
||||
}
|
||||
|
||||
if(activeID->updated()){
|
||||
LOG0("Set Input Source to ID=%d\n",activeID->getNewVal());
|
||||
}
|
||||
|
||||
return(true);
|
||||
}
|
||||
};
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
void setup() {
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
homeSpan.setLogLevel(2);
|
||||
|
||||
homeSpan.begin(Category::Television,"HomeSpan Television");
|
||||
|
||||
SPAN_ACCESSORY();
|
||||
|
||||
(new HomeSpanTV()) // Define a Television Service and link in the InputSources!
|
||||
->addLink(new TVInput(10,"Xfinity"))
|
||||
->addLink(new TVInput(20,"BlueRay Disc"))
|
||||
->addLink(new TVInput(30,"Amazon Prime"))
|
||||
->addLink(new TVInput(40,"Netflix"))
|
||||
->addLink(new TVInput(50,"Hulu"))
|
||||
;
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void loop(){
|
||||
homeSpan.poll();
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
|
@ -114,6 +114,7 @@ struct HapCharacteristics {
|
|||
HAPCHAR( CurrentTemperature, 11, PR+EV, FLOAT, false );
|
||||
HAPCHAR( CurrentTiltAngle, C1, PR+EV, INT, false );
|
||||
HAPCHAR( CurrentVisibilityState, 135, PR+EV, UINT8, true );
|
||||
HAPCHAR( DisplayOrder, 136, PR+EV, TLV_ENC, true );
|
||||
HAPCHAR( FilterLifeLevel, AB, PR+EV, FLOAT, false );
|
||||
HAPCHAR( FilterChangeIndication, AC, PR+EV, UINT8, true );
|
||||
HAPCHAR( FirmwareRevision, 52, PR+EV, STRING, true );
|
||||
|
|
|
|||
33
src/HAP.cpp
33
src/HAP.cpp
|
|
@ -334,7 +334,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
|||
return(0);
|
||||
}
|
||||
|
||||
int tlvState=(*itState)[0];
|
||||
int tlvState=itState->getVal();
|
||||
|
||||
if(nAdminControllers()){ // error: Device already paired (i.e. there is at least one admin Controller). We should not be receiving any requests for Pair-Setup!
|
||||
LOG0("\n*** ERROR: Device already paired!\n\n");
|
||||
|
|
@ -363,7 +363,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
|||
|
||||
auto itMethod=iosTLV.find(kTLVType_Method);
|
||||
|
||||
if(iosTLV.len(itMethod)!=1 || (*itMethod)[0]!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3)
|
||||
if(iosTLV.len(itMethod)!=1 || itMethod->getVal()!=0){ // error: "Pair Setup" method must always be 0 to indicate setup without MiFi Authentification (HAP Table 5-3)
|
||||
LOG0("\n*** ERROR: Pair 'Method' missing or not set to 0\n\n");
|
||||
responseTLV.add(kTLVType_Error,tagError_Unavailable); // set Error=Unavailable
|
||||
tlvRespond(responseTLV); // send response to client
|
||||
|
|
@ -404,7 +404,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
|||
return(0);
|
||||
};
|
||||
|
||||
srp->createSessionKey(*itPublicKey,(*itPublicKey).len); // create session key, K, from client Public Key, A
|
||||
srp->createSessionKey(*itPublicKey,itPublicKey->getLen()); // create session key, K, from client Public Key, A
|
||||
|
||||
if(!srp->verifyClientProof(*itClientProof)){ // verify client Proof, M1
|
||||
LOG0("\n*** ERROR: SRP Proof Verification Failed\n\n");
|
||||
|
|
@ -454,9 +454,9 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
|||
|
||||
// use SessionKey to decrypt encryptedData TLV with padded nonce="PS-Msg05"
|
||||
|
||||
TempBuffer<uint8_t> decrypted((*itEncryptedData).len-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).len, NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){
|
||||
if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PS-Msg05", sessionKey)==-1){
|
||||
LOG0("\n*** ERROR: Exchange-Request Authentication Failed\n\n");
|
||||
responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||
tlvRespond(responseTLV); // send response to client
|
||||
|
|
@ -492,7 +492,7 @@ int HAPClient::postPairSetupURL(uint8_t *content, size_t len){
|
|||
|
||||
// Concatenate iosDeviceX, IOS ID, and IOS PublicKey into iosDeviceInfo
|
||||
|
||||
TempBuffer<uint8_t> iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(*itIdentifier).val.get(),(*itIdentifier).len,(*itPublicKey).val.get(),(*itPublicKey).len,NULL);
|
||||
TempBuffer<uint8_t> iosDeviceInfo(iosDeviceX,iosDeviceX.len(),(uint8_t *)(*itIdentifier),itIdentifier->getLen(),(uint8_t *)(*itPublicKey),itPublicKey->getLen(),NULL);
|
||||
|
||||
if(crypto_sign_verify_detached(*itSignature, iosDeviceInfo, iosDeviceInfo.len(), *itPublicKey) != 0){ // verify signature of iosDeviceInfo using iosDeviceLTPK
|
||||
LOG0("\n*** ERROR: LPTK Signature Verification Failed\n\n");
|
||||
|
|
@ -585,7 +585,7 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){
|
|||
return(0);
|
||||
}
|
||||
|
||||
int tlvState=(*itState)[0];
|
||||
int tlvState=itState->getVal();
|
||||
|
||||
if(!nAdminControllers()){ // error: Device not yet paired - we should not be receiving any requests for Pair-Verify!
|
||||
LOG0("\n*** ERROR: Device not yet paired!\n\n");
|
||||
|
|
@ -668,9 +668,9 @@ int HAPClient::postPairVerifyURL(uint8_t *content, size_t len){
|
|||
|
||||
// use Session Curve25519 Key (from previous step) to decrypt encrypytedData TLV with padded nonce="PV-Msg03"
|
||||
|
||||
TempBuffer<uint8_t> decrypted((*itEncryptedData).len-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).len, NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){
|
||||
if(crypto_aead_chacha20poly1305_ietf_decrypt(decrypted, NULL, NULL, *itEncryptedData, itEncryptedData->getLen(), NULL, 0, (unsigned char *)"\x00\x00\x00\x00PV-Msg03", sessionKey)==-1){
|
||||
LOG0("\n*** ERROR: Verify Authentication Failed\n\n");
|
||||
responseTLV.add(kTLVType_State,pairState_M4); // set State=<M4>
|
||||
responseTLV.add(kTLVType_Error,tagError_Authentication); // set Error=Authentication
|
||||
|
|
@ -771,7 +771,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){
|
|||
auto itState=iosTLV.find(kTLVType_State);
|
||||
auto itMethod=iosTLV.find(kTLVType_Method);
|
||||
|
||||
if(iosTLV.len(itState)!=1 || (*itState)[0]!=1){ // missing STATE TLV
|
||||
if(iosTLV.len(itState)!=1 || itState->getVal()!=1){ // missing STATE TLV
|
||||
LOG0("\n*** ERROR: Parirings 'State' is either missing or not set to <M1>\n\n");
|
||||
badRequestError(); // return with 400 error, which closes connection
|
||||
return(0);
|
||||
|
|
@ -783,7 +783,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){
|
|||
return(0);
|
||||
}
|
||||
|
||||
int tlvMethod=(*itMethod)[0];
|
||||
int tlvMethod=itMethod->getVal();
|
||||
|
||||
responseTLV.add(kTLVType_State,pairState_M2); // all responses include State=M2
|
||||
|
||||
|
|
@ -810,7 +810,7 @@ int HAPClient::postPairingsURL(uint8_t *content, size_t len){
|
|||
return(0);
|
||||
}
|
||||
|
||||
tagError err=addController(*itIdentifier,*itPublicKey,(*itPermissions)[0]);
|
||||
tagError err=addController(*itIdentifier,*itPublicKey,itPermissions->getVal());
|
||||
if(err!=tagError_None)
|
||||
responseTLV.add(kTLVType_Error,err);
|
||||
|
||||
|
|
@ -1345,7 +1345,7 @@ int HAPClient::receiveEncrypted(uint8_t *httpBuf, int messageSize){
|
|||
/////////////////////////////////////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void HAPClient::hexPrintColumn(uint8_t *buf, int n, int minLogLevel){
|
||||
void HAPClient::hexPrintColumn(const uint8_t *buf, int n, int minLogLevel){
|
||||
|
||||
if(homeSpan.logLevel<minLogLevel)
|
||||
return;
|
||||
|
|
@ -1356,7 +1356,7 @@ void HAPClient::hexPrintColumn(uint8_t *buf, int n, int minLogLevel){
|
|||
|
||||
//////////////////////////////////////
|
||||
|
||||
void HAPClient::hexPrintRow(uint8_t *buf, int n, int minLogLevel){
|
||||
void HAPClient::hexPrintRow(const uint8_t *buf, int n, int minLogLevel){
|
||||
|
||||
if(homeSpan.logLevel<minLogLevel)
|
||||
return;
|
||||
|
|
@ -1367,7 +1367,7 @@ void HAPClient::hexPrintRow(uint8_t *buf, int n, int minLogLevel){
|
|||
|
||||
//////////////////////////////////////
|
||||
|
||||
void HAPClient::charPrintRow(uint8_t *buf, int n, int minLogLevel){
|
||||
void HAPClient::charPrintRow(const uint8_t *buf, int n, int minLogLevel){
|
||||
|
||||
if(homeSpan.logLevel<minLogLevel)
|
||||
return;
|
||||
|
|
@ -1503,6 +1503,9 @@ void HAPClient::printControllers(int minLogLevel){
|
|||
|
||||
void HAPClient::saveControllers(){
|
||||
|
||||
if(homeSpan.controllerCallback)
|
||||
homeSpan.controllerCallback();
|
||||
|
||||
if(controllerList.empty()){
|
||||
nvs_erase_key(homeSpan.hapNVS,"CONTROLLERS");
|
||||
return;
|
||||
|
|
|
|||
27
src/HAP.h
27
src/HAP.h
|
|
@ -34,7 +34,6 @@
|
|||
#include "HAPConstants.h"
|
||||
#include "HKDF.h"
|
||||
#include "SRP.h"
|
||||
#include "TLV8.h"
|
||||
|
||||
const TLV8_names HAP_Names[] = {
|
||||
{kTLVType_Separator,"SEPARATOR"},
|
||||
|
|
@ -64,26 +63,6 @@ struct Nonce {
|
|||
void inc();
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Paired Controller Structure for Permanently-Stored Data
|
||||
|
||||
struct Controller {
|
||||
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[hap_controller_IDBYTES]; // Pairing ID
|
||||
uint8_t LTPK[crypto_sign_PUBLICKEYBYTES]; // Long Term Ed2519 Public Key
|
||||
|
||||
Controller(){}
|
||||
|
||||
Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
|
||||
allocated=true;
|
||||
admin=ad;
|
||||
memcpy(ID,id,hap_controller_IDBYTES);
|
||||
memcpy(LTPK,ltpk,crypto_sign_PUBLICKEYBYTES);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// Accessory Structure for Permanently-Stored Data
|
||||
|
||||
|
|
@ -151,9 +130,9 @@ struct HAPClient {
|
|||
|
||||
static void init(); // initialize HAP after start-up
|
||||
|
||||
static void hexPrintColumn(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, one byte per row, subject to specified minimum log level
|
||||
static void hexPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, all on one row, subject to specified minimum log level
|
||||
static void charPrintRow(uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level
|
||||
static void hexPrintColumn(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, one byte per row, subject to specified minimum log level
|
||||
static void hexPrintRow(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as HEX, all on one row, subject to specified minimum log level
|
||||
static void charPrintRow(const uint8_t *buf, int n, int minLogLevel=0); // prints 'n' bytes of *buf as CHAR, all on one row, subject to specified minimum log level
|
||||
|
||||
static Controller *findController(uint8_t *id); // returns pointer to controller with matching ID (or NULL if no match)
|
||||
static tagError addController(uint8_t *id, uint8_t *ltpk, boolean admin); // stores data for new Controller with specified data. Returns tagError (if any)
|
||||
|
|
|
|||
135
src/HomeSpan.cpp
135
src/HomeSpan.cpp
|
|
@ -611,8 +611,8 @@ void Span::processSerialCommand(const char *c){
|
|||
|
||||
if(hap[i]->cPair){
|
||||
LOG0(" ID=");
|
||||
HAPClient::charPrintRow(hap[i]->cPair->ID,36);
|
||||
LOG0(hap[i]->cPair->admin?" (admin)":" (regular)");
|
||||
HAPClient::charPrintRow(hap[i]->cPair->getID(),36);
|
||||
LOG0(hap[i]->cPair->isAdmin()?" (admin)":" (regular)");
|
||||
} else {
|
||||
LOG0(" (unverified)");
|
||||
}
|
||||
|
|
@ -864,7 +864,7 @@ void Span::processSerialCommand(const char *c){
|
|||
char pNames[][7]={"PR","PW","EV","AA","TW","HD","WR"};
|
||||
|
||||
for(auto acc=Accessories.begin(); acc!=Accessories.end(); acc++){
|
||||
LOG0("\u27a4 Accessory: AID=%d\n",(*acc)->aid);
|
||||
LOG0("\u27a4 Accessory: AID=%u\n",(*acc)->aid);
|
||||
boolean foundInfo=false;
|
||||
|
||||
if(acc==Accessories.begin() && (*acc)->aid!=1)
|
||||
|
|
@ -874,21 +874,27 @@ void Span::processSerialCommand(const char *c){
|
|||
LOG0(" *** ERROR #%d! AID already in use for another Accessory ***\n",++nErrors);
|
||||
|
||||
aidValues.push_back((*acc)->aid);
|
||||
vector<uint32_t, Mallocator<uint32_t>> iidValues;
|
||||
|
||||
for(auto svc=(*acc)->Services.begin(); svc!=(*acc)->Services.end(); svc++){
|
||||
LOG0(" \u279f Service %s: IID=%d, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type);
|
||||
LOG0(" \u279f Service %s: IID=%u, %sUUID=\"%s\"\n",(*svc)->hapName,(*svc)->iid,(*svc)->isCustom?"Custom-":"",(*svc)->type);
|
||||
|
||||
if(!strcmp((*svc)->type,"3E")){
|
||||
foundInfo=true;
|
||||
if((*svc)->iid!=1)
|
||||
LOG0(" *** ERROR #%d! The Accessory Information Service must be defined before any other Services in an Accessory ***\n",++nErrors);
|
||||
LOG0(" *** ERROR #%d! The Accessory Information Service must be defined with IID=1 (i.e. before any other Services in an Accessory) ***\n",++nErrors);
|
||||
}
|
||||
else if((*acc)->aid==1) // this is an Accessory with aid=1, but it has more than just AccessoryInfo. So...
|
||||
isBridge=false; // ...this is not a bridge device
|
||||
|
||||
isBridge=false; // ...this is not a bridge device
|
||||
|
||||
if(std::find(iidValues.begin(),iidValues.end(),(*svc)->iid)!=iidValues.end())
|
||||
LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors);
|
||||
|
||||
iidValues.push_back((*svc)->iid);
|
||||
|
||||
for(auto chr=(*svc)->Characteristics.begin(); chr!=(*svc)->Characteristics.end(); chr++){
|
||||
LOG0(" \u21e8 Characteristic %s(%s): IID=%d, %sUUID=\"%s\", %sPerms=",
|
||||
(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":"");
|
||||
LOG0(" \u21e8 Characteristic %s(%.33s%s): IID=%u, %sUUID=\"%s\", %sPerms=",
|
||||
(*chr)->hapName,(*chr)->uvPrint((*chr)->value).c_str(),strlen((*chr)->uvPrint((*chr)->value).c_str())>33?"...\"":"",(*chr)->iid,(*chr)->isCustom?"Custom-":"",(*chr)->type,(*chr)->perms!=(*chr)->hapChar->perms?"Custom-":"");
|
||||
|
||||
int foundPerms=0;
|
||||
for(uint8_t i=0;i<7;i++){
|
||||
|
|
@ -896,7 +902,7 @@ void Span::processSerialCommand(const char *c){
|
|||
LOG0("%s%s",(foundPerms++)?"+":"",pNames[i]);
|
||||
}
|
||||
|
||||
if((*chr)->format!=FORMAT::STRING && (*chr)->format!=FORMAT::BOOL && (*chr)->format!=FORMAT::DATA){
|
||||
if((*chr)->format<FORMAT::STRING && (*chr)->format!=FORMAT::BOOL){
|
||||
if((*chr)->validValues)
|
||||
LOG0(", Valid Values=%s",(*chr)->validValues);
|
||||
else if((*chr)->uvGet<double>((*chr)->stepValue)>0)
|
||||
|
|
@ -925,7 +931,7 @@ void Span::processSerialCommand(const char *c){
|
|||
if(!(*chr)->isCustom && !(*svc)->isCustom && std::find((*svc)->req.begin(),(*svc)->req.end(),(*chr)->hapChar)==(*svc)->req.end() && std::find((*svc)->opt.begin(),(*svc)->opt.end(),(*chr)->hapChar)==(*svc)->opt.end())
|
||||
LOG0(" *** WARNING #%d! Service does not support this Characteristic ***\n",++nWarnings);
|
||||
else
|
||||
if(invalidUUID((*chr)->type,(*chr)->isCustom))
|
||||
if(invalidUUID((*chr)->type))
|
||||
LOG0(" *** ERROR #%d! Format of UUID is invalid ***\n",++nErrors);
|
||||
else
|
||||
if(std::find_if((*svc)->Characteristics.begin(),chr,[chr](SpanCharacteristic *c)->boolean{return(c->hapChar==(*chr)->hapChar);})!=chr)
|
||||
|
|
@ -937,8 +943,13 @@ void Span::processSerialCommand(const char *c){
|
|||
if((*chr)->setValidValuesError)
|
||||
LOG0(" *** WARNING #%d! Attempt to set Custom Valid Values for this Characteristic ignored ***\n",++nWarnings);
|
||||
|
||||
if((*chr)->format!=STRING && (!(((*chr)->uvGet<double>((*chr)->value) >= (*chr)->uvGet<double>((*chr)->minValue)) && ((*chr)->uvGet<double>((*chr)->value) <= (*chr)->uvGet<double>((*chr)->maxValue)))))
|
||||
if((*chr)->format<STRING && (!(((*chr)->uvGet<double>((*chr)->value) >= (*chr)->uvGet<double>((*chr)->minValue)) && ((*chr)->uvGet<double>((*chr)->value) <= (*chr)->uvGet<double>((*chr)->maxValue)))))
|
||||
LOG0(" *** WARNING #%d! Value of %g is out of range [%g,%g] ***\n",++nWarnings,(*chr)->uvGet<double>((*chr)->value),(*chr)->uvGet<double>((*chr)->minValue),(*chr)->uvGet<double>((*chr)->maxValue));
|
||||
|
||||
if(std::find(iidValues.begin(),iidValues.end(),(*chr)->iid)!=iidValues.end())
|
||||
LOG0(" *** ERROR #%d! IID already in use for another Service or Characteristic within this Accessory ***\n",++nErrors);
|
||||
|
||||
iidValues.push_back((*chr)->iid);
|
||||
|
||||
} // Characteristics
|
||||
|
||||
|
|
@ -985,7 +996,7 @@ void Span::processSerialCommand(const char *c){
|
|||
for(int i=0;i<Accessories.size();i++){ // identify all services with over-ridden loop() methods
|
||||
for(int j=0;j<Accessories[i]->Services.size();j++){
|
||||
SpanService *s=Accessories[i]->Services[j];
|
||||
LOG0("%-30s %8.8s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid,
|
||||
LOG0("%-30s %8.8s %10u %3u %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid,
|
||||
(void(*)())(s->*(&SpanService::update))!=(void(*)())(&SpanService::update)?"YES":"NO",
|
||||
(void(*)())(s->*(&SpanService::loop))!=(void(*)())(&SpanService::loop)?"YES":"NO",
|
||||
(void(*)(int,boolean))(s->*(&SpanService::button))!=(void(*)(int,boolean))(&SpanService::button)?"YES":"NO"
|
||||
|
|
@ -993,7 +1004,7 @@ void Span::processSerialCommand(const char *c){
|
|||
if(s->linkedServices.empty())
|
||||
LOG0("-");
|
||||
for(int k=0;k<s->linkedServices.size();k++){
|
||||
LOG0("%d",s->linkedServices[k]->iid);
|
||||
LOG0("%u",s->linkedServices[k]->iid);
|
||||
if(k<s->linkedServices.size()-1)
|
||||
LOG0(",");
|
||||
}
|
||||
|
|
@ -1016,6 +1027,9 @@ void Span::processSerialCommand(const char *c){
|
|||
}
|
||||
|
||||
LOG0("\nConfigured as Bridge: %s\n",isBridge?"YES":"NO");
|
||||
if(!isBridge && Accessories.size()>3)
|
||||
LOG0("*** WARNING #%d! HomeKit requires the device be configured as a Bridge when more than 3 Accessories are defined ***\n",++nWarnings);
|
||||
|
||||
if(hapConfig.configNumber>0)
|
||||
LOG0("Configuration Number: %d\n",hapConfig.configNumber);
|
||||
LOG0("\nDatabase Validation: Warnings=%d, Errors=%d\n",nWarnings,nErrors);
|
||||
|
|
@ -1078,7 +1092,7 @@ void Span::processSerialCommand(const char *c){
|
|||
reboot();
|
||||
} else {
|
||||
HAPClient::controllerList.push_back(tCont);
|
||||
HAPClient::charPrintRow(tCont.ID,36);
|
||||
HAPClient::charPrintRow(tCont.getID(),36);
|
||||
LOG0("\n");
|
||||
}
|
||||
}
|
||||
|
|
@ -1309,7 +1323,7 @@ boolean Span::deleteAccessory(uint32_t n){
|
|||
|
||||
///////////////////////////////
|
||||
|
||||
SpanCharacteristic *Span::find(uint32_t aid, int iid){
|
||||
SpanCharacteristic *Span::find(uint32_t aid, uint32_t iid){
|
||||
|
||||
int index=-1;
|
||||
for(int i=0;i<Accessories.size();i++){ // loop over all Accessories to find aid
|
||||
|
|
@ -1380,7 +1394,7 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
|
|||
okay|=1;
|
||||
} else
|
||||
if(!strcmp(t2,"iid") && (t3=strtok_r(t1,"}[]:, \"\t\n\r",&p2))){
|
||||
pObj[nObj].iid=atoi(t3);
|
||||
sscanf(t3,"%u",&pObj[nObj].iid);
|
||||
okay|=2;
|
||||
} else
|
||||
if(!strcmp(t2,"value") && (t3=strtok_r(t1,"}[]:,\"",&p2))){
|
||||
|
|
@ -1444,31 +1458,28 @@ int Span::updateCharacteristics(char *buf, SpanBuf *pObj){
|
|||
for(int i=0;i<nObj;i++){ // PASS 2: loop again over all objects
|
||||
if(pObj[i].status==StatusCode::TBD){ // if object status still TBD
|
||||
|
||||
StatusCode status=pObj[i].characteristic->service->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false
|
||||
StatusCode status=pObj[i].characteristic->service->update()?StatusCode::OK:StatusCode::Unable; // update service and save statusCode as OK or Unable depending on whether return is true or false
|
||||
|
||||
for(int j=i;j<nObj;j++){ // loop over this object plus any remaining objects to update values and save status for any other characteristics in this service
|
||||
for(int j=i;j<nObj;j++){ // loop over this object plus any remaining objects to update values and save status for any other characteristics in this service
|
||||
|
||||
if(pObj[j].characteristic->service==pObj[i].characteristic->service){ // if service of this characteristic matches service that was updated
|
||||
pObj[j].status=status; // save statusCode for this object
|
||||
LOG1("Updating aid=");
|
||||
LOG1(pObj[j].characteristic->aid);
|
||||
LOG1(" iid=");
|
||||
LOG1(pObj[j].characteristic->iid);
|
||||
if(status==StatusCode::OK){ // if status is okay
|
||||
pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value
|
||||
if(pObj[j].characteristic->nvsKey){ // if storage key found
|
||||
if(pObj[j].characteristic->format!=FORMAT::STRING && pObj[j].characteristic->format!=FORMAT::DATA)
|
||||
nvs_set_u64(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet())
|
||||
if(pObj[j].characteristic->service==pObj[i].characteristic->service){ // if service of this characteristic matches service that was updated
|
||||
pObj[j].status=status; // save statusCode for this object
|
||||
LOG1("Updating aid=%u iid=%u",pObj[j].characteristic->aid,pObj[j].characteristic->iid);
|
||||
if(status==StatusCode::OK){ // if status is okay
|
||||
pObj[j].characteristic->uvSet(pObj[j].characteristic->value,pObj[j].characteristic->newValue); // update characteristic value with new value
|
||||
if(pObj[j].characteristic->nvsKey){ // if storage key found
|
||||
if(pObj[j].characteristic->format<FORMAT::STRING)
|
||||
nvs_set_u64(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.UINT64); // store data as uint64_t regardless of actual type (it will be read correctly when access through uvGet())
|
||||
else
|
||||
nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data
|
||||
nvs_set_str(charNVS,pObj[j].characteristic->nvsKey,pObj[j].characteristic->value.STRING); // store data
|
||||
nvs_commit(charNVS);
|
||||
}
|
||||
LOG1(" (okay)\n");
|
||||
} else { // if status not okay
|
||||
pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value
|
||||
} else { // if status not okay
|
||||
pObj[j].characteristic->uvSet(pObj[j].characteristic->newValue,pObj[j].characteristic->value); // replace characteristic new value with original value
|
||||
LOG1(" (failed)\n");
|
||||
}
|
||||
pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic
|
||||
pObj[j].characteristic->updateFlag=0; // reset updateFlag for characteristic
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1541,13 +1552,13 @@ void Span::printfAttributes(SpanBuf *pObj, int nObj){
|
|||
boolean Span::printfAttributes(char **ids, int numIDs, int flags){
|
||||
|
||||
uint32_t aid;
|
||||
int iid;
|
||||
uint32_t iid;
|
||||
|
||||
SpanCharacteristic *Characteristics[numIDs];
|
||||
StatusCode status[numIDs];
|
||||
|
||||
for(int i=0;i<numIDs;i++){ // PASS 1: loop over all ids requested to check status codes - only errors are if characteristic not found, or not readable
|
||||
sscanf(ids[i],"%u.%d",&aid,&iid); // parse aid and iid
|
||||
sscanf(ids[i],"%u.%u",&aid,&iid); // parse aid and iid
|
||||
Characteristics[i]=find(aid,iid); // find matching chararacteristic
|
||||
|
||||
if(Characteristics[i]){ // if found
|
||||
|
|
@ -1571,7 +1582,7 @@ boolean Span::printfAttributes(char **ids, int numIDs, int flags){
|
|||
if(Characteristics[i]) // if found
|
||||
Characteristics[i]->printfAttributes(flags); // get JSON attributes for characteristic (may or may not include status=0 attribute)
|
||||
else{ // else create JSON status attribute based on requested aid/iid
|
||||
sscanf(ids[i],"%u.%d",&aid,&iid);
|
||||
sscanf(ids[i],"%u.%u",&aid,&iid);
|
||||
hapOut << "{\"iid\":" << iid << ",\"aid\":" << aid << ",\"status\":" << (int)status[i] << "}";
|
||||
}
|
||||
|
||||
|
|
@ -1586,6 +1597,26 @@ boolean Span::printfAttributes(char **ids, int numIDs, int flags){
|
|||
|
||||
///////////////////////////////
|
||||
|
||||
Span& Span::resetIID(uint32_t newIID){
|
||||
|
||||
if(Accessories.empty()){
|
||||
LOG0("\nFATAL ERROR! Can't reset the Accessory IID count without a defined Accessory ***\n");
|
||||
LOG0("\n=== PROGRAM HALTED ===");
|
||||
while(1);
|
||||
}
|
||||
|
||||
if(newIID<1){
|
||||
LOG0("\nFATAL ERROR! Request to reset the Accessory IID count to 0 not allowed (IID must be 1 or greater) ***\n");
|
||||
LOG0("\n=== PROGRAM HALTED ===");
|
||||
while(1);
|
||||
}
|
||||
|
||||
Accessories.back()->iidCount=newIID-1;
|
||||
return(*this);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
boolean Span::updateDatabase(boolean updateMDNS){
|
||||
|
||||
printfAttributes(GET_META|GET_PERMS|GET_TYPE|GET_DESC); // stream attributes database, which automtically produces a SHA-384 hash
|
||||
|
|
@ -1627,6 +1658,18 @@ boolean Span::updateDatabase(boolean updateMDNS){
|
|||
return(changed);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
list<Controller, Mallocator<Controller>>::const_iterator Span::controllerListBegin(){
|
||||
return(HAPClient::controllerList.cbegin());
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
||||
list<Controller, Mallocator<Controller>>::const_iterator Span::controllerListEnd(){
|
||||
return(HAPClient::controllerList.cend());
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
// SpanAccessory //
|
||||
///////////////////////////////
|
||||
|
|
@ -1733,7 +1776,7 @@ SpanService::~SpanService(){
|
|||
}
|
||||
}
|
||||
|
||||
LOG1("Deleted Service AID=%d IID=%d\n",accessory->aid,iid);
|
||||
LOG1("Deleted Service AID=%u IID=%u\n",accessory->aid,iid);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
|
@ -1833,12 +1876,12 @@ SpanCharacteristic::~SpanCharacteristic(){
|
|||
free(validValues);
|
||||
free(nvsKey);
|
||||
|
||||
if(format==FORMAT::STRING || format==FORMAT::DATA){
|
||||
if(format>=FORMAT::STRING){
|
||||
free(value.STRING);
|
||||
free(newValue.STRING);
|
||||
}
|
||||
|
||||
LOG1("Deleted Characteristic AID=%d IID=%d\n",aid,iid);
|
||||
LOG1("Deleted Characteristic AID=%u IID=%u\n",aid,iid);
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
|
|
@ -1927,13 +1970,7 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){
|
|||
if(evFlag && !(perms&EV)) // notification is not supported for characteristic
|
||||
return(StatusCode::NotifyNotAllowed);
|
||||
|
||||
LOG1("Notification Request for aid=");
|
||||
LOG1(aid);
|
||||
LOG1(" iid=");
|
||||
LOG1(iid);
|
||||
LOG1(": ");
|
||||
LOG1(evFlag?"true":"false");
|
||||
LOG1("\n");
|
||||
LOG1("Notification Request for aid=%u iid=%u: %s\n",aid,iid,evFlag?"true":"false");
|
||||
this->ev[HAPClient::conNum]=evFlag;
|
||||
}
|
||||
|
||||
|
|
@ -2005,7 +2042,9 @@ StatusCode SpanCharacteristic::loadUpdate(char *val, char *ev, boolean wr){
|
|||
break;
|
||||
|
||||
case STRING:
|
||||
uvSet(newValue,(const char *)val);
|
||||
case DATA:
|
||||
case TLV_ENC:
|
||||
uvSet(newValue,(const char *)stripBackslash(val));
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
|||
320
src/HomeSpan.h
320
src/HomeSpan.h
|
|
@ -55,6 +55,7 @@
|
|||
#include "HAPConstants.h"
|
||||
#include "HapQR.h"
|
||||
#include "Characteristics.h"
|
||||
#include "TLV8.h"
|
||||
|
||||
using std::vector;
|
||||
using std::unordered_map;
|
||||
|
|
@ -112,6 +113,7 @@ struct SpanRange;
|
|||
struct SpanBuf;
|
||||
struct SpanButton;
|
||||
struct SpanUserCommand;
|
||||
class Controller;
|
||||
|
||||
extern Span homeSpan;
|
||||
|
||||
|
|
@ -135,7 +137,7 @@ struct SpanConfig{
|
|||
|
||||
struct SpanBuf{ // temporary storage buffer for use with putCharacteristicsURL() and checkTimedResets()
|
||||
uint32_t aid=0; // updated aid
|
||||
int iid=0; // updated iid
|
||||
uint32_t iid=0; // updated iid
|
||||
boolean wr=false; // flag to indicate write-response has been requested
|
||||
char *val=NULL; // updated value (optional, though either at least 'val' or 'ev' must be specified)
|
||||
char *ev=NULL; // updated event notification flag (optional, though either at least 'val' or 'ev' must be specified)
|
||||
|
|
@ -188,6 +190,35 @@ struct SpanOTA{ // manages OTA process
|
|||
static void error(ota_error_t err);
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////
|
||||
// Paired Controller Structure for Permanently-Stored Data
|
||||
|
||||
class Controller {
|
||||
friend class HAPClient;
|
||||
|
||||
boolean allocated=false; // DEPRECATED (but needed for backwards compatability with original NVS storage of Controller info)
|
||||
boolean admin; // Controller has admin privileges
|
||||
uint8_t ID[36]; // Pairing ID
|
||||
uint8_t LTPK[32]; // Long Term Ed2519 Public Key
|
||||
|
||||
public:
|
||||
|
||||
Controller(uint8_t *id, uint8_t *ltpk, boolean ad){
|
||||
allocated=true;
|
||||
admin=ad;
|
||||
memcpy(ID,id,36);
|
||||
memcpy(LTPK,ltpk,32);
|
||||
}
|
||||
|
||||
Controller(){}
|
||||
|
||||
const uint8_t *getID() const {return(ID);}
|
||||
const uint8_t *getLTPK() const {return(LTPK);}
|
||||
boolean isAdmin() const {return(admin);}
|
||||
|
||||
};
|
||||
|
||||
//////////////////////////////////////
|
||||
// USER API CLASSES BEGINS HERE //
|
||||
//////////////////////////////////////
|
||||
|
|
@ -249,6 +280,7 @@ class Span{
|
|||
void (*apFunction)()=NULL; // optional function to invoke when starting Access Point
|
||||
void (*statusCallback)(HS_STATUS status)=NULL; // optional callback when HomeSpan status changes
|
||||
void (*rebootCallback)(uint8_t)=NULL; // optional callback when device reboots
|
||||
void (*controllerCallback)()=NULL; // optional callback when Controller is added/removed/changed
|
||||
|
||||
WiFiServer *hapServer; // pointer to the HAP Server connection
|
||||
Blinker *statusLED; // indicates HomeSpan status
|
||||
|
|
@ -279,7 +311,7 @@ class Span{
|
|||
|
||||
void printfAttributes(int flags=GET_VALUE|GET_META|GET_PERMS|GET_TYPE|GET_DESC); // writes Attributes JSON database to hapOut stream
|
||||
|
||||
SpanCharacteristic *find(uint32_t aid, int iid); // return Characteristic with matching aid and iid (else NULL if not found)
|
||||
SpanCharacteristic *find(uint32_t aid, uint32_t iid); // return Characteristic with matching aid and iid (else NULL if not found)
|
||||
int countCharacteristics(char *buf); // return number of characteristic objects referenced in PUT /characteristics JSON request
|
||||
int updateCharacteristics(char *buf, SpanBuf *pObj); // parses PUT /characteristics JSON request 'buf into 'pObj' and updates referenced characteristics; returns 1 on success, 0 on fail
|
||||
void printfAttributes(SpanBuf *pObj, int nObj); // writes SpanBuf objects to hapOut stream
|
||||
|
|
@ -287,10 +319,13 @@ class Span{
|
|||
void clearNotify(int slotNum); // set ev notification flags for connection 'slotNum' to false across all characteristics
|
||||
void printfNotify(SpanBuf *pObj, int nObj, int conNum); // writes notification JSON to hapOut stream based on SpanBuf objects and specified connection number
|
||||
|
||||
static boolean invalidUUID(const char *uuid, boolean isCustom){
|
||||
static boolean invalidUUID(const char *uuid){
|
||||
int x=0;
|
||||
sscanf(uuid,"%*8[0-9a-fA-F]%n",&x); // check for short-form of UUID
|
||||
if(strlen(uuid)==x && uuid[0]!='0')
|
||||
return(false);
|
||||
sscanf(uuid,"%*8[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*4[0-9a-fA-F]-%*12[0-9a-fA-F]%n",&x);
|
||||
return(isCustom && (strlen(uuid)!=36 || x!=36));
|
||||
return(strlen(uuid)!=36 || x!=36);
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -349,7 +384,9 @@ class Span{
|
|||
Span& setStatusCallback(void (*f)(HS_STATUS status)){statusCallback=f;return(*this);} // sets an optional user-defined function to call when HomeSpan status changes
|
||||
const char* statusString(HS_STATUS s); // returns char string for HomeSpan status change messages
|
||||
Span& setPairingCode(const char *s, boolean progCall=true); // sets the Pairing Code - use is NOT recommended. Use 'S' from CLI instead
|
||||
void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS
|
||||
void deleteStoredValues(){processSerialCommand("V");} // deletes stored Characteristic values from NVS
|
||||
Span& resetIID(uint32_t newIID); // resets the IID count for the current Accessory to start at newIID
|
||||
Span& setControllerCallback(void (*f)()){controllerCallback=f;return(*this);} // sets an optional user-defined function to call whenever a Controller is added/removed/changed
|
||||
|
||||
int enableOTA(boolean auth=true, boolean safeLoad=true){return(spanOTA.init(auth, safeLoad, NULL));} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password
|
||||
int enableOTA(const char *pwd, boolean safeLoad=true){return(spanOTA.init(true, safeLoad, pwd));} // enables Over-the-Air updates, with custom authorization password (overrides any password stored with the 'O' command)
|
||||
|
|
@ -388,6 +425,9 @@ class Span{
|
|||
TaskHandle_t getAutoPollTask(){return(pollTaskHandle);}
|
||||
|
||||
Span& setTimeServerTimeout(uint32_t tSec){webLog.waitTime=tSec*1000;return(*this);} // sets wait time (in seconds) for optional web log time server to connect
|
||||
|
||||
list<Controller, Mallocator<Controller>>::const_iterator controllerListBegin();
|
||||
list<Controller, Mallocator<Controller>>::const_iterator controllerListEnd();
|
||||
|
||||
[[deprecated("Please use reserveSocketConnections(n) method instead.")]]
|
||||
void setMaxConnections(uint8_t n){requestedMaxCon=n;} // sets maximum number of simultaneous HAP connections
|
||||
|
|
@ -403,15 +443,15 @@ class SpanAccessory{
|
|||
friend class SpanButton;
|
||||
friend class SpanRange;
|
||||
|
||||
uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1)
|
||||
int iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory
|
||||
vector<SpanService *, Mallocator<SpanService*>> Services; // vector of pointers to all Services in this Accessory
|
||||
uint32_t aid=0; // Accessory Instance ID (HAP Table 6-1)
|
||||
uint32_t iidCount=0; // running count of iid to use for Services and Characteristics associated with this Accessory
|
||||
vector<SpanService *, Mallocator<SpanService*>> Services; // vector of pointers to all Services in this Accessory
|
||||
|
||||
void printfAttributes(int flags); // writes Accessory JSON to hapOut stream
|
||||
void printfAttributes(int flags); // writes Accessory JSON to hapOut stream
|
||||
|
||||
protected:
|
||||
|
||||
~SpanAccessory(); // destructor
|
||||
~SpanAccessory(); // destructor
|
||||
|
||||
public:
|
||||
|
||||
|
|
@ -428,32 +468,34 @@ class SpanService{
|
|||
friend class SpanCharacteristic;
|
||||
friend class SpanRange;
|
||||
|
||||
int iid=0; // Instance ID (HAP Table 6-2)
|
||||
const char *type; // Service Type
|
||||
const char *hapName; // HAP Name
|
||||
boolean hidden=false; // optional property indicating service is hidden
|
||||
boolean primary=false; // optional property indicating service is primary
|
||||
vector<SpanCharacteristic *, Mallocator<SpanCharacteristic*>> Characteristics; // vector of pointers to all Characteristics in this Service
|
||||
vector<SpanService *, Mallocator<SpanService *>> linkedServices; // vector of pointers to any optional linked Services
|
||||
boolean isCustom; // flag to indicate this is a Custom Service
|
||||
SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service
|
||||
uint32_t iid=0; // Instance ID (HAP Table 6-2)
|
||||
const char *type; // Service Type
|
||||
const char *hapName; // HAP Name
|
||||
boolean hidden=false; // optional property indicating service is hidden
|
||||
boolean primary=false; // optional property indicating service is primary
|
||||
vector<SpanCharacteristic *, Mallocator<SpanCharacteristic*>> Characteristics; // vector of pointers to all Characteristics in this Service
|
||||
vector<SpanService *, Mallocator<SpanService *>> linkedServices; // vector of pointers to any optional linked Services
|
||||
boolean isCustom; // flag to indicate this is a Custom Service
|
||||
SpanAccessory *accessory=NULL; // pointer to Accessory containing this Service
|
||||
|
||||
void printfAttributes(int flags); // writes Service JSON to hapOut stream
|
||||
void printfAttributes(int flags); // writes Service JSON to hapOut stream
|
||||
|
||||
protected:
|
||||
|
||||
virtual ~SpanService(); // destructor
|
||||
vector<HapChar *, Mallocator<HapChar*>> req; // vector of pointers to all required HAP Characteristic Types for this Service
|
||||
vector<HapChar *, Mallocator<HapChar*>> opt; // vector of pointers to all optional HAP Characteristic Types for this Service
|
||||
virtual ~SpanService(); // destructor
|
||||
vector<HapChar *, Mallocator<HapChar*>> req; // vector of pointers to all required HAP Characteristic Types for this Service
|
||||
vector<HapChar *, Mallocator<HapChar*>> opt; // vector of pointers to all optional HAP Characteristic Types for this Service
|
||||
|
||||
public:
|
||||
|
||||
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
|
||||
SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor
|
||||
SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self
|
||||
SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self
|
||||
SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self
|
||||
vector<SpanService *, Mallocator<SpanService *>> getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops
|
||||
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
|
||||
SpanService(const char *type, const char *hapName, boolean isCustom=false); // constructor
|
||||
SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self
|
||||
SpanService *setHidden(); // sets the Service Type to be hidden and returns pointer to self
|
||||
SpanService *addLink(SpanService *svc); // adds svc as a Linked Service and returns pointer to self
|
||||
vector<SpanService *, Mallocator<SpanService *>> getLinks(){return(linkedServices);} // returns linkedServices vector for use as range in "for-each" loops
|
||||
|
||||
uint32_t getIID(){return(iid);} // returns IID of Service
|
||||
|
||||
virtual boolean update() {return(true);} // placeholder for code that is called when a Service is updated via a Controller. Must return true/false depending on success of update
|
||||
virtual void loop(){} // loops for each Service - called every cycle if over-ridden with user-defined code
|
||||
|
|
@ -478,7 +520,7 @@ class SpanCharacteristic{
|
|||
STRING_t STRING = NULL;
|
||||
};
|
||||
|
||||
int iid=0; // Instance ID (HAP Table 6-3)
|
||||
uint32_t iid=0; // Instance ID (HAP Table 6-3)
|
||||
HapChar *hapChar; // pointer to HAP Characteristic structure
|
||||
const char *type; // Characteristic Type
|
||||
const char *hapName; // HAP Name
|
||||
|
|
@ -509,7 +551,7 @@ class SpanCharacteristic{
|
|||
StatusCode loadUpdate(char *val, char *ev, boolean wr); // load updated val/ev from PUT /characteristic JSON request. Return intitial HAP status code (checks to see if characteristic is found, is writable, etc.)
|
||||
|
||||
String uvPrint(UVal &u){
|
||||
char c[67]; // space for 64 characters + surrounding quotes + terminating null
|
||||
char c[64];
|
||||
switch(format){
|
||||
case FORMAT::BOOL:
|
||||
return(String(u.BOOL));
|
||||
|
|
@ -530,14 +572,13 @@ class SpanCharacteristic{
|
|||
case FORMAT::STRING:
|
||||
case FORMAT::DATA:
|
||||
case FORMAT::TLV_ENC:
|
||||
sprintf(c,"\"%.64s\"",u.STRING); // Truncating string to 64 chars
|
||||
return(String(c));
|
||||
return(String("\"") + String(u.STRING) + String("\""));
|
||||
} // switch
|
||||
return(String()); // included to prevent compiler warnings
|
||||
}
|
||||
|
||||
void uvSet(UVal &dest, UVal &src){
|
||||
if(format==FORMAT::STRING || format==FORMAT::DATA || format==FORMAT::TLV_ENC)
|
||||
if(format>=FORMAT::STRING)
|
||||
uvSet(dest,(const char *)src.STRING);
|
||||
else
|
||||
dest=src;
|
||||
|
|
@ -650,6 +691,8 @@ class SpanCharacteristic{
|
|||
void *operator new(size_t size){return(HS_MALLOC(size));} // override new operator to use PSRAM when available
|
||||
SpanCharacteristic(HapChar *hapChar, boolean isCustom=false); // constructor
|
||||
|
||||
uint32_t getIID(){return(iid);} // returns IID of Characteristic
|
||||
|
||||
template <class T=int> T getVal(){
|
||||
return(uvGet<T>(value));
|
||||
}
|
||||
|
|
@ -658,37 +701,140 @@ class SpanCharacteristic{
|
|||
return(uvGet<T>(newValue));
|
||||
}
|
||||
|
||||
char *getString(){
|
||||
char *getStringGeneric(UVal &val){
|
||||
if(format>=FORMAT::STRING)
|
||||
return value.STRING;
|
||||
return val.STRING;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char *getNewString(){
|
||||
if(format>=FORMAT::STRING)
|
||||
return newValue.STRING;
|
||||
char *getString(){return(getStringGeneric(value));}
|
||||
char *getNewString(){return(getStringGeneric(newValue));}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void setString(const char *val, boolean notify=true){
|
||||
|
||||
if(!((perms&EV) || (updateFlag==2))){
|
||||
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName);
|
||||
return;
|
||||
}
|
||||
|
||||
if(updateFlag==1)
|
||||
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName);
|
||||
void setString(const char *val, boolean notify=true){
|
||||
|
||||
setValCheck();
|
||||
uvSet(value,val);
|
||||
uvSet(newValue,value);
|
||||
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(updateFlag!=2){ // do not broadcast EV if update is being done in context of write-response
|
||||
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
|
||||
|
|
@ -701,75 +847,13 @@ class SpanCharacteristic{
|
|||
nvs_set_str(homeSpan.charNVS,nvsKey,value.STRING); // store data
|
||||
nvs_commit(homeSpan.charNVS);
|
||||
}
|
||||
}
|
||||
|
||||
} // setString()
|
||||
|
||||
size_t getData(uint8_t *data, size_t len){
|
||||
if(format<FORMAT::DATA)
|
||||
return(0);
|
||||
|
||||
size_t olen;
|
||||
int ret=mbedtls_base64_decode(data,len,&olen,(uint8_t *)value.STRING,strlen(value.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 getNewData(uint8_t *data, size_t len){
|
||||
if(format<FORMAT::DATA)
|
||||
return(0);
|
||||
template <typename T> void setVal(T val, boolean notify=true){
|
||||
|
||||
size_t olen;
|
||||
int ret=mbedtls_base64_decode(data,len,&olen,(uint8_t *)newValue.STRING,strlen(newValue.STRING));
|
||||
setValCheck();
|
||||
|
||||
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 setData(uint8_t *data, size_t len, boolean notify=true){
|
||||
|
||||
if(!((perms&EV) || (updateFlag==2))){
|
||||
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName);
|
||||
return;
|
||||
}
|
||||
|
||||
if(len<1){
|
||||
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. Size of data buffer must be greater than zero\n\n",hapName);
|
||||
return;
|
||||
}
|
||||
|
||||
size_t olen;
|
||||
mbedtls_base64_encode(NULL,0,&olen,data,len); // get length of string buffer needed (mbedtls includes the trailing null in this size)
|
||||
TempBuffer<char> tBuf(olen); // create temporary string buffer, with room for trailing null
|
||||
mbedtls_base64_encode((uint8_t*)tBuf.get(),olen,&olen,data,len ); // encode data into string buf
|
||||
setString(tBuf,notify); // call setString to continue processing as if characteristic was a string
|
||||
}
|
||||
|
||||
template <typename T> void setVal(T val, boolean notify=true){
|
||||
|
||||
if(!((perms&EV) || (updateFlag==2))){
|
||||
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setData() ignored. No EVENT NOTIFICATION (EV) permission on this characteristic\n\n",hapName);
|
||||
return;
|
||||
}
|
||||
|
||||
if(updateFlag==1)
|
||||
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal() within update() while it is being updated by Home App. This may cause device to become non-responsive!\n\n",hapName);
|
||||
|
||||
if(!((val >= uvGet<T>(minValue)) && (val <= uvGet<T>(maxValue)))){
|
||||
LOG0("\n*** WARNING: Attempt to update Characteristic::%s with setVal(%g) is out of range [%g,%g]. This may cause device to become non-responsive!\n\n",
|
||||
hapName,(double)val,uvGet<double>(minValue),uvGet<double>(maxValue));
|
||||
|
|
|
|||
14
src/Span.h
14
src/Span.h
|
|
@ -429,6 +429,7 @@ namespace Service {
|
|||
CREATE_SERV(Television,D8) // Defines a TV. Optional Linked Services: <b>InputSource</b> and <b>TelevisionSpeaker</b>.
|
||||
REQ(Active);
|
||||
OPT(ActiveIdentifier);
|
||||
OPT(DisplayOrder);
|
||||
OPT(RemoteKey);
|
||||
OPT(PowerModeSelection);
|
||||
OPT(ConfiguredName);
|
||||
|
|
@ -513,6 +514,7 @@ namespace Characteristic {
|
|||
CREATE_CHAR(double,CurrentRelativeHumidity,0,0,100); // current humidity measured as a percentage
|
||||
CREATE_CHAR(double,CurrentTemperature,0,0,100); // current temperature measured in Celsius
|
||||
CREATE_CHAR(int,CurrentTiltAngle,0,-90,90); // current angle (in degrees) of slats from fully up or left (-90) to fully open (0) to fully down or right (90)
|
||||
CREATE_CHAR(const char *,DisplayOrder,"",0,1); // specifies the order in which the TV inputs are displayed for selection in the Home App
|
||||
CREATE_CHAR(double,FilterLifeLevel,100,0,100); // measured as a percentage of remaining life
|
||||
CREATE_CHAR(uint8_t,FilterChangeIndication,0,0,1,NO_CHANGE_NEEDED,CHANGE_NEEDED); // indicates state of filter
|
||||
CREATE_CHAR(const char *,FirmwareRevision,"1.0.0",0,1); // must be in form x[.y[.z]] - informational only
|
||||
|
|
@ -614,7 +616,11 @@ namespace Characteristic {
|
|||
|
||||
#define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \
|
||||
HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),DATA,true}; \
|
||||
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; }
|
||||
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; }
|
||||
|
||||
#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \
|
||||
HapChar _CUSTOM_##NAME {#UUID,#NAME,(PERMS)(PERMISISONS),TLV_ENC,true}; \
|
||||
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; }
|
||||
|
||||
#else
|
||||
|
||||
|
|
@ -628,7 +634,11 @@ namespace Characteristic {
|
|||
|
||||
#define CUSTOM_CHAR_DATA(NAME,UUID,PERMISISONS) \
|
||||
extern HapChar _CUSTOM_##NAME; \
|
||||
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="AA==", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; }
|
||||
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; }
|
||||
|
||||
#define CUSTOM_CHAR_TLV8(NAME,UUID,PERMISISONS) \
|
||||
extern HapChar _CUSTOM_##NAME; \
|
||||
namespace Characteristic { struct NAME : SpanCharacteristic { NAME(const char * val="", boolean nvsStore=false) : SpanCharacteristic {&_CUSTOM_##NAME,true} { init(val,nvsStore); } }; }
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
100
src/TLV8.cpp
100
src/TLV8.cpp
|
|
@ -70,12 +70,36 @@ void tlv8_t::osprint(std::ostream& os){
|
|||
|
||||
TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){
|
||||
|
||||
if(!empty() && front().tag==tag)
|
||||
front().update(len,val);
|
||||
if(!empty() && back().getTag()==tag)
|
||||
back().update(len,val);
|
||||
else
|
||||
emplace_front(tag,len,val);
|
||||
emplace_back(tag,len,val);
|
||||
|
||||
return(begin());
|
||||
return(--end());
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
TLV8_it TLV8::add(uint8_t tag, TLV8 &subTLV){
|
||||
|
||||
auto it=add(tag,subTLV.pack_size(),NULL); // create space for inserting sub TLV and store iterator to new element
|
||||
subTLV.pack(*it); // pack subTLV into new element
|
||||
return(--end());
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
TLV8_it TLV8::add(uint8_t tag, uint64_t val){
|
||||
|
||||
uint8_t *p=reinterpret_cast<uint8_t *>(&val);
|
||||
size_t nBytes=sizeof(uint64_t);
|
||||
while(nBytes>1 && p[nBytes-1]==0) // TLV requires little endian of size 1, 2, 4, or 8 bytes (include trailing zeros as needed)
|
||||
nBytes--;
|
||||
if(nBytes==3) // need to include a trailing zero so that total bytes=4
|
||||
nBytes=4;
|
||||
else if(nBytes>4) // need to include multiple trailing zeros so that total bytes=8
|
||||
nBytes=8;
|
||||
return(add(tag, nBytes, p));
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
|
|
@ -83,9 +107,9 @@ TLV8_it TLV8::add(uint8_t tag, size_t len, const uint8_t* val){
|
|||
TLV8_it TLV8::find(uint8_t tag, TLV8_it it1, TLV8_it it2){
|
||||
|
||||
auto it=it1;
|
||||
while(it!=it2 && (*it).tag!=tag)
|
||||
while(it!=it2 && it->getTag()!=tag)
|
||||
it++;
|
||||
return(it==it2?end():it);
|
||||
return(it);
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
|
|
@ -95,9 +119,9 @@ size_t TLV8::pack_size(TLV8_it it1, TLV8_it it2){
|
|||
size_t nBytes=0;
|
||||
|
||||
while(it1!=it2){
|
||||
nBytes+=2+(*it1).len;
|
||||
if((*it1).len>255)
|
||||
nBytes+=2*(((*it1).len-1)/255);
|
||||
nBytes+=2+(*it1).getLen();
|
||||
if((*it1).getLen()>255)
|
||||
nBytes+=2*(((*it1).getLen()-1)/255);
|
||||
it1++;
|
||||
}
|
||||
|
||||
|
|
@ -114,13 +138,13 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){
|
|||
switch(currentPackPhase){
|
||||
|
||||
case 0:
|
||||
currentPackBuf=(*currentPackIt).val.get();
|
||||
endPackBuf=(*currentPackIt).val.get()+(*currentPackIt).len;
|
||||
currentPackBuf=*currentPackIt;
|
||||
endPackBuf=(*currentPackIt)+currentPackIt->getLen();
|
||||
currentPackPhase=1;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
*buf++=(*currentPackIt).tag;
|
||||
*buf++=currentPackIt->getTag();
|
||||
nBytes++;
|
||||
currentPackPhase=2;
|
||||
break;
|
||||
|
|
@ -160,7 +184,10 @@ size_t TLV8::pack(uint8_t *buf, size_t bufSize){
|
|||
|
||||
/////////////////////////////////////
|
||||
|
||||
void TLV8::unpack(uint8_t *buf, size_t bufSize){
|
||||
int TLV8::unpack(uint8_t *buf, size_t bufSize){
|
||||
|
||||
if(bufSize==0)
|
||||
return(-1);
|
||||
|
||||
if(empty())
|
||||
unpackPhase=0;
|
||||
|
|
@ -171,18 +198,17 @@ void TLV8::unpack(uint8_t *buf, size_t bufSize){
|
|||
case 0:
|
||||
unpackTag=*buf++;
|
||||
bufSize--;
|
||||
add(unpackTag);
|
||||
unpackPhase=1;
|
||||
break;
|
||||
|
||||
case 1:
|
||||
unpackBytes=*buf++;
|
||||
bufSize--;
|
||||
if(unpackBytes==0){
|
||||
add(unpackTag);
|
||||
if(unpackBytes==0)
|
||||
unpackPhase=0;
|
||||
} else {
|
||||
else
|
||||
unpackPhase=2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
|
|
@ -196,8 +222,18 @@ void TLV8::unpack(uint8_t *buf, size_t bufSize){
|
|||
break;
|
||||
}
|
||||
}
|
||||
return(unpackPhase);
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
int TLV8::unpack(TLV8_it it){
|
||||
|
||||
if(it==end())
|
||||
return(0);
|
||||
|
||||
return(unpack(*it,it->getLen()));
|
||||
}
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
|
|
@ -219,14 +255,20 @@ const char *TLV8::getName(uint8_t tag){
|
|||
void TLV8::print(TLV8_it it1, TLV8_it it2){
|
||||
|
||||
while(it1!=it2){
|
||||
const char *name=getName((*it1).tag);
|
||||
const char *name=getName(it1->getTag());
|
||||
if(name)
|
||||
Serial.printf("%s",name);
|
||||
else
|
||||
Serial.printf("%d",(*it1).tag);
|
||||
Serial.printf("(%d) ",(*it1).len);
|
||||
for(int i=0;i<(*it1).len;i++)
|
||||
Serial.printf("%02X",(*it1).val.get()[i]);
|
||||
Serial.printf("%d",it1->getTag());
|
||||
Serial.printf("(%d) ",it1->getLen());
|
||||
for(int i=0;i<it1->getLen();i++)
|
||||
Serial.printf("%02X",(*it1)[i]);
|
||||
if(it1->getLen()==0)
|
||||
Serial.printf(" [null]");
|
||||
else if(it1->getLen()<=4)
|
||||
Serial.printf(" [%u]",it1->getVal());
|
||||
else if(it1->getLen()<=8)
|
||||
Serial.printf(" [%llu]",it1->getVal<uint64_t>());
|
||||
Serial.printf("\n");
|
||||
it1++;
|
||||
}
|
||||
|
|
@ -234,6 +276,20 @@ void TLV8::print(TLV8_it it1, TLV8_it it2){
|
|||
|
||||
//////////////////////////////////////
|
||||
|
||||
void TLV8::printAll_r(String label){
|
||||
|
||||
for(auto it=begin();it!=end();it++){
|
||||
Serial.printf("%s",label.c_str());
|
||||
print(it);
|
||||
TLV8 tlv;
|
||||
if(tlv.unpack(*it,(*it).getLen())==0)
|
||||
tlv.printAll_r(label+String((*it).getTag())+"-");
|
||||
}
|
||||
Serial.printf("%sDONE\n",label.c_str());
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void TLV8::osprint(std::ostream& os, TLV8_it it1, TLV8_it it2){
|
||||
|
||||
for(auto it=it1;it!=it2;it++)
|
||||
|
|
|
|||
62
src/TLV8.h
62
src/TLV8.h
|
|
@ -29,34 +29,62 @@
|
|||
|
||||
#include <Arduino.h>
|
||||
#include <sstream>
|
||||
#include <forward_list>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "PSRAM.h"
|
||||
|
||||
struct tlv8_t {
|
||||
class tlv8_t {
|
||||
|
||||
private:
|
||||
|
||||
uint8_t tag;
|
||||
size_t len;
|
||||
std::unique_ptr<uint8_t> val;
|
||||
|
||||
public:
|
||||
|
||||
tlv8_t(uint8_t tag, size_t len, const uint8_t* val);
|
||||
void update(size_t addLen, const uint8_t *addVal);
|
||||
void osprint(std::ostream& os);
|
||||
|
||||
|
||||
operator uint8_t*() const{
|
||||
return(val.get());
|
||||
}
|
||||
|
||||
uint8_t & operator[](int index){
|
||||
return(val.get()[index]);
|
||||
}
|
||||
|
||||
uint8_t *get(){
|
||||
return(val.get());
|
||||
}
|
||||
|
||||
size_t getLen(){
|
||||
return(len);
|
||||
}
|
||||
|
||||
uint8_t getTag(){
|
||||
return(tag);
|
||||
}
|
||||
|
||||
template<class T=uint32_t> T getVal(){
|
||||
T iVal=0;
|
||||
for(int i=0;i<len;i++)
|
||||
iVal|=static_cast<T>(val.get()[i])<<(i*8);
|
||||
return(iVal);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
typedef std::forward_list<tlv8_t, Mallocator<tlv8_t>>::iterator TLV8_it;
|
||||
typedef std::list<tlv8_t, Mallocator<tlv8_t>>::iterator TLV8_it;
|
||||
typedef struct { const uint8_t tag; const char *name; } TLV8_names;
|
||||
|
||||
/////////////////////////////////////
|
||||
|
||||
class TLV8 : public std::forward_list<tlv8_t, Mallocator<tlv8_t>> {
|
||||
class TLV8 : public std::list<tlv8_t, Mallocator<tlv8_t>> {
|
||||
|
||||
TLV8_it currentPackIt;
|
||||
TLV8_it endPackIt;
|
||||
|
|
@ -71,27 +99,31 @@ class TLV8 : public std::forward_list<tlv8_t, Mallocator<tlv8_t>> {
|
|||
|
||||
const TLV8_names *names=NULL;
|
||||
int nNames=0;
|
||||
|
||||
|
||||
void printAll_r(String label);
|
||||
|
||||
public:
|
||||
|
||||
TLV8(){};
|
||||
TLV8(const TLV8_names *names, int nNames) : names{names}, nNames{nNames} {};
|
||||
|
||||
TLV8_it add(uint8_t tag, size_t len, const uint8_t *val);
|
||||
TLV8_it add(uint8_t tag, uint8_t val){return(add(tag, 1, &val));}
|
||||
TLV8_it add(uint8_t tag, uint64_t val);
|
||||
TLV8_it add(uint8_t tag, TLV8 &subTLV);
|
||||
TLV8_it add(uint8_t tag){return(add(tag, 0, NULL));}
|
||||
TLV8_it add(uint8_t tag, const char *val){return(add(tag, strlen(val), reinterpret_cast<const uint8_t*>(val)));}
|
||||
|
||||
TLV8_it find(uint8_t tag, TLV8_it it1, TLV8_it it2);
|
||||
TLV8_it find(uint8_t tag, TLV8_it it1){return(find(tag, it1, end()));}
|
||||
TLV8_it find(uint8_t tag){return(find(tag, begin(), end()));}
|
||||
|
||||
int len(TLV8_it it){return(it==end()?-1:(*it).len);}
|
||||
int len(TLV8_it it){return(it==end()?-1:(*it).getLen());}
|
||||
|
||||
size_t pack_size(TLV8_it it1, TLV8_it it2);
|
||||
size_t pack_size(){return(pack_size(begin(), end()));}
|
||||
|
||||
void pack_init(TLV8_it it1, TLV8_it it2){currentPackIt=it1; endPackIt=it2; currentPackPhase=0;}
|
||||
void pack_init(TLV8_it it1){pack_init(it1, it1++);}
|
||||
void pack_init(TLV8_it it1){pack_init(it1, std::next(it1));}
|
||||
void pack_init(){pack_init(begin(),end());}
|
||||
|
||||
size_t pack(uint8_t *buf, size_t bufSize);
|
||||
|
|
@ -100,15 +132,17 @@ class TLV8 : public std::forward_list<tlv8_t, Mallocator<tlv8_t>> {
|
|||
const char *getName(uint8_t tag);
|
||||
|
||||
void print(TLV8_it it1, TLV8_it it2);
|
||||
void print(TLV8_it it1){print(it1, it1++);}
|
||||
void print(TLV8_it it1){print(it1, std::next(it1));}
|
||||
void print(){print(begin(), end());}
|
||||
void printAll(){printAll_r("");}
|
||||
|
||||
void osprint(std::ostream& os, TLV8_it it1, TLV8_it it2);
|
||||
void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, it1++);}
|
||||
void osprint(std::ostream& os, TLV8_it it1){osprint(os, it1, std::next(it1));}
|
||||
void osprint(std::ostream& os){osprint(os, begin(), end());}
|
||||
|
||||
void unpack(uint8_t *buf, size_t bufSize);
|
||||
|
||||
void wipe(){std::forward_list<tlv8_t, Mallocator<tlv8_t>>().swap(*this);}
|
||||
int unpack(uint8_t *buf, size_t bufSize);
|
||||
int unpack(TLV8_it it);
|
||||
|
||||
void wipe(){std::list<tlv8_t, Mallocator<tlv8_t>>().swap(*this);}
|
||||
|
||||
};
|
||||
|
|
|
|||
|
|
@ -73,6 +73,20 @@ char *Utils::readSerial(char *c, int max){
|
|||
|
||||
//////////////////////////////////////
|
||||
|
||||
char *Utils::stripBackslash(char *c){
|
||||
|
||||
size_t n=strlen(c);
|
||||
char *p=c;
|
||||
for(int i=0;i<=n;i++){
|
||||
*p=c[i];
|
||||
if(*p!='\\')
|
||||
p++;
|
||||
}
|
||||
return(c);
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
String Utils::mask(char *c, int n){
|
||||
String s="";
|
||||
int len=strlen(c);
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ 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 '*'
|
||||
|
||||
char *stripBackslash(char *c); // strips backslashes out of c (Apple unecessesarily "escapes" forward slashes in JSON)
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
|
|
|||
63
src/src.ino
63
src/src.ino
|
|
@ -27,61 +27,42 @@
|
|||
|
||||
#include "HomeSpan.h"
|
||||
|
||||
#define MAX_LIGHTS 1
|
||||
|
||||
void setup() {
|
||||
|
||||
|
||||
Serial.begin(115200);
|
||||
|
||||
homeSpan.setLogLevel(2);
|
||||
homeSpan.enableWebLog(50,"pool.ntp.org","UTC",NULL);
|
||||
// homeSpan.setPairingCode("12345670");
|
||||
// homeSpan.enableWebLog(50,"pool.ntp.org","UTC","myStatus");
|
||||
// homeSpan.enableWebLog(50,NULL,NULL,NULL);
|
||||
homeSpan.begin(Category::Lighting,"HomeSpan Light");
|
||||
|
||||
new SpanAccessory();
|
||||
new Service::AccessoryInformation();
|
||||
new Characteristic::Identify();
|
||||
new Service::LightBulb();
|
||||
new Characteristic::On();
|
||||
|
||||
homeSpan.begin(Category::Lighting,"HomeSpan Max");
|
||||
// new SpanUserCommand('k',"- list controllers",list_controllers);
|
||||
homeSpan.setControllerCallback(list_controllers);
|
||||
}
|
||||
|
||||
new SpanAccessory();
|
||||
new Service::AccessoryInformation();
|
||||
new Characteristic::Identify();
|
||||
|
||||
for(int i=0;i<MAX_LIGHTS;i++){
|
||||
new SpanAccessory();
|
||||
new Service::AccessoryInformation();
|
||||
new Characteristic::Identify();
|
||||
char c[30];
|
||||
sprintf(c,"Light-%d",i);
|
||||
new Characteristic::Name(c);
|
||||
new Service::LightBulb();
|
||||
new Characteristic::On(0,false);
|
||||
WEBLOG("Configuring %s",c);
|
||||
}
|
||||
|
||||
new SpanUserCommand('w', " - get web log test",webLogTest); // simulate getting an HTTPS request for weblog
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void loop(){
|
||||
|
||||
homeSpan.poll();
|
||||
|
||||
homeSpan.poll();
|
||||
}
|
||||
|
||||
//////////////////////////////////////
|
||||
|
||||
void webLogTest(const char *dummy){
|
||||
Serial.printf("\n*** In Web Log Test. Starting Custom Web Log Handler\n"); // here is where you would perform any HTTPS initializations
|
||||
homeSpan.getWebLog(webLogHandler,NULL); // this starts the normal weblog with output redirected to the specified handler (below)
|
||||
}
|
||||
|
||||
void webLogHandler(const char *buf, void *args){
|
||||
if(buf!=NULL){
|
||||
Serial.printf("--------\n");
|
||||
Serial.printf("%s",buf); // here is where you would transmit data to the HTTPS connection
|
||||
Serial.printf("********\n");
|
||||
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");
|
||||
}
|
||||
else
|
||||
Serial.print("*** DONE!\n\n"); // here is where you would close the HTTPS connection
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue