Merge pull request #45 from HomeSpan/release-1.2.0

Release 1.2.0
This commit is contained in:
HomeSpan 2021-02-18 21:37:00 -06:00 committed by GitHub
commit 6ff15e9b9e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 523 additions and 75 deletions

View File

@ -42,7 +42,19 @@ In addition to listening for incoming HAP requests, HomeSpan also continuously p
* This command deletes whatever WiFi Credentials have been stored in the device NVS, and restarts. * This command deletes whatever WiFi Credentials have been stored in the device NVS, and restarts.
* **S** \<code\> - change the HomeKit Pairing Setup Code to \<code\> * **S** \<code\> - change the HomeKit Pairing Setup Code to \<code\>
* Every HomeKit device requires a unique 8-digit Setup code used for pairing. When HomeSpan is run for the first time on a new device it sets the HomeKit Setup Code to a default value of **466-37-726**, and stores it in a dedicated NVS partition. This command allows you to update the stored Setup Code to any other 8-digit code. Note that in accordance with HAP specifications, HomeSpan actually stores a hashed version of the Setup Code, rather than the Setup Code itself. This means the actual value is not recoverable, so if you forget your Setup Code you'll need to run this command and create a new one. * Every HomeKit device requires a unique 8-digit Setup Code used for pairing. When HomeSpan is run for the first time on a new device it sets the HomeKit Setup Code to a default value of **466-37-726**, and stores it in a dedicated NVS partition. This command allows you to update the stored Setup Code to any other 8-digit code. Note that in accordance with HAP specifications, HomeSpan actually stores a hashed version of the Setup Code, rather than the Setup Code itself. This means the actual value is not recoverable, so if you forget your Setup Code you'll need to run this command and create a new one. Alternatively, you can restore the default Setup Code by fully erasing the NVS with the 'E' command.
* **Q** \<id\> - change HomeSpan's default QR-pairing Setup ID to \<id\>
* This command changes HomeSpan's default Setup ID, which is used when pairing with a QR Code, from the new-device value of "HSPN" to \<id\>. See [HomeSpan QR Codes](QRCodes.md) for details on how the Setup ID is used. The Setup ID must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z).
* Note the new Setup ID is retained in HomeSpan's NVS and used as the default for all sketches, unless a specific Setup ID is set in the sketch using the method `homeSpan.setQRID(const char *id)`. See the [HomeSpan API Reference](Reference.md) for details.
* Deleting a device's HomeKit ID and Controller data with the 'H' command (see below) also restores the default Setup ID to "HSPN".
* **O** - prompts you to set the password used for Over-the-Air (OTA) Updating
* HomeSpan supports [Over-the-Air (OTA) Updating](OTA.md) but, by default, requires the use of a password. Similar to a device's Setup Code, HomeSpan saves a non-recoverable *hashed* version of the OTA password you set with this command in NVS. If you forget the password you specified, you'll need to create a new one using this command. Alternatively, you can restore the default OTA password by fully erasing the NVS with the 'E' command.
* HomeSpan uses "homespan-ota" as its default OTA password for new devices.
* Changes to the OTA password do not take effect until the device is restarted.
* OTA is not active unless specifically enabled for a sketch using the method `homeSpan.enableOTA()`.
* You can disable the use an authorizing password by invoking `homeSpan.enableOTA(false)` instead, though this creates a security risk and is therefore **not** recommended. See the [HomeSpan API Reference](Reference.md) for details.
* **A** - start the HomeSpan Setup Access Point * **A** - start the HomeSpan Setup Access Point
* This command starts HomeSpan's temporary Access Point, which provides users with an alternate methods for configuring a device's WiFi Credentials and HomeKit Setup Code. Starting the Access Point with this command is identical to starting it via the Control Button. See the [HomeSpan User Guide](UserGuide.md) for complete details. * This command starts HomeSpan's temporary Access Point, which provides users with an alternate methods for configuring a device's WiFi Credentials and HomeKit Setup Code. Starting the Access Point with this command is identical to starting it via the Control Button. See the [HomeSpan User Guide](UserGuide.md) for complete details.
@ -53,12 +65,13 @@ In addition to listening for incoming HAP requests, HomeSpan also continuously p
* **H** - delete HomeKit Device ID as well as all Controller data and restart * **H** - delete HomeKit Device ID as well as all Controller data and restart
* In addition to deleting all Controller data (as if the 'U' command was run), this command also deletes the device's HomeKit ID. This unique ID is broadcast to all HomeKit Controllers so the device can be uniquely recognized. When HomeSpan first runs on a new device, it creates this unique ID and stores it permanently in an NVS partition. Normally, this ID should not changed once set. However, if you are actively developing and testing a HomeSpan sketch, you may find that HomeKit is cacheing information about your device and the changes you have made to your HAP Accessory Database are not always reflected in the Home App. Sometimes simply unpairing and re-pairing the device solves this HomeKit issue. If not, deleting your device's HomeKit ID with this command forces HomeSpan to generate a new one after restarting, which means HomeKit will think this is a completely different device, thereby ignoring any prior data it had cached. * In addition to deleting all Controller data (as if the 'U' command was run), this command also deletes the device's HomeKit ID. This unique ID is broadcast to all HomeKit Controllers so the device can be uniquely recognized. When HomeSpan first runs on a new device, it creates this unique ID and stores it permanently in an NVS partition. Normally, this ID should not changed once set. However, if you are actively developing and testing a HomeSpan sketch, you may find that HomeKit is cacheing information about your device and the changes you have made to your HAP Accessory Database are not always reflected in the Home App. Sometimes simply unpairing and re-pairing the device solves this HomeKit issue. If not, deleting your device's HomeKit ID with this command forces HomeSpan to generate a new one after restarting, which means HomeKit will think this is a completely different device, thereby ignoring any prior data it had cached.
* This command also restores the device's default Setup ID, which is used for optional pairing with QR codes, to "HSPN".
* **R** - restart the device * **R** - restart the device
* This command simply reboots HomeSpan. * This command simply reboots HomeSpan.
* **F** - factory reset and restart * **F** - factory reset and restart
* This deletes all data stored in the NVS, *except* for the HomeKit Pairing Setup Code, and restarts the device. This is effectively the same as executing the 'X' command followed by the 'H' command. * This deletes all data stored in the NVS, *except* for the HomeKit Pairing Setup Code and OTA password, and restarts the device. This is effectively the same as executing the 'X' command followed by the 'H' command.
* **E** - erase ALL stored data and restart * **E** - erase ALL stored data and restart
* This completely erases the NVS, deleting all stored data, *including* the HomeKit Pairing Setup code. The device is then restarted and initialized as if it were new. * This completely erases the NVS, deleting all stored data, *including* the HomeKit Pairing Setup code. The device is then restarted and initialized as if it were new.

29
docs/OTA.md Normal file
View File

@ -0,0 +1,29 @@
# Over-the-Air (OTA) Updates
HomeSpan supports Over-the-Air (OTA) updates, which allows you to *wirelessly* upload sketches directly from the Arduino IDE - no serial connection needed. To activate this feature for your sketch, simply call the method `homeSpan.enableOTA()` prior to calling `homeSpan.begin()`.
When a HomeSpan sketch is run with OTA enabled, the device shows up as a "network" port that can be selected under the *Tools → Port* menu in the Arduino IDE. Once selected, the IDE will direct all uploads to the device via WiFi instead of looking for it on a serial port. Note that you can upload via OTA even if your device is still connected to a serial port, but the Arduino IDE does not presently support multiple port connections at the same time. If you select a "network" port, the IDE will automatically close the Serial Monitor if it is open. To re-instate uploads via the "serial" port, simply choose that port from the *Tools → Port* menu in the Arduino IDE. Uploading via the serial port is always possible regardless of whether you have enabled OTA for a sketch.
By default, HomeSpan requires the use of a password whenever you begin an OTA upload. The default OTA password is "homespan-ota". The Arduino will prompt you for this password upon your first attempt to upload a sketch to a newly-connected device. However, once the password for a specific device is entered, the Arduino IDE retains it in memory as long as the IDE is running, thereby saving you from having to type it again every time you re-upload a sketch via OTA.
You can change the password for a HomeSpan device from the [HomeSpan CLI](CLI.md) with the 'O' command. Similar to a device's Setup Code, HomeSpan saves a non-recoverable hashed version of the OTA password you specify in non-volatile storage (NVS). If you forget the password you specified, you'll need to create a new one using the 'O' command, or you can restore the default OTA password by fully erasing the NVS with the 'E' command.
> :exclamation: Though not recommended, you can override the requirement for a password when enabling OTA for your sketch by including *false* as a parameter to the enabling method as such: `homeSpan.enableOTA(false)`. Use with caution! Anyone who can access the device over your network will now be able to upload a new sketch.
Note that in in order for OTA to properly operate, your sketch must be compiled with a partition scheme that includes OTA partitions. Partition schemes are found under the *Tools → Partition Scheme* menu of the Arduino IDE. Select a scheme that indicates it supports OTA. Note that schemes labeled "default" usually include OTA partitions. If unsure, try it out. HomeSpan will let you know if it does or does not.
This is because HomeSpan checks that a sketch has been compiled with OTA partitions if OTA has been enabled for that sketch. If OTA has been enabled but HomeSpan does not find any OTA partitioms, it will indicate it cannot start the OTA Server via a warning message sent to the Serial Monitor immediately after WiFi connectivity has been established. Otherwise it will output a confirmation message indicating the OTA Server has sucessfully started.
### OTA Tips and Tricks
* The name of the device HomeSpan uses for OTA is the same as the name you assigned in your call to `homeSpan.begin()`. If you have multiple devices you intend to maintain with OTA, use `homeSpan.begin()` to give them each different names so you can tell them apart when selecting which one to connect to from the Arduino IDE.
* Use the `homeSpan.setSketchVersion()` method to set a version for your sketch (see the [HomeSpan API](Reference.md) for details). If specified, HomeSpan will include the sketch version as part of its HAP MDNS broadcast. This allows you determine which version of a sketch is running on a remote HomeSpan device, even if you can't plug it into a serial port for use with the Arduino Serial Monitor. In addition to the sketch version, HomeSpan also includes two other fields in its MDNS broadcast: the version number of the HomeSpan *library* used to compile the sketch, and a field indicating whether or not OTA is enabled for the sketch.
* If a sketch you've uploaded with OTA does not operate as expected, you can continue making modifications to the code and re-upload again. Or, you can upload a prior version that was working properly. However, this assumes that the sketch you uploaded does not have major problems, such as causing a kernel panic that leads to an endless cycle of device reboots. If this happens, HomeSpan won't be able to run the OTA Server code, and further OTA updates will *not* ne possible. Instead, you'll have to connect the device through a serial port to upload a new, working sketch. **For this reason you should always fully test out a new sketch on a local device connected to your computer *before* uploading it to a remote, hard-to-access device via OTA.**
* The ESP32 itself supports "automated" rollbacks that are designed to restore a device with a previously-working sketch if the latest sketch causes a reboot before being able to set a self-test flag verifiying the code is operating correctly. However, the version of the ESP32-IDF library (3.2.3) used by the latest version of the Arduino-ESP32 Library (1.0.4, at the time of this posting) does not support this feature.

View File

@ -26,7 +26,7 @@ As shown above, the Simulator's QR Code Generator requires the input of the foll
* **Category**. Set this to match the Category of your HomeSpan device (e.g. Lightbulb, Fan, Door Lock). Note the Home App only uses this for display purposes when you first scan the QR Code. The Home App does not actually check that the Category listed in the QR Code matches the Category of the device you are pairing. * **Category**. Set this to match the Category of your HomeSpan device (e.g. Lightbulb, Fan, Door Lock). Note the Home App only uses this for display purposes when you first scan the QR Code. The Home App does not actually check that the Category listed in the QR Code matches the Category of the device you are pairing.
* **Setup Flags**. These flags provide information on which methods of pairing are supported by a HomeKit device. HomeSpan only supports IP Pairing, so you check that box and leave the other two blank. However, it does not seem to matter which boxes (if any) you check since the Home App does not appear to use this information for anything. * **Setup Flags**. These flags provide information on which methods of pairing are supported by a HomeKit device. HomeSpan only supports IP Pairing, so you check that box and leave the other two blank. However, it does not seem to matter which boxes (if any) you check since the Home App does not appear to use this information for anything.
* **Setup Code**. This is the 8-digit *Setup Code* you set for your device using either the [HomeSpan Command-Line Interface (CLI)](https://github.com/HomeSpan/HomeSpan/blob/master/docs/CLI.md) or [HomeSpan's WiFi Setup Web Page](https://github.com/HomeSpan/HomeSpan/blob/master/docs/UserGuide.md#setting-homespans-wifi-credentials-and-setup-code). Note the code shown in the above screenshot is the default HomeSpan uses if you do not set your own. * **Setup Code**. This is the 8-digit *Setup Code* you set for your device using either the [HomeSpan Command-Line Interface (CLI)](https://github.com/HomeSpan/HomeSpan/blob/master/docs/CLI.md) or [HomeSpan's WiFi Setup Web Page](https://github.com/HomeSpan/HomeSpan/blob/master/docs/UserGuide.md#setting-homespans-wifi-credentials-and-setup-code). Note the code shown in the above screenshot is the default HomeSpan uses if you do not set your own.
* **Setup ID**. This is the 4-character *Setup ID* you set for your HomeSpan device from within your sketch using the method `homeSpan.setQRID(const char *id)`. Note the ID shown in the above screenshot is the default HomeSpan uses if you do not set your own. Also note case matters! HSPN is not the same as "hspn". * **Setup ID**. This is the 4-character *Setup ID* you set for your HomeSpan device from within your sketch using the method `homeSpan.setQRID(const char *id)`. If you do not specify a QR Setup ID in your sketch, HomeSpan uses a default value of "HSPN" (as shown in the example above) unless you've updated the default for this device via the [CLI](CLI.md) using the 'Q' command. Note case matters! HSPN is not the same as "hspn".
* **Setup Payload**. This is the output that results from the above inputs, and is the text that is represented by the QR Code shown. The Setup Payload for a HomeKit device always begins with "X-HM://", followed by 9 alphanumeric characters, and ending with the *Setup ID* in plain text. If you've not changed HomeSpan's default *Setup Code* or *Setup ID*, you can pair your device by scanning this graphic with the Home App. Even easier is to scan it right from your camera - your iPhone will recognize that this is a HomeKit QR Code and open the Home App for you. * **Setup Payload**. This is the output that results from the above inputs, and is the text that is represented by the QR Code shown. The Setup Payload for a HomeKit device always begins with "X-HM://", followed by 9 alphanumeric characters, and ending with the *Setup ID* in plain text. If you've not changed HomeSpan's default *Setup Code* or *Setup ID*, you can pair your device by scanning this graphic with the Home App. Even easier is to scan it right from your camera - your iPhone will recognize that this is a HomeKit QR Code and open the Home App for you.
You probably noticed that the QR Code contains extra graphics, such as Apple's HomeKit logo in the upper left. This is purely cosmetic and not required by the Home App for pairing. Similarly, having the device's 8-digit *Setup Code* shown in big numerals in the upper right is also cosmetic and not needed for pairing, though it may be handy if you have problems scanning the QR Code and want to manually type the *Setup Code* into the Home App. You probably noticed that the QR Code contains extra graphics, such as Apple's HomeKit logo in the upper left. This is purely cosmetic and not required by the Home App for pairing. Similarly, having the device's 8-digit *Setup Code* shown in big numerals in the upper right is also cosmetic and not needed for pairing, though it may be handy if you have problems scanning the QR Code and want to manually type the *Setup Code* into the Home App.

View File

@ -37,9 +37,9 @@ HomeSpan provides a microcontroller-focused implementation of [Apple's HomeKit A
* Launch the WiFi Access Point * Launch the WiFi Access Point
* A standalone, detailed End-User Guide * A standalone, detailed End-User Guide
## Latest Update (1/24/2021) ## Latest Update (2/18/2021)
* HomeSpan 1.1.4 - HomeSpan now recognizes QR Codes for pairing! This release also includes a new method allowing you to change the TCP port used for communication to-and-from HomeKit, as well as a new method to modify the hostname suffix (or eliminate it all together). Improved parsing of text input through the Serial Monitor addresses (hopefully!) an issue using HomeSpan with Platform IO. See [Release](https://github.com/HomeSpan/HomeSpan/releases) for details. * HomeSpan 1.2.0 - HomeSpan now suports Over-the-Air ([OTA](https://github.com/HomeSpan/HomeSpan/blob/master/docs/OTA.md)) updates directly from the Arduino IDE (no serial connection needed)! This release also adds support for Linked Services and includes a new [tutorial example](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Tutorials.md#example-17---linkedservices) demonstrating how Linked Services can be used to implement a multi-headed spa shower. Other new features include the ability to set the version number of your sketch (helpful when using OTA updates), and a method for specifying a user-defined function to callback after WiFi connectivity is established. See [Release](https://github.com/HomeSpan/HomeSpan/releases) for details.
# HomeSpan Resources # HomeSpan Resources
@ -55,6 +55,7 @@ HomeSpan includes the following documentation:
* [HomeSpan User Guide](https://github.com/HomeSpan/HomeSpan/blob/master/docs/UserGuide.md) - turnkey instructions on how to configure an already-programmed HomeSpan device's WiFi Credentials, modify its HomeKit Setup Code, and pair the device to HomeKit. No computer needed! * [HomeSpan User Guide](https://github.com/HomeSpan/HomeSpan/blob/master/docs/UserGuide.md) - turnkey instructions on how to configure an already-programmed HomeSpan device's WiFi Credentials, modify its HomeKit Setup Code, and pair the device to HomeKit. No computer needed!
* [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) - a complete guide to the HomeSpan Library API * [HomeSpan API Reference](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Reference.md) - a complete guide to the HomeSpan Library API
* [HomeSpan QR Codes](https://github.com/HomeSpan/HomeSpan/blob/master/docs/QRCodes.md) - create and use QR Codes for pairing HomeSpan devices * [HomeSpan QR Codes](https://github.com/HomeSpan/HomeSpan/blob/master/docs/QRCodes.md) - create and use QR Codes for pairing HomeSpan devices
* [HomeSpan OTA](https://github.com/HomeSpan/HomeSpan/blob/master/docs/OTA.md) - update your sketches Over-the-Air directly from the Arduino IDE without a serial connection
* [HomeSpan Extras](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Extras.md) - integrated access to the ESP32's on-chip PWM and Remote Control peripherals! * [HomeSpan Extras](https://github.com/HomeSpan/HomeSpan/blob/master/docs/Extras.md) - integrated access to the ESP32's on-chip PWM and Remote Control peripherals!
* [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library * [HomeSpan Projects](https://github.com/topics/homespan) - real-world applications of the HomeSpan Library

View File

@ -52,7 +52,11 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
* this parameter can also be changed at runtime via the [HomeSpan CLI](CLI.md) * this parameter can also be changed at runtime via the [HomeSpan CLI](CLI.md)
* `void setMaxConnections(uint8_t nCon)` * `void setMaxConnections(uint8_t nCon)`
* sets the maximum number of HAP Controllers that be simultaneously connected to HomeSpan (default=8) * sets the desired maximum number of HAP Controllers that can be simultaneously connected to HomeSpan (default=8)
* due to limitations of the ESP32 Arduino library, HomeSpan will override *nCon* if it exceed the following internal limits:
* if OTA is not enabled, *nCon* will be reduced to 8 if it has been set to a value greater than 8
* if OTA is enabled, *nCon* will be reduced to 7 if it has been set to a value greater than 7
* if you add code to a sketch that uses it own network resources, you will need to determine how many TCP sockets your code may need to use, and use this method to reduce the maximum number of connections available to HomeSpan accordingly
* `void setPortNum(uint16_t port)` * `void setPortNum(uint16_t port)`
* sets the TCP port number used for communication between HomeKit and HomeSpan (default=80) * sets the TCP port number used for communication between HomeKit and HomeSpan (default=80)
@ -60,14 +64,31 @@ The following **optional** `homeSpan` methods override various HomeSpan initiali
* `void setHostNameSuffix(const char *suffix)` * `void setHostNameSuffix(const char *suffix)`
* sets the suffix HomeSpan appends to *hostNameBase* to create the full hostName * sets the suffix HomeSpan appends to *hostNameBase* to create the full hostName
* if not specified, the default is for HomeSpan to append a dash "-" followed the 6-byte Accessory ID of the HomeSpan device * if not specified, the default is for HomeSpan to append a dash "-" followed the 6-byte Accessory ID of the HomeSpan device
* setting *suffix* to a null string "" is permitted. * setting *suffix* to a null string "" is permitted
* example: `homeSpan.begin(Category::Fans, "Living Room Ceiling Fan", "LivingRoomFan");` will yield a default *hostName* of the form *LivingRoomFan-A1B2C3D4E5F6.local*. Calling `homeSpan.setHostNameSuffix("v2")` prior to `homeSpan.begin()` will instead yield a *hostName* of *LivingRoomFanv2.local* * example: `homeSpan.begin(Category::Fans, "Living Room Ceiling Fan", "LivingRoomFan");` will yield a default *hostName* of the form *LivingRoomFan-A1B2C3D4E5F6.local*. Calling `homeSpan.setHostNameSuffix("v2")` prior to `homeSpan.begin()` will instead yield a *hostName* of *LivingRoomFanv2.local*
* `void setQRID(const char *ID)` * `void setQRID(const char *id)`
* changes the Setup ID from the HomeSpan default ("HSPN") to *ID* * changes the Setup ID, which is used for pairing a device with a [QR Code](QRCodes.md), from the HomeSpan default to *id*
* *ID* must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z). If not, the request to change the Setup ID is silently ignored and remains "HSPN" * the HomeSpan default is "HSPN" unless permanently changed for the device via the [HomeSpan CLI](CLI.md) using the 'Q' command
* The Setup ID is an optional parameter used when pairing the device to HomeKit with a QR Code (instead of the usual Setup Code) * *id* must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z). If not, the request to change the Setup ID is silently ignored and the default is used instead
* `void enableOTA(boolean auth=true)`
* enables [Over-the-Air (OTA) Updating](OTA.md) of a HomeSpan device, which is otherwise disabled
* HomeSpan OTA requires an authorizing password unless *auth* is specified and set to *false*
* the default OTA password for new HomeSpan devices is "homespan-ota"
* this can be changed via the [HomeSpan CLI](CLI.md) using the 'O' command
* `void setSketchVersion(const char *sVer)`
* sets the version of a HomeSpan sketch to *sVer*, which can be any arbitrary character string
* if unspecified, HomeSpan uses "n/a" as the default version text
* HomeSpan displays the version of the sketch in the Arduino IDE Serial Monitor upon start-up
* HomeSpan also includes both the version of the sketch, as well as the version of the HomeSpan library used to compile the sketch, as part of its HAP MDNS broadcast. This data is *not* used by HAP. Rather, it is for informational purposes and allows you to identify the version of a sketch for a device that is updated via [OTA](OTA.md), rather than connected to a computer
* `const char *getSketchVersion()`
* returns the version of a HomeSpan sketch, as set using `void setSketchVersion(const char *sVer)`, or "n/a" if not set
* `void setWifiCallback(void (*func)(void))`
* Sets an optional user-defined callback function, *func*, to be called by HomeSpan upon start-up just after WiFi connectivity has been established. This one-time call to *func* is provided for users that are implementing other network-related services as part of their sketch, but that cannot be started until WiFi connectivity is established. The function *func* must be of type *void* and have no arguments
## *SpanAccessory(uint32_t aid)* ## *SpanAccessory(uint32_t aid)*
@ -97,10 +118,17 @@ This is a **base class** from which all HomeSpan Services are derived, and shoul
The following methods are supported: The following methods are supported:
* `SpanService *setPrimary()` * `SpanService *setPrimary()`
* specifies that this is the primary Service for the Accessory. Returns a pointer to the Service itself so that the method can be chained during instantiation. Example: `new Service::Fan->setPrimary();` * specifies that this is the primary Service for the Accessory. Returns a pointer to the Service itself so that the method can be chained during instantiation.
* example: `(new Service::Fan)->setPrimary();`
* `SpanService *setHidden()` * `SpanService *setHidden()`
* specifies that this is hidden Service for the Accessory. Returns a pointer to the Service itself so that the method can be chained during instantiation. * specifies that this is hidden Service for the Accessory. Returns a pointer to the Service itself so that the method can be chained during instantiation.
* note this does not seem to have any affect on the Home App. Services marked as hidden still appear as normal
* `SpanService *addLink(SpanService *svc)`
* adds *svc* as a Linked Service. Returns a pointer to the calling Service itself so that the method can be chained during instantiation.
* note that Linked Services are only applicable for select HAP Services. See Apple's [HAP-R2](https://developer.apple.com/support/homekit-accessory-protocol/) documentation for full details.
* example: `(new Service::Faucet)->addLink(new Service::Valve)->addLink(new Service::Valve);` (links two Valves to a Faucet)
* `virtual boolean update()` * `virtual boolean update()`
* HomeSpan calls this method upon receiving a request from a HomeKit Controller to update one or more Characteristics associated with the Service. Users should override this method with code that implements that requested updates using one or more of the SpanCharacteristic methods below. Method **must** return *true* if update succeeds, or *false* if not. * HomeSpan calls this method upon receiving a request from a HomeKit Controller to update one or more Characteristics associated with the Service. Users should override this method with code that implements that requested updates using one or more of the SpanCharacteristic methods below. Method **must** return *true* if update succeeds, or *false* if not.

View File

@ -87,6 +87,11 @@ This example introduces HomeSpan functionality that lets you easily connect real
### [Example 16 - ProgrammableSwitches](../examples/16-ProgrammableSwitches) ### [Example 16 - ProgrammableSwitches](../examples/16-ProgrammableSwitches)
Example 16 does not introduce any new HomeSpan functionality, but instead showcases a unique feature of HomeKit that you can readily access with HomeSpan. In all prior examples we used the ESP32 to control a local appliance - something connected directly to the ESP32 device. We've then seen how you can control the device via HomeKit's iOS or MacOS Home App, or by the addition of local pushbuttons connected directly to the ESP32 device. In this example we do the opposite, and use pushbuttons connected to the ESP32 to control OTHER HomeKit devices of any type. To do so, we use HomeKit's Stateless Programmable Switch Service. Example 16 does not introduce any new HomeSpan functionality, but instead showcases a unique feature of HomeKit that you can readily access with HomeSpan. In all prior examples we used the ESP32 to control a local appliance - something connected directly to the ESP32 device. We've then seen how you can control the device via HomeKit's iOS or MacOS Home App, or by the addition of local pushbuttons connected directly to the ESP32 device. In this example we do the opposite, and use pushbuttons connected to the ESP32 to control OTHER HomeKit devices of any type. To do so, we use HomeKit's Stateless Programmable Switch Service.
### [Example 17 - LinkedServices](../examples/17-LinkedServices)
Example 17 introduces the HAP concept of Linked Services and demonstrates how they are used through the implementation of a multi-head Shower. This example also illustrates some different coding styles that showcase the power and flexibility of HomeSpan's C++ *structure-based* design paradigm. New HomeSpan API topics covered in this example include:
* creating Linked Services using the `addLink()` method
--- ---
[↩️](README.md) Back to the Welcome page [↩️](README.md) Back to the Welcome page

View File

@ -0,0 +1,173 @@
/*********************************************************************************
* MIT License
*
* Copyright (c) 2020-2021 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 17: Linked Services //
// * implementing a multi-head Spa Shower //
// //
////////////////////////////////////////////////////////////
#include "HomeSpan.h"
// HAP normally treats multiple Services created within the same Accessory as independent of one another. However, certain HAP Services are designed to represent a central point
// of control over other, more typical Services. For example, you can create an Accessory with one or more Valve Services, each operating independently. But HAP also includes a
// Faucet Service that can be used to "control" one or more Valve Services. This is done by LINKING the Faucet Service to one or more Valve Services.
//
// Only a few types of HAP Services allow/require links to be made to other Services, and only a few types of Services can be selected as a link.
//
// Linked Services can be created in HomeSpan using the addLink() method. For example, if spaShower is a pointer to a Faucet Service, and showerHead and handSprayer are both
// pointers to Valve Services, you can link the faucet to the valves as follows:
//
// spaShower->addLink(showerHead);
// spaShower->addLink(handSprayer);
//
// The addLink method returns a pointer to the object that called it, which provides you with the option of combining both methods above into a single line as follows:
//
// spaShower->addLink(showerHead)->addLink(handSprayer);
// Note that HAP does *not* provide any of the actual logic that's needed for the "controlling" Service to operate the "linked" Services. This must still be programmed by the user.
// More so, the logic needs to conform with the behavior HAP expects for the Service as outlined in the HAP documention for the controlling Service. The only thing HAP really does with
// Linked Services, besides making you do extra work, is to provide a customized Tile that shows you the controlling Service and the Services to which it is linked.
// Also as noted above, only a few Services support the Linked Services protcol. If you use the addLink() method with Services that do not support linkages, HAP will simply ignore
// the linkage request. But the reverse is not true. If you implement a Service that requires other Linked Services (such as a Faucet) you MUST create those linkages for the
// Service to operate properly.
// Example 17 below demonstrates Linked Services by implementing a multi-head Spa Shower using one HAP Faucet Service and muliple HAP Valve Services. As usual, we will create
// our own "child" Services from HAP's Faucet and Valve Services so we can add the logic required to implement our device. However, instead of placing all that logic in separate
// *.h files, we include them directly in the main sketch file (below) to illustrate an alternative way of organizing your sketch code.
// This Example further illustrates yet another coding style option: instead of instantiating all the Services needed in the setup() portion of the sketch, we only instantiate
// the Shower Service, and have the Shower Service itself instantiate all the Valve Services. In fact, our entire definition of the Value Service is fully encapsulated
// in the definition of the Shower Service.
// This hopefully provides a good example of the flexibility of HomeSpan. Because all HomeSpan components are defined using standard C++ structures (as opposed to external files
// based on some pre-defined format), you can choose whatever coding style you'd like. The style below was chosen since it seemed to fit well for illustating how Linked Services work.
// But note that it is only the addLink() method that creates the actual linkages. The fact that the WaterValve Service is defined within the Shower Service is purely a style choice
// and does not itself create the linkages. We could have used a standalone structure for the WaterValve definitions and the results would be the same.
//////////////////////////////////////
// The HAP Valve Service requires both an Active Characteristic and an InUse Characteristic. The Active Characteristic controls whether a Valve is open (active) or closed (inactive).
// This Characteristic is normally controlled by the user through the Home App. The InUse Characteristic specifies whether there is water (or gas, etc.) actually flowing
// through the Valve. This is because opening a Valve does not necessarily mean water will be flowing. There may be another real-world "master" Valve that also needs to be open
// before water can begin flowing. Or there may be another Service that must also be Active to enable water to flow through the Valve. Hence, InUse can either be true or false
// if the Valve is open, but it can only be false if the Valve is closed. The Home App cannot change the InUse Characteristic. It is only read by the Home App as a status.
// It is possible to create a multi-valve Accessory where each Valve is controlled independently from the Home App, and HomeSpan uses internal logic to determine, based
// on the combination of Valves that are open or closed, which Valves have water flowing (InUse=true) and which do not (InUse=false).
// The HAP Faucet Service is used to create a "central control switch" for all the Valves linked to it. The Home App displays each Valve as a small icon on the control
// page of the Faucet. Clicking a Valve icon toggles it open/close, and changes the icon accordingly. However, water is not supposed to flow unless the Shower control switch
// itself is also turned on. Thus, the logic you need to encode to implement a HAP Faucet is to set the InUse Characteristic of a Valve to true ONLY if the Valve is open
// AND the Shower is switched on. If the Shower is then switched off, the Valve remains open, but the InUse Characteristic needs to be reset to false. Similarly, if the Shower
// is switched back on, the InUse Characteristic of each Valve that is open needs to be set to true. This mimics how an actual Shower with a central controlling switch
// would operate.
// In addition, the Home App displays one of 4 status messages as you operate the Shower and Valve controls:
// OFF: The Shower switch is OFF, AND the InUse Characteristic for EVERY Valve is set to FALSE (no water flowing anywhere);
// STOPPING: The Shower switch is OFF, BUT at least one Valve still has its InUse Characteristic set to TRUE. Presumably this means the Valve is in the process of turning off;
// STARTING: The Shower switch is ON, BUT the InUse Characteristic for EVERY Valve is set to FALSE. This indicates the Shower is waiting for water to start flowing;
// RUNNING: The Shower switch in ON, AND at least one of the Valves has its InUse Characteristic set to TRUE. This indicates water is flowing.
// Note that the Shower Service only monitors the InUse Characteristics of its Linked Valves. It does not monitor the Active Characteristics of the Linked Valves. Also, turning
// on and off the Shower Switch should NOT change the Active Characteristic of any Valve. Below is the code that implements all of this HAP-required logic:
struct Shower:Service::Faucet{ // this is our Shower structure, which we define as a child class of the HomeSpan Faucet structure
SpanCharacteristic *active=new Characteristic::Active(); // our implementation only requires the Active Characteristic
Shower(int nHeads){ // this is the constructor for Shower. It takes a single argument that specifies the number of spray heads (WaterValves)
for(int i=0;i<nHeads;i++) // for each spray head needed ---
addLink(new WaterValve(this)); // --- instantiate a new WaterValve AND link it to the Shower. Also, pass the Shower object's pointer to WaterValve constructor. We'll see why below.
}
struct WaterValve:Service::Valve{ // here we define our WaterValve structure as a child class of the HomeSpan Valve Service
SpanCharacteristic *active=new Characteristic::Active();; // the Active Characteristic is used to specify whether the Valve is Active (open) or Inactive (closed)
SpanCharacteristic *inUse=new Characteristic::InUse(); // the InUser Characteristic is used to specify whether water is actually flowing through value
Shower *shower; // storage for the pointer to the "controlling" Shower Service
WaterValve(Shower *s){ // this is constructor for WaterValve. It takes a single argument that points to the "controlling" Shower Service
shower=s; // store the pointer to the Shower Service
new Characteristic::ValveType(2); // specify the Value Type (2=Shower Head; see HAP R2 for other choices)
}
boolean update() override { // HomeSpan calls this whenever the Home App requests a change in a Valve's Active Characteristic
if(shower->active->getVal()) // here's where we use the pointer to Shower: ONLY if the Shower object itself is active---
inUse->setVal(active->getNewVal()); // --- do we update the InUse Characteristic to reflect a change in the status of flowing water.
return(true); // Note that the Valve itself will still change from Active to Inactive (or vice versa) regardless of the status of the Shower
}
void loop() override { // Here we check if the Shower is turned on or off, and determine if that means we need to update the Valve
if(shower->active->getVal() && active->getVal() && !inUse->getVal()) // If the Shower is Active, and the Valve is Active, but InUse is NOT Active...
inUse->setVal(1); // ...set the InUse Characteristic to Active
else if(!shower->active->getVal() && inUse->getVal()) // Otherwise, if the Shower is NOT Active but InUse is Active...
inUse->setVal(0); // ...set the InUse Characteristic to NOT Active
}
}; // WaterValve
};
//////////////////////////////////////
void setup() {
Serial.begin(115200);
homeSpan.begin(Category::ShowerSystems,"HomeSpan Shower");
new SpanAccessory();
new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics
new Characteristic::Name("Spa Shower");
new Characteristic::Manufacturer("HomeSpan");
new Characteristic::SerialNumber("HSL-123");
new Characteristic::Model("HSL Test");
new Characteristic::FirmwareRevision(HOMESPAN_VERSION);
new Characteristic::Identify();
new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service
new Characteristic::Version("1.1.0");
new Shower(4); // Create a Spa Shower with 4 spray heads
} // end of setup()
//////////////////////////////////////
void loop(){
homeSpan.poll();
} // end of loop()
//////////////////////////////////////

View File

@ -1,5 +1,5 @@
name=HomeSpan name=HomeSpan
version=1.1.4 version=1.2.0
author=Gregg <homespan@icloud.com> author=Gregg <homespan@icloud.com>
maintainer=Gregg <homespan@icloud.com> maintainer=Gregg <homespan@icloud.com>
sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE. sentence=A robust and extremely easy-to-use HomeKit implementation for the Espressif ESP32 running on the Arduino IDE.

View File

@ -28,6 +28,7 @@
#include <ESPmDNS.h> #include <ESPmDNS.h>
#include <nvs_flash.h> #include <nvs_flash.h>
#include <sodium.h> #include <sodium.h>
#include <MD5Builder.h>
#include "HAP.h" #include "HAP.h"
#include "HomeSpan.h" #include "HomeSpan.h"
@ -43,10 +44,21 @@ void HAPClient::init(){
nvs_open("WIFI",NVS_READWRITE,&wifiNVS); // open WIFI data namespace in NVS nvs_open("WIFI",NVS_READWRITE,&wifiNVS); // open WIFI data namespace in NVS
nvs_open("SRP",NVS_READWRITE,&srpNVS); // open SRP data namespace in NVS nvs_open("SRP",NVS_READWRITE,&srpNVS); // open SRP data namespace in NVS
nvs_open("HAP",NVS_READWRITE,&hapNVS); // open HAP data namespace in NVS nvs_open("HAP",NVS_READWRITE,&hapNVS); // open HAP data namespace in NVS
nvs_open("OTA",NVS_READWRITE,&otaNVS); // open OTA data namespace in NVS
if(!nvs_get_blob(wifiNVS,"WIFIDATA",NULL,&len)) // if found WiFi data in NVS if(!nvs_get_blob(wifiNVS,"WIFIDATA",NULL,&len)) // if found WiFi data in NVS
nvs_get_blob(wifiNVS,"WIFIDATA",&homeSpan.network.wifiData,&len); // retrieve data nvs_get_blob(wifiNVS,"WIFIDATA",&homeSpan.network.wifiData,&len); // retrieve data
if(!nvs_get_str(otaNVS,"OTADATA",NULL,&len)){ // if found OTA data in NVS
nvs_get_str(otaNVS,"OTADATA",homeSpan.otaPwd,&len); // retrieve data
} else {
MD5Builder otaPwdHash;
otaPwdHash.begin();
otaPwdHash.add(DEFAULT_OTA_PASSWORD);
otaPwdHash.calculate();
otaPwdHash.getChars(homeSpan.otaPwd);
}
struct { // temporary structure to hold SRP verification code and salt stored in NVS struct { // temporary structure to hold SRP verification code and salt stored in NVS
uint8_t salt[16]; uint8_t salt[16];
uint8_t verifyCode[384]; uint8_t verifyCode[384];
@ -67,6 +79,14 @@ void HAPClient::init(){
Serial.print(homeSpan.qrCode.get(atoi(homeSpan.defaultSetupCode),homeSpan.qrID,atoi(homeSpan.category))); Serial.print(homeSpan.qrCode.get(atoi(homeSpan.defaultSetupCode),homeSpan.qrID,atoi(homeSpan.category)));
Serial.print("\n\n"); Serial.print("\n\n");
} }
if(!strlen(homeSpan.qrID)){ // Setup ID has not been specified in sketch
if(!nvs_get_str(hapNVS,"SETUPID",NULL,&len)){ // check for saved value
nvs_get_str(hapNVS,"SETUPID",homeSpan.qrID,&len); // retrieve data
} else {
sprintf(homeSpan.qrID,"%s",DEFAULT_QR_ID); // use default
}
}
if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS if(!nvs_get_blob(hapNVS,"ACCESSORY",NULL,&len)){ // if found long-term Accessory data in NVS
nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data nvs_get_blob(hapNVS,"ACCESSORY",&accessory,&len); // retrieve data
@ -443,8 +463,8 @@ int HAPClient::postPairSetupURL(){
tlv8.clear(); tlv8.clear();
tlv8.val(kTLVType_State,pairState_M2); // set State=<M2> tlv8.val(kTLVType_State,pairState_M2); // set State=<M2>
srp.createPublicKey(); // create accessory public key from random Pair-Setup code (displayed to user) srp.createPublicKey(); // create accessory public key from random Pair-Setup code (displayed to user)
srp.loadTLV(kTLVType_PublicKey,&srp.B); // load server public key, B srp.loadTLV(kTLVType_PublicKey,&srp.B,384); // load server public key, B
srp.loadTLV(kTLVType_Salt,&srp.s); // load salt, s srp.loadTLV(kTLVType_Salt,&srp.s,16); // load salt, s
tlvRespond(); // send response to client tlvRespond(); // send response to client
pairStatus=pairState_M3; // set next expected pair-state request from client pairStatus=pairState_M3; // set next expected pair-state request from client
@ -481,7 +501,7 @@ int HAPClient::postPairSetupURL(){
srp.createProof(); // M1 has been successully verified; now create accessory proof M2 srp.createProof(); // M1 has been successully verified; now create accessory proof M2
tlv8.clear(); // clear TLV records tlv8.clear(); // clear TLV records
tlv8.val(kTLVType_State,pairState_M4); // set State=<M4> tlv8.val(kTLVType_State,pairState_M4); // set State=<M4>
srp.loadTLV(kTLVType_Proof,&srp.M2); // load M2 counter-proof srp.loadTLV(kTLVType_Proof,&srp.M2,64); // load M2 counter-proof
tlvRespond(); // send response to client tlvRespond(); // send response to client
pairStatus=pairState_M5; // set next expected pair-state request from client pairStatus=pairState_M5; // set next expected pair-state request from client
@ -1635,6 +1655,7 @@ TLV<kTLVType,10> HAPClient::tlv8;
nvs_handle HAPClient::hapNVS; nvs_handle HAPClient::hapNVS;
nvs_handle HAPClient::wifiNVS; nvs_handle HAPClient::wifiNVS;
nvs_handle HAPClient::srpNVS; nvs_handle HAPClient::srpNVS;
nvs_handle HAPClient::otaNVS;
uint8_t HAPClient::httpBuf[MAX_HTTP+1]; uint8_t HAPClient::httpBuf[MAX_HTTP+1];
HKDF HAPClient::hkdf; HKDF HAPClient::hkdf;
pairState HAPClient::pairStatus; pairState HAPClient::pairStatus;

View File

@ -82,6 +82,7 @@ struct HAPClient {
static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data static nvs_handle hapNVS; // handle for non-volatile-storage of HAP data
static nvs_handle wifiNVS; // handle for non-volatile-storage of WiFi data static nvs_handle wifiNVS; // handle for non-volatile-storage of WiFi data
static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data static nvs_handle srpNVS; // handle for non-volatile-storage of SRP data
static nvs_handle otaNVS; // handle for non-volatile-storage of OTA data
static uint8_t httpBuf[MAX_HTTP+1]; // buffer to store HTTP messages (+1 to leave room for storing an extra 'overflow' character) static uint8_t httpBuf[MAX_HTTP+1]; // buffer to store HTTP messages (+1 to leave room for storing an extra 'overflow' character)
static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string static HKDF hkdf; // generates (and stores) HKDF-SHA-512 32-byte keys derived from an inputKey of arbitrary length, a salt string, and an info string
static pairState pairStatus; // tracks pair-setup status static pairState pairStatus; // tracks pair-setup status

View File

@ -29,6 +29,8 @@
#include <nvs_flash.h> #include <nvs_flash.h>
#include <sodium.h> #include <sodium.h>
#include <WiFi.h> #include <WiFi.h>
#include <ArduinoOTA.h>
#include <esp_ota_ops.h>
#include "HomeSpan.h" #include "HomeSpan.h"
#include "HAP.h" #include "HAP.h"
@ -52,10 +54,16 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
controlButton.init(controlPin); controlButton.init(controlPin);
statusLED.init(statusPin); statusLED.init(statusPin);
int maxLimit=CONFIG_LWIP_MAX_SOCKETS-2-otaEnabled;
if(maxConnections>maxLimit)
maxConnections=maxLimit;
hap=(HAPClient **)calloc(maxConnections,sizeof(HAPClient *)); hap=(HAPClient **)calloc(maxConnections,sizeof(HAPClient *));
for(int i=0;i<maxConnections;i++) for(int i=0;i<maxConnections;i++)
hap[i]=new HAPClient; hap[i]=new HAPClient;
hapServer=new WiFiServer(tcpPortNum);
delay(2000); delay(2000);
Serial.print("\n************************************************************\n" Serial.print("\n************************************************************\n"
@ -69,11 +77,19 @@ void Span::begin(Category catID, const char *displayName, const char *hostNameBa
Serial.print("\nStatus LED: Pin "); Serial.print("\nStatus LED: Pin ");
Serial.print(statusPin); Serial.print(statusPin);
Serial.print("\nDevice Control: Pin "); Serial.print("\nDevice Control: Pin ");
Serial.print(controlPin); Serial.print(controlPin);
Serial.print("\nSketch Version: ");
Serial.print(getSketchVersion());
Serial.print("\nHomeSpan Version: "); Serial.print("\nHomeSpan Version: ");
Serial.print(HOMESPAN_VERSION); Serial.print(HOMESPAN_VERSION);
Serial.print("\nESP-IDF Version: "); Serial.print("\nESP-IDF Version: ");
Serial.print(esp_get_idf_version()); Serial.print(esp_get_idf_version());
#ifdef ARDUINO_VARIANT
Serial.print("\nESP32 Board: ");
Serial.print(ARDUINO_VARIANT);
#endif
Serial.print("\nSketch Compiled: "); Serial.print("\nSketch Compiled: ");
Serial.print(__DATE__); Serial.print(__DATE__);
Serial.print(" "); Serial.print(" ");
@ -125,7 +141,6 @@ void Span::poll() {
statusLED.start(LED_WIFI_NEEDED); statusLED.start(LED_WIFI_NEEDED);
} else { } else {
homeSpan.statusLED.start(LED_WIFI_CONNECTING); homeSpan.statusLED.start(LED_WIFI_CONNECTING);
hapServer=new WiFiServer(tcpPortNum,maxConnections);
} }
controlButton.reset(); controlButton.reset();
@ -149,7 +164,7 @@ void Span::poll() {
WiFiClient newClient; WiFiClient newClient;
if(hapServer && (newClient=hapServer->available())){ // found a new HTTP client if(newClient=hapServer->available()){ // found a new HTTP client
int freeSlot=getFreeSlot(); // get next free slot int freeSlot=getFreeSlot(); // get next free slot
if(freeSlot==-1){ // no available free slots if(freeSlot==-1){ // no available free slots
@ -174,6 +189,10 @@ void Span::poll() {
LOG1(millis()/1000); LOG1(millis()/1000);
LOG1(" sec) "); LOG1(" sec) ");
LOG1(hap[freeSlot]->client.remoteIP()); LOG1(hap[freeSlot]->client.remoteIP());
LOG1(" on Socket ");
LOG1(hap[freeSlot]->client.fd()-LWIP_SOCKET_OFFSET+1);
LOG1("/");
LOG1(CONFIG_LWIP_MAX_SOCKETS);
LOG1("\n"); LOG1("\n");
LOG2("\n"); LOG2("\n");
@ -207,6 +226,9 @@ void Span::poll() {
HAPClient::checkNotifications(); HAPClient::checkNotifications();
HAPClient::checkTimedWrites(); HAPClient::checkTimedWrites();
if(otaEnabled)
ArduinoOTA.handle();
if(controlButton.primed()){ if(controlButton.primed()){
statusLED.start(LED_ALERT); statusLED.start(LED_ALERT);
} }
@ -376,6 +398,16 @@ void Span::checkConnect(){
else else
sprintf(hostName,"%s%s",hostNameBase,hostNameSuffix); sprintf(hostName,"%s%s",hostNameBase,hostNameSuffix);
char d[strlen(hostName)+1];
sscanf(hostName,"%[A-Za-z0-9-]",d);
if(strlen(hostName)>255|| hostName[0]=='-' || hostName[strlen(hostName)-1]=='-' || strlen(hostName)!=strlen(d)){
Serial.printf("\n*** Error: Can't start MDNS due to invalid hostname '%s'.\n",hostName);
Serial.print("*** Hostname must consist of 255 or less alphanumeric characters or a hyphen, except that the hyphen cannot be the first or last character.\n");
Serial.print("*** PROGRAM HALTED!\n\n");
while(1);
}
Serial.print("\nStarting MDNS...\n\n"); Serial.print("\nStarting MDNS...\n\n");
Serial.print("HostName: "); Serial.print("HostName: ");
Serial.print(hostName); Serial.print(hostName);
@ -385,7 +417,9 @@ void Span::checkConnect(){
Serial.print(displayName); Serial.print(displayName);
Serial.print("\nModel Name: "); Serial.print("\nModel Name: ");
Serial.print(modelName); Serial.print(modelName);
Serial.print("\n"); Serial.print("\nSetup ID: ");
Serial.print(qrID);
Serial.print("\n\n");
MDNS.begin(hostName); // set server host name (.local implied) MDNS.begin(hostName); // set server host name (.local implied)
MDNS.setInstanceName(displayName); // set server display name MDNS.setInstanceName(displayName); // set server display name
@ -410,6 +444,10 @@ void Span::checkConnect(){
else else
mdns_service_txt_item_set("_hap","_tcp","sf","0"); // set Status Flag = 0 mdns_service_txt_item_set("_hap","_tcp","sf","0"); // set Status Flag = 0
mdns_service_txt_item_set("_hap","_tcp","hspn",HOMESPAN_VERSION); // HomeSpan Version Number (info only - NOT used by HAP)
mdns_service_txt_item_set("_hap","_tcp","sketch",sketchVersion); // Sketch Version (info only - NOT used by HAP)
mdns_service_txt_item_set("_hap","_tcp","ota",otaEnabled?"yes":"no"); // OTA Enabled (info only - NOT used by HAP)
uint8_t hashInput[22]; uint8_t hashInput[22];
uint8_t hashOutput[64]; uint8_t hashOutput[64];
char setupHash[9]; char setupHash[9];
@ -421,17 +459,67 @@ void Span::checkConnect(){
mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash mbedtls_base64_encode((uint8_t *)setupHash,9,&len,hashOutput,4); // Step 3: Encode the first 4 bytes of hashOutput in base64, which results in an 8-character, null-terminated, setupHash
mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash mdns_service_txt_item_set("_hap","_tcp","sh",setupHash); // Step 4: broadcast the resulting Setup Hash
Serial.print("\nStarting Web (HTTP) Server supporting up to "); if(otaEnabled){
if(esp_ota_get_running_partition()!=esp_ota_get_next_update_partition(NULL)){
ArduinoOTA.setHostname(hostName);
if(otaAuth)
ArduinoOTA.setPasswordHash(otaPwd);
ArduinoOTA
.onStart([]() {
String type;
if (ArduinoOTA.getCommand() == U_FLASH)
type = "sketch";
else // U_SPIFFS
type = "filesystem";
Serial.println("\n*** OTA Starting:" + type);
homeSpan.statusLED.start(LED_OTA_STARTED);
})
.onEnd([]() {
Serial.println("\n*** OTA Completed. Rebooting...");
homeSpan.statusLED.off();
})
.onProgress([](unsigned int progress, unsigned int total) {
Serial.printf("*** Progress: %u%%\r", (progress / (total / 100)));
})
.onError([](ota_error_t error) {
Serial.printf("*** OTA Error[%u]: ", error);
if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed\n");
else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed\n");
else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed\n");
else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed\n");
else if (error == OTA_END_ERROR) Serial.println("End Failed\n");
});
ArduinoOTA.begin();
Serial.print("Starting OTA Server: ");
Serial.print(displayName);
Serial.print(" at ");
Serial.print(WiFi.localIP());
Serial.print("\nAuthorization Password: ");
Serial.print(otaAuth?"Enabled\n\n":"DISABLED!\n\n");
} else {
Serial.print("\n*** Warning: Can't start OTA Server - Partition table used to compile this sketch is not configured for OTA.\n\n");
}
}
Serial.print("Starting Web (HTTP) Server supporting up to ");
Serial.print(maxConnections); Serial.print(maxConnections);
Serial.print(" simultaneous connections...\n\n"); Serial.print(" simultaneous connections...\n");
hapServer->begin(); hapServer->begin();
Serial.print("\n");
if(!HAPClient::nAdminControllers()){ if(!HAPClient::nAdminControllers()){
Serial.print("DEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n"); Serial.print("DEVICE NOT YET PAIRED -- PLEASE PAIR WITH HOMEKIT APP\n\n");
statusLED.start(LED_PAIRING_NEEDED); statusLED.start(LED_PAIRING_NEEDED);
} else { } else {
statusLED.on(); statusLED.on();
} }
if(wifiCallback)
wifiCallback();
} // initWiFi } // initWiFi
@ -443,7 +531,7 @@ void Span::setQRID(const char *id){
sscanf(id,"%4[0-9A-Za-z]",tBuf); sscanf(id,"%4[0-9A-Za-z]",tBuf);
if(strlen(id)==4 && strlen(tBuf)==4){ if(strlen(id)==4 && strlen(tBuf)==4){
qrID=id; sprintf(qrID,"%s",id);
} }
} // setQRID } // setQRID
@ -477,14 +565,17 @@ void Span::processSerialCommand(const char *c){
if(hap[i]->client){ if(hap[i]->client){
Serial.print(hap[i]->client.remoteIP()); Serial.print(hap[i]->client.remoteIP());
Serial.print(" "); Serial.print(" on Socket ");
Serial.print(hap[i]->client.fd()-LWIP_SOCKET_OFFSET+1);
Serial.print("/");
Serial.print(CONFIG_LWIP_MAX_SOCKETS);
if(hap[i]->cPair){ if(hap[i]->cPair){
Serial.print("ID="); Serial.print(" ID=");
HAPClient::charPrintRow(hap[i]->cPair->ID,36); HAPClient::charPrintRow(hap[i]->cPair->ID,36);
Serial.print(hap[i]->cPair->admin?" (admin)":" (regular)"); Serial.print(hap[i]->cPair->admin?" (admin)":" (regular)");
} else { } else {
Serial.print("(unverified)"); Serial.print(" (unverified)");
} }
} else { } else {
@ -494,7 +585,7 @@ void Span::processSerialCommand(const char *c){
Serial.print("\n"); Serial.print("\n");
} }
Serial.print("\n*** End Status ***\n"); Serial.print("\n*** End Status ***\n\n");
} }
break; break;
@ -513,6 +604,61 @@ void Span::processSerialCommand(const char *c){
} }
break; break;
case 'Q': {
char tBuf[5];
const char *s=c+1+strspn(c+1," ");
sscanf(s," %4[0-9A-Za-z]",tBuf);
if(strlen(s)==4 && strlen(tBuf)==4){
sprintf(qrID,"%s",tBuf);
Serial.print("\nChanging default Setup ID for QR Code to: '");
Serial.print(qrID);
Serial.print("'. Will take effect after next restart.\n\n");
nvs_set_str(HAPClient::hapNVS,"SETUPID",qrID); // update data
nvs_commit(HAPClient::hapNVS);
} else {
Serial.print("\n*** Invalid request to change Setup ID for QR Code to: '");
Serial.print(s);
Serial.print("'. Setup ID must be exactly 4 alphanumeric characters (0-9, A-Z, and a-z).\n\n");
}
}
break;
case 'O': {
char textPwd[34]="\0";
Serial.print("\n>>> New OTA Password, or <return> to cancel request: ");
readSerial(textPwd,33);
if(strlen(textPwd)==0){
Serial.print("(cancelled)\n\n");
return;
}
if(strlen(textPwd)==33){
Serial.print("\n*** Sorry, 32 character limit - request cancelled\n\n");
return;
}
Serial.print(mask(textPwd,2));
Serial.print("\n");
MD5Builder otaPwdHash;
otaPwdHash.begin();
otaPwdHash.add(textPwd);
otaPwdHash.calculate();
otaPwdHash.getChars(otaPwd);
nvs_set_str(HAPClient::otaNVS,"OTADATA",otaPwd); // update data
nvs_commit(HAPClient::otaNVS);
Serial.print("... Accepted! Password change will take effect after next restart.\n");
if(!otaEnabled)
Serial.print("... Note: OTA has not been enabled in this sketch.\n");
Serial.print("\n");
}
break;
case 'S': { case 'S': {
char buf[128]; char buf[128];
@ -695,20 +841,24 @@ void Span::processSerialCommand(const char *c){
Serial.print("\n\n"); Serial.print("\n\n");
char d[]="------------------------------"; char d[]="------------------------------";
char cBuf[256]; Serial.printf("%-30s %s %10s %s %s %s %s %s\n","Service","Type","AID","IID","Update","Loop","Button","Linked Services");
sprintf(cBuf,"%-30s %s %10s %s %s %s %s\n","Service","Type","AID","IID","Update","Loop","Button"); Serial.printf("%.30s %.4s %.10s %.3s %.6s %.4s %.6s %.15s\n",d,d,d,d,d,d,d,d);
Serial.print(cBuf);
sprintf(cBuf,"%.30s %.4s %.10s %.3s %.6s %.4s %.6s\n",d,d,d,d,d,d,d);
Serial.print(cBuf);
for(int i=0;i<Accessories.size();i++){ // identify all services with over-ridden loop() methods 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++){ for(int j=0;j<Accessories[i]->Services.size();j++){
SpanService *s=Accessories[i]->Services[j]; SpanService *s=Accessories[i]->Services[j];
sprintf(cBuf,"%-30s %4s %10u %3d %6s %4s %6s\n",s->hapName,s->type,Accessories[i]->aid,s->iid, Serial.printf("%-30s %4s %10u %3d %6s %4s %6s ",s->hapName,s->type,Accessories[i]->aid,s->iid,
(void(*)())(s->*(&SpanService::update))!=(void(*)())(&SpanService::update)?"YES":"NO", (void(*)())(s->*(&SpanService::update))!=(void(*)())(&SpanService::update)?"YES":"NO",
(void(*)())(s->*(&SpanService::loop))!=(void(*)())(&SpanService::loop)?"YES":"NO", (void(*)())(s->*(&SpanService::loop))!=(void(*)())(&SpanService::loop)?"YES":"NO",
(void(*)(int,boolean))(s->*(&SpanService::button))!=(void(*)(int,boolean))(&SpanService::button)?"YES":"NO" (void(*)(int,boolean))(s->*(&SpanService::button))!=(void(*)(int,boolean))(&SpanService::button)?"YES":"NO"
); );
Serial.print(cBuf); if(s->linkedServices.empty())
Serial.print("-");
for(int k=0;k<s->linkedServices.size();k++){
Serial.print(s->linkedServices[k]->iid);
if(k<s->linkedServices.size()-1)
Serial.print(",");
}
Serial.print("\n");
} }
} }
Serial.print("\n*** End Info ***\n"); Serial.print("\n*** End Info ***\n");
@ -725,6 +875,8 @@ void Span::processSerialCommand(const char *c){
Serial.print(" W - configure WiFi Credentials and restart\n"); Serial.print(" W - configure WiFi Credentials and restart\n");
Serial.print(" X - delete WiFi Credentials and restart\n"); Serial.print(" X - delete WiFi Credentials and restart\n");
Serial.print(" S <code> - change the HomeKit Pairing Setup Code to <code>\n"); Serial.print(" S <code> - change the HomeKit Pairing Setup Code to <code>\n");
Serial.print(" Q <id> - change the HomeKit Setup ID for QR Codes to <id>\n");
Serial.print(" O - change the OTA password\n");
Serial.print(" A - start the HomeSpan Setup Access Point\n"); Serial.print(" A - start the HomeSpan Setup Access Point\n");
Serial.print("\n"); Serial.print("\n");
Serial.print(" U - unpair device by deleting all Controller data\n"); Serial.print(" U - unpair device by deleting all Controller data\n");
@ -1240,6 +1392,13 @@ SpanService *SpanService::setHidden(){
/////////////////////////////// ///////////////////////////////
SpanService *SpanService::addLink(SpanService *svc){
linkedServices.push_back(svc);
return(this);
}
///////////////////////////////
int SpanService::sprintfAttributes(char *cBuf){ int SpanService::sprintfAttributes(char *cBuf){
int nBytes=0; int nBytes=0;
@ -1250,6 +1409,16 @@ int SpanService::sprintfAttributes(char *cBuf){
if(primary) if(primary)
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"primary\":true,"); nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"primary\":true,");
if(!linkedServices.empty()){
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"linked\":[");
for(int i=0;i<linkedServices.size();i++){
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"%d",linkedServices[i]->iid);
if(i+1<linkedServices.size())
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,",");
}
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"],");
}
nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"characteristics\":["); nBytes+=snprintf(cBuf?(cBuf+nBytes):NULL,cBuf?64:0,"\"characteristics\":[");

View File

@ -87,6 +87,7 @@ struct Span{
String configLog; // log of configuration process, including any errors String configLog; // log of configuration process, including any errors
boolean isBridge=true; // flag indicating whether device is configured as a bridge (i.e. first Accessory contains nothing but AccessoryInformation and HAPProtocolInformation) boolean isBridge=true; // flag indicating whether device is configured as a bridge (i.e. first Accessory contains nothing but AccessoryInformation and HAPProtocolInformation)
HapQR qrCode; // optional QR Code to use for pairing HapQR qrCode; // optional QR Code to use for pairing
const char *sketchVersion="n/a"; // version of the sketch
boolean connected=false; // WiFi connection status boolean connected=false; // WiFi connection status
unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts unsigned long waitTime=60000; // time to wait (in milliseconds) between WiFi connection attempts
@ -99,7 +100,11 @@ struct Span{
uint8_t maxConnections=DEFAULT_MAX_CONNECTIONS; // number of simultaneous HAP connections uint8_t maxConnections=DEFAULT_MAX_CONNECTIONS; // number of simultaneous HAP connections
unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations unsigned long comModeLife=DEFAULT_COMMAND_TIMEOUT*1000; // length of time (in milliseconds) to keep Command Mode alive before resuming normal operations
uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan uint16_t tcpPortNum=DEFAULT_TCP_PORT; // port for TCP communications between HomeKit and HomeSpan
const char *qrID=DEFAULT_QR_ID; // optional Setup ID used to pair with QR Code char qrID[5]=""; // Setup ID used for pairing with QR Code
boolean otaEnabled=false; // enables Over-the-Air ("OTA") updates
char otaPwd[33]; // MD5 Hash of OTA password, represented as a string of hexidecimal characters
boolean otaAuth; // OTA requires password when set to true
void (*wifiCallback)()=NULL; // optional callback function to invoke once WiFi connectivity is established
WiFiServer *hapServer; // pointer to the HAP Server connection WiFiServer *hapServer; // pointer to the HAP Server connection
Blinker statusLED; // indicates HomeSpan status Blinker statusLED; // indicates HomeSpan status
@ -150,6 +155,10 @@ struct Span{
void setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;} // sets the hostName suffix to be used instead of the 6-byte AccessoryID void setHostNameSuffix(const char *suffix){hostNameSuffix=suffix;} // sets the hostName suffix to be used instead of the 6-byte AccessoryID
void setPortNum(uint16_t port){tcpPortNum=port;} // sets the TCP port number to use for communications between HomeKit and HomeSpan void setPortNum(uint16_t port){tcpPortNum=port;} // sets the TCP port number to use for communications between HomeKit and HomeSpan
void setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code void setQRID(const char *id); // sets the Setup ID for optional pairing with a QR Code
void enableOTA(boolean auth=true){otaEnabled=true;otaAuth=auth;} // enables Over-the-Air updates, with (auth=true) or without (auth=false) authorization password
void setSketchVersion(const char *sVer){sketchVersion=sVer;} // set optional sketch version number
const char *getSketchVersion(){return sketchVersion;} // get sketch version number
void setWifiCallback(void (*f)()){wifiCallback=f;} // sets an optional user-defined function to call once WiFi connectivity is established
}; };
/////////////////////////////// ///////////////////////////////
@ -178,11 +187,13 @@ struct SpanService{
vector<SpanCharacteristic *> Characteristics; // vector of pointers to all Characteristics in this Service vector<SpanCharacteristic *> Characteristics; // vector of pointers to all Characteristics in this Service
vector<HapCharType *> req; // vector of pointers to all required HAP Characteristic Types for this Service vector<HapCharType *> req; // vector of pointers to all required HAP Characteristic Types for this Service
vector<HapCharType *> opt; // vector of pointers to all optional HAP Characteristic Types for this Service vector<HapCharType *> opt; // vector of pointers to all optional HAP Characteristic Types for this Service
vector<SpanService *> linkedServices; // vector of pointers to any optional linked Services
SpanService(const char *type, const char *hapName); SpanService(const char *type, const char *hapName);
SpanService *setPrimary(); // sets the Service Type to be primary and returns pointer to self 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 *setHidden(); // sets the Service Type to be hidden and returns pointer to self
SpanService *addLink(SpanService *svc); // adds svc as a Linked Service
int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator int sprintfAttributes(char *cBuf); // prints Service JSON records into buf; return number of characters printed, excluding null terminator
void validate(); // error-checks Service void validate(); // error-checks Service

View File

@ -238,9 +238,8 @@ void SRP6A::createProof(){
////////////////////////////////////// //////////////////////////////////////
int SRP6A::loadTLV(kTLVType tag, mbedtls_mpi *mpi){ int SRP6A::loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes){
int nBytes=mbedtls_mpi_size(mpi);
uint8_t *buf=HAPClient::tlv8.buf(tag,nBytes); uint8_t *buf=HAPClient::tlv8.buf(tag,nBytes);
if(!buf) if(!buf)

View File

@ -81,8 +81,9 @@ struct SRP6A {
void createPublicKey(); // computes x, v, and B from random s, P, and b void createPublicKey(); // computes x, v, and B from random s, P, and b
void createSessionKey(); // computes u from A and B, and then S from A, v, u, and b void createSessionKey(); // computes u from A and B, and then S from A, v, u, and b
int loadTLV(kTLVType tag, mbedtls_mpi *mpi); // load binary contents of mpi into a TLV record and set its length int loadTLV(kTLVType tag, mbedtls_mpi *mpi, int nBytes); // load binary contents of mpi into a TLV record and set its length
int writeTLV(kTLVType tag, mbedtls_mpi *mpi); // write binary contents of a TLV record into an mpi int writeTLV(kTLVType tag, mbedtls_mpi *mpi); // write binary contents of a TLV record into an mpi
int verifyProof(); // verify M1 SRP6A Proof received from HAP client (return 1 on success, 0 on failure) int verifyProof(); // verify M1 SRP6A Proof received from HAP client (return 1 on success, 0 on failure)
void createProof(); // create M2 server-side SRP6A Proof based on M1 as received from HAP Client void createProof(); // create M2 server-side SRP6A Proof based on M1 as received from HAP Client

View File

@ -33,8 +33,8 @@
// HomeSpan Version // // HomeSpan Version //
#define HS_MAJOR 1 #define HS_MAJOR 1
#define HS_MINOR 1 #define HS_MINOR 2
#define HS_PATCH 4 #define HS_PATCH 0
#define STRINGIFY(x) _STR(x) #define STRINGIFY(x) _STR(x)
#define _STR(x) #x #define _STR(x) #x
@ -68,6 +68,7 @@
#define DEFAULT_AP_SSID "HomeSpan-Setup" // change with homeSpan.setApSSID(ssid) #define DEFAULT_AP_SSID "HomeSpan-Setup" // change with homeSpan.setApSSID(ssid)
#define DEFAULT_AP_PASSWORD "homespan" // change with homeSpan.setApPassword(pwd) #define DEFAULT_AP_PASSWORD "homespan" // change with homeSpan.setApPassword(pwd)
#define DEFAULT_OTA_PASSWORD "homespan-ota" // change with 'O' command
#define DEFAULT_AP_TIMEOUT 300 // change with homeSpan.setApTimeout(nSeconds) #define DEFAULT_AP_TIMEOUT 300 // change with homeSpan.setApTimeout(nSeconds)
#define DEFAULT_COMMAND_TIMEOUT 120 // change with homeSpan.setCommandTimeout(nSeconds) #define DEFAULT_COMMAND_TIMEOUT 120 // change with homeSpan.setCommandTimeout(nSeconds)
@ -87,6 +88,7 @@
#define LED_WIFI_CONNECTING 2000 // slow flashing #define LED_WIFI_CONNECTING 2000 // slow flashing
#define LED_AP_STARTED 100,0.5,2,300 // rapid double-blink #define LED_AP_STARTED 100,0.5,2,300 // rapid double-blink
#define LED_AP_CONNECTED 300,0.5,2,400 // medium double-blink #define LED_AP_CONNECTED 300,0.5,2,400 // medium double-blink
#define LED_OTA_STARTED 300,0.5,3,400 // medium triple-blink
///////////////////////////////////////////////////// /////////////////////////////////////////////////////
// Message Log Level Control Macros // // Message Log Level Control Macros //

View File

@ -215,30 +215,6 @@ void TLV<tagType, maxTags>::print(){
} // loop over all TLVs } // loop over all TLVs
} }
//////////////////////////////////////
// TLV pack_old(buf)
template<class tagType, int maxTags>
int TLV<tagType, maxTags>::pack_old(uint8_t *buf){
int n=0;
for(int i=0;i<numTags;i++){
if(tlv[i].len>0){
*buf++=tlv[i].tag;
*buf++=tlv[i].len;
memcpy(buf,tlv[i].val,tlv[i].len);
buf+=tlv[i].len;
n+=tlv[i].len+2;
} // len>0
} // loop over all TLVs
return(n);
}
////////////////////////////////////// //////////////////////////////////////
// TLV pack(tlvBuf) // TLV pack(tlvBuf)

View File

@ -10,11 +10,15 @@ void setup() {
homeSpan.setLogLevel(1); homeSpan.setLogLevel(1);
homeSpan.setHostNameSuffix(""); homeSpan.setHostNameSuffix("-lamp1");
homeSpan.setPortNum(1200); homeSpan.setPortNum(1201);
homeSpan.setQRID("One1"); // homeSpan.setMaxConnections(6);
// homeSpan.setQRID("One1");
homeSpan.enableOTA();
homeSpan.setSketchVersion("Test 1.3.1");
homeSpan.setWifiCallback(wifiEstablished);
homeSpan.begin(Category::Lighting,"HomeSpanTest"); homeSpan.begin(Category::Lighting,"HomeSpan Lamp Server","homespan");
new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments
@ -29,8 +33,17 @@ void setup() {
new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service
new Characteristic::Version("1.1.0"); // Set the Version Characteristic to "1.1.0" as required by HAP new Characteristic::Version("1.1.0"); // Set the Version Characteristic to "1.1.0" as required by HAP
new Service::LightBulb(); // Create the Light Bulb Service new Service::LightBulb();
new Characteristic::On(); // This Service requires the "On" Characteristic to turn the light on and off new Characteristic::On();
new Characteristic::Brightness();
new Characteristic::Name("Light 1");
new Service::LightBulb();
new Characteristic::On();
new Characteristic::Brightness();
new Characteristic::Name("Light 2");
(new Service::Switch())->setPrimary();
new Characteristic::On();
new Characteristic::Name("Switch 3");
} // end of setup() } // end of setup()
@ -39,5 +52,11 @@ void setup() {
void loop(){ void loop(){
homeSpan.poll(); homeSpan.poll();
} // end of loop() } // end of loop()
//////////////////////////////////////
void wifiEstablished(){
Serial.print("IN CALLBACK FUNCTION\n\n");
}