├── DEV_FAN.h ├── DEV_LED.h ├── LICENSE ├── My_Home.ino └── README.md /DEV_FAN.h: -------------------------------------------------------------------------------- 1 | struct DEV_FAN : Service::Fan { // First we create a derived class from the HomeSpan LightBulb Service 2 | 3 | int fanPin; // this variable stores the pin number defined for this LED 4 | SpanCharacteristic *power; // here we create a generic pointer to a SpanCharacteristic named "power" that we will use below 5 | 6 | // Next we define the constructor for DEV_LED. Note that it takes one argument, ledPin, 7 | // which specifies the pin to which the LED is attached. 8 | 9 | DEV_FAN(int fanPin) : Service::Fan(){ 10 | 11 | power=new Characteristic::Active(); // this is where we create the On Characterstic we had previously defined in setup(). Save this in the pointer created above, for use below 12 | this->fanPin=fanPin; // don't forget to store ledPin... 13 | pinMode(fanPin,OUTPUT); // ...and set the mode for ledPin to be an OUTPUT (standard Arduino function) 14 | 15 | } // end constructor 16 | 17 | // Finally, we over-ride the default update() method with instructions that actually turn on/off the LED. Note update() returns type boolean 18 | 19 | boolean update(){ 20 | 21 | digitalWrite(fanPin,power->getNewVal()); // use a standard Arduino function to turn on/off ledPin based on the return of a call to power->getNewVal() (see below for more info) 22 | 23 | return(true); // return true to indicate the update was successful (otherwise create code to return false if some reason you could not turn on the LED) 24 | 25 | } // update 26 | }; 27 | -------------------------------------------------------------------------------- /DEV_LED.h: -------------------------------------------------------------------------------- 1 | 2 | //////////////////////////////////// 3 | // DEVICE-SPECIFIC LED SERVICES // 4 | //////////////////////////////////// 5 | 6 | // HERE'S WHERE WE DEFINE OUR NEW LED SERVICE! 7 | 8 | struct DEV_LED : Service::LightBulb { // First we create a derived class from the HomeSpan LightBulb Service 9 | 10 | int ledPin; // this variable stores the pin number defined for this LED 11 | SpanCharacteristic *power; // here we create a generic pointer to a SpanCharacteristic named "power" that we will use below 12 | 13 | // Next we define the constructor for DEV_LED. Note that it takes one argument, ledPin, 14 | // which specifies the pin to which the LED is attached. 15 | 16 | DEV_LED(int ledPin) : Service::LightBulb(){ 17 | 18 | power=new Characteristic::On(); // this is where we create the On Characterstic we had previously defined in setup(). Save this in the pointer created above, for use below 19 | this->ledPin=ledPin; // don't forget to store ledPin... 20 | pinMode(ledPin,OUTPUT); // ...and set the mode for ledPin to be an OUTPUT (standard Arduino function) 21 | 22 | } // end constructor 23 | 24 | // Finally, we over-ride the default update() method with instructions that actually turn on/off the LED. Note update() returns type boolean 25 | 26 | boolean update(){ 27 | 28 | digitalWrite(ledPin,power->getNewVal()); // use a standard Arduino function to turn on/off ledPin based on the return of a call to power->getNewVal() (see below for more info) 29 | 30 | return(true); // return true to indicate the update was successful (otherwise create code to return false if some reason you could not turn on the LED) 31 | 32 | } // update 33 | }; 34 | 35 | ////////////////////////////////// 36 | 37 | // HOW update() WORKS: 38 | // ------------------ 39 | // 40 | // Whenever a HomeKit controller requests HomeSpan to update a Characteristic, HomeSpan calls the update() method for the SERVICE that contains the 41 | // Characteristic. It calls this only one time, even if multiple Characteristics updates are requested for that Service. For example, if you 42 | // direct HomeKit to turn on a light and set it to 50% brightness, it will send HomeSpan two requests: one to update the "On" Characteristic of the 43 | // LightBulb Service from "false" to "true" and another to update the "Brightness" Characteristic of that same Service to 50. This is VERY inefficient 44 | // and would require the user to process multiple updates to the same Service. 45 | // 46 | // Instead, HomeSpan combines both requests into a single call to update() for the Service itself, where you can process all of the Characteristics 47 | // that change at the same time. In the example above, we only have a single Characteristic to deal with, so this does not mean much. But in later 48 | // examples we'll see how this works with multiple Characteristics. 49 | 50 | // HOW TO ACCESS A CHARACTERISTIC'S NEW AND CURRENT VALUES 51 | // ------------------------------------------------------- 52 | // 53 | // HomeSpan stores the values for its Characteristics in a union structure that allows for different types, such as floats, booleans, etc. The specific 54 | // types are defined by HAP for each Characteristic. Looking up whether a Characteristic is a uint8 or uint16 can be tiresome, so HomeSpan abstracts 55 | // all these details. Since C++ adheres to strict variable typing, this is done through the use of template methods. Every Characteristic supports 56 | // the following two methods: 57 | // 58 | // getVal() - returns the CURRENT value of the Characterisic, after casting into "type" 59 | // getNewVal() - returns the NEW value (i.e. to be updated) of the Characteritic, after casting into "type" 60 | // 61 | // For example, MyChar->getVal() returns the current value of SpanCharacterstic MyChar as an int, REGARDLESS of how the value is stored by HomeSpan. 62 | // Similarly, MyChar->getVal() returns a value as a double, even it is stored as as a boolean (in which case you'll either get 0.00 or 1.00). 63 | // Of course you need to make sure you understand the range of expected values so that you don't try to access a value stored as 2-byte int using getVal(). 64 | // But it's perfectly okay to use getVal() to access the value of a Characteristic that HAP insists on storing as a float, even though its range is 65 | // strictly between 0 and 100 in steps of 1. Knowing the range and step size is all you need to know in determining you can access this as an or even a . 66 | // 67 | // Because most Characteristic values can properly be cast into int, getVal and getNewVal both default to if the template parameter is not specified. 68 | // As you can see above, we retrieved the new value HomeKit requested for the On Characteristic that we named "power" by simply calling power->getNewVal(). 69 | // Since no template parameter is specified, getNewVal() will return an int. And since the On Characteristic is natively stored as a boolean, getNewVal() 70 | // will either return a 0 or a 1, depending on whether HomeKit is requesting the Characteristic to be turned off or on. 71 | // 72 | // You may also note that in the above example we needed to use getNewVal(), but did not use getVal() anywhere. This is because we know exactly what 73 | // to do if HomeKit requests an LED to be turned on or off. The current status of the LED (on or off) does not matter. In latter examples we will see 74 | // instances where the current state of the device DOES matter, and we will need to access both current and new values. 75 | // 76 | // Finally, there is one additional method for Characteristics that is not used above but will be in later examples: updated(). This method returns a 77 | // boolean indicating whether HomeKit has requested a Characteristic to be updated, which means that getNewVal() will contain the new value it wants to set 78 | // for that Characteristic. For a Service with only one Characteristic, as above, we don't need to ask if "power" was updated using power->updated() because 79 | // the fact the the update() method for the Service is being called means that HomeKit is requesting an update, and the only thing to update is "power". 80 | // But for Services with two or more Characteristics, update() can be called with a request to update only a subset of the Characteristics. We will 81 | // find good use for the updated() method in later, multi-Characteristic examples. 82 | 83 | // UNDER THE HOOD: WHAT THE RETURN CODE FOR UPDATE() DOES 84 | // ------------------------------------------------------ 85 | // 86 | // HomeKit requires each Characteristic to return a special HAP status code when an attempt to update its value is made. HomeSpan automatically takes care of 87 | // most of the errors, such as a Characteristic not being found, or a request to update a Characteristic that is read only. In these cases update() is never 88 | // even called. But if it is, HomeSpan needs to return a HAP status code for each of the Characteristics that were to be updated in that Service. 89 | // By returning "true" you tell HomeSpan that the newValues requested are okay and you've made the required updates to the physical device. Upon 90 | // receiving a true return value, HomeSpan updates the Characteristics themselves by copying the "newValue" data elements into the current "value" data elements. 91 | // HomeSpan then sends a message back to HomeKit with a HAP code representing "OK," which lets the Controller know that the new values it requested have been 92 | // sucessfully processed. At no point does HomeKit ask for, or allow, a data value to be sent back from HomeSpan indicating the data in a Characteristic. 93 | // When requesting an update, HomeKit simply expects a HAP status code of OK, or some other status code representing an error. To tell HomeSpan to send the Controller 94 | // an error code, indicating that you were not able to successfully process the update, simply have update() return a value of "false." HomeSpan converts a 95 | // return of "false" to the HAP status code representing "UNABLE," which will cause the Controller to show that the device is not responding. 96 | 97 | // There are very few reasons you should need to return "false" since so much checking is done in advance by either HomeSpan or HomeKit 98 | // itself. For instance, HomeKit does not allow you to use the Controller, or even Siri, to change the brightness of LightBulb to a value outside the 99 | // range of allowable values you specified. This means that any update() requests you receive should only contain newValue data elements that are in-range. 100 | // 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 FreelanceCoder-kih 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /My_Home.ino: -------------------------------------------------------------------------------- 1 | /********************************************************************************* 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Gregg E. Berman 5 | * 6 | * https://github.com/HomeSpan/HomeSpan 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all 16 | * copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | * 26 | ********************************************************************************/ 27 | 28 | //////////////////////////////////////////////////////////// 29 | // // 30 | // HomeSpan: A HomeKit implementation for the ESP32 // 31 | // ------------------------------------------------ // 32 | // // 33 | // Example 5: Two working on/off LEDs based on the // 34 | // LightBulb Service // 35 | // // 36 | //////////////////////////////////////////////////////////// 37 | 38 | 39 | #include "HomeSpan.h" 40 | #include "DEV_LED.h" 41 | #include "DEV_FAN.h" // NEW! Include this new file, DEV_LED.h, which will be fully explained below 42 | 43 | void setup() { 44 | 45 | // First light! Control an LED from HomeKit! 46 | 47 | // Example 5 expands on Example 2 by adding in the code needed to actually control LEDs connected to the ESP32 from HomeKit. 48 | // In Example 2 we built out all the functionality to create a "Tile" Acessories inside HomeKit that displayed an on/off light, but 49 | // these control did not actually operate anything on the ESP32. To operate actual devices HomeSpan needs to be programmed to 50 | // respond to "update" requests from HomeKit by performing some form of operation. 51 | 52 | // Though HomeKit itself sends "update" requests to individual Characteristics, this is not intuitive and leads to complex coding requirements 53 | // when a Service has more than one Characteristic, such as both "On" and "Brightness." To make this MUCH easier for the user, HomeSpan 54 | // uses a framework in which Services are updated instead of individual Characteristics. It does so by calling the update() method of 55 | // each Service with flags indicating all the Characteristics in that Service that HomeKit requested to update. The user simply 56 | // implements code to perform the actual operation, and returns either true or false if the update was successful. HomeSpan takes care of all 57 | // the underlying nuts and bolts. 58 | 59 | // Every Service defined in HomeKit, such as Service:LightBulb and Service:Fan (and even Service::AccessoryInformation) implements an update() 60 | // method that, as a default, does nothing but returns a value of true. To actually operate real devices you need to over-ride this default update() 61 | // method with your own code. The easiest way to do this is by creating a DERIVED class based on one of the built-in HomeSpan Services. 62 | // Within this derived class you can perform initial set-up routines (if needed), over-ride the update() method with your own code, and even create 63 | // any other methods or class-specific variables you need to fully operate complex devices. Most importantly, the derived class can take arguments 64 | // so that you can make them more generic, re-use them multiple times (as will be seen below), and convert them to standalone modules (also shown below). 65 | 66 | // All of the HomeKit Services implemented by HomeSpan can be found in the Services.h file. Any can be used as the parent for a derived Service. 67 | 68 | // We begin by repeating nearly the same code from Example 2, but with a few key changes. For ease of reading, all prior comments have been removed 69 | // from lines that simply repeat Example 2, and new comments have been added to explictly show the new code. 70 | 71 | Serial.begin(115200); 72 | 73 | homeSpan.begin(Category::Lighting,"HomeSpan LEDs"); 74 | 75 | new SpanAccessory(); 76 | 77 | new Service::AccessoryInformation(); 78 | new Characteristic::Name("LED #1"); 79 | new Characteristic::Manufacturer("HomeSpan"); 80 | new Characteristic::SerialNumber("123-ABC"); 81 | new Characteristic::Model("20mA LED"); 82 | new Characteristic::FirmwareRevision("0.9"); 83 | new Characteristic::Identify(); 84 | 85 | new Service::HAPProtocolInformation(); 86 | new Characteristic::Version("1.1.0"); 87 | 88 | // In Example 2 we instantiated a LightBulb Service and its "On" Characteristic here. We are now going to replace these two lines (by commenting them out)... 89 | 90 | // new Service::LightBulb(); 91 | // new Characteristic::On(); 92 | 93 | // ...with a single new line instantiating a new class we will call DEV_LED(): 94 | 95 | new DEV_LED(18); // this instantiates a new LED Service. Where is this defined? What happpened to Characteristic::On? Keep reading... 96 | 97 | // The full definition and code for DEV_LED is implemented in a separate file called "DEV_LED.h" that is specified using the #include at the top of this program. 98 | // The prefix DEV_ is not required but it's a helpful convention when naming all your device-specific Services. Note that DEV_LED will include all the required 99 | // Characterictics of the Service, so you DO NOT have to separately instantiate Characteristic::On --- everything HomeSpan needs for DEV_LED should be implemented 100 | // in DEV_LED itself (though it's not all that much). Finally, note that we created DEV_LED to take a single integer argument. If you guessed this is 101 | // the number of the Pin to which you have attached an LED, you'd be right. See DEV_LED.h for a complete explanation of how it works. 102 | 103 | new SpanAccessory(); 104 | 105 | new Service::AccessoryInformation(); 106 | new Characteristic::Name("LED #2"); 107 | new Characteristic::Manufacturer("HomeSpan"); 108 | new Characteristic::SerialNumber("123-ABC"); 109 | new Characteristic::Model("20mA LED"); 110 | new Characteristic::FirmwareRevision("0.9"); 111 | new Characteristic::Identify(); 112 | 113 | new Service::HAPProtocolInformation(); 114 | new Characteristic::Version("1.1.0"); 115 | 116 | // new Service::LightBulb(); // Same as above, this line is deleted... 117 | // new Characteristic::On(); // This line is also deleted... 118 | 119 | new DEV_LED(19); 120 | 121 | new SpanAccessory(); // Begin by creating a new Accessory using SpanAccessory(), which takes no arguments 122 | 123 | new Service::AccessoryInformation(); // HAP requires every Accessory to implement an AccessoryInformation Service, which has 6 required Characteristics 124 | new Characteristic::Name("My Ceiling Fan"); // Name of the Accessory, which shows up on the HomeKit "tiles", and should be unique across Accessories 125 | new Characteristic::Manufacturer("HomeSpan"); // Manufacturer of the Accessory (arbitrary text string, and can be the same for every Accessory) 126 | new Characteristic::SerialNumber("123-ABC"); // Serial Number of the Accessory (arbitrary text string, and can be the same for every Accessory) 127 | new Characteristic::Model("120-Volt Lamp"); // Model of the Accessory (arbitrary text string, and can be the same for every Accessory) 128 | new Characteristic::FirmwareRevision("0.9"); // Firmware of the Accessory (arbitrary text string, and can be the same for every Accessory) 129 | new Characteristic::Identify(); // Create the required Identify 130 | 131 | new Service::HAPProtocolInformation(); // Create the HAP Protcol Information Service 132 | new Characteristic::Version("1.1.0"); // ...and replaced with a single line that instantiates a second DEV_LED Service on Pin 17 133 | 134 | new DEV_FAN(32); 135 | } // end of setup() 136 | 137 | ////////////////////////////////////// 138 | 139 | void loop(){ 140 | 141 | homeSpan.poll(); 142 | 143 | } // end of loop() 144 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HomekitEsp32 2 | This is an implementation of the [HomeSpan](https://github.com/HomeSpan/HomeSpan) library. I only modified two examples and combined them into one project. 3 | 4 | ## Requirements 5 | 1. [Arduino software](https://www.arduino.cc/en/software) from the arduino website 6 | 2. [Arduino-Esp32 core](https://docs.espressif.com/projects/arduino-esp32/en/latest/installing.html) from EspressIf's website. See also the espressif [arduino-esp32](https://github.com/espressif/arduino-esp32#arduino-core-for-esp32-wifi-chip) github page 7 | 3. You can install the HomeSpan library directly from your Arduino IDE. 8 | 9 | ## Demonstratioon 10 | Watch the [YouTube video](https://www.youtube.com/watch?v=RR8mykLFI9E&t=159s) 11 | 12 | ## Setup Code 13 | The default setup code is 466-37-726. You can find the HomeSpan Command Line Interface [here](https://github.com/HomeSpan/HomeSpan/blob/master/docs/CLI.md) 14 | 15 | --------------------------------------------------------------------------------