From d8a8eedad4c8e7c2e0c7abf5f3726aa4ef28e2f5 Mon Sep 17 00:00:00 2001 From: Gregg Date: Fri, 7 Aug 2020 09:38:36 -0500 Subject: [PATCH] Completed Example 12 Demonstrates how to use ServiceType to set Primary Service as well as how to name individual Services. --- .../12-ServiceOptions/12-ServiceOptions.ino | 154 +++++++++++++----- 1 file changed, 112 insertions(+), 42 deletions(-) diff --git a/examples/Advanced/12-ServiceOptions/12-ServiceOptions.ino b/examples/Advanced/12-ServiceOptions/12-ServiceOptions.ino index 9c5f365..d569322 100644 --- a/examples/Advanced/12-ServiceOptions/12-ServiceOptions.ino +++ b/examples/Advanced/12-ServiceOptions/12-ServiceOptions.ino @@ -19,41 +19,45 @@ void setup() { // Every Accessory we define has at least two Services, one of which will always be the required Accessory Information Service (and // which we've conveniently wrapped into a derived Service we called DEV_Identify). The second is usually a Service that performs // some sort of actual operation, such as the LightBulb Service or the Fan Service. It's this second operative Service that creates - // the controls we see in the HomeKit iOS or MacOS application. These appear as a tile with a lightbulb control or fan control. - // - // But we've also created Accessories with more than two operational Service, such as our definition of a ceiling fan that includeds + // the controls we see in the HomeKit iOS or MacOS application. These appear as tiles with a lightbulb control or fan control. + + // We've also created Accessories with more than two operational Service, such as our definition of a ceiling fan that includes // BOTH a Fan Service and a LightBulb Service. The HomeKit application can display an Accessory with two or more operational Services - // in one of two ways, as detemined by the user within the HomeKit settings panel. One way is to display each Service as a separate - // tile, so that our ceiling fan would show up as one standalone lightbulb tile for the LightBulb Service and one standalone fan tile - // for the Fan Service. The second way is for HomeKit to display our ceiling fan as a single tile that shows both the fan control and - // the lightbulb control on a single screen once you click the ceiling fan tile. - // - // HomeSpan has no control over whether HomeKit displays our two ceiling fan Services as two separate tiles or a combined single tile. - // However, HomeSpan does have control over the icon used to display the ceiling fan in single-tile mode. Should it be a lightbulb or - // a fan? HomeKit determines which icon to show in a single tile based on what is considered the Primary Service of the Accessory. - // This can be set directly in HomeSpan for any given Service by specifying the ServiceType for a Service. ServiceType is an optional - // argument to the new Service constructor. As an optional argument, it therefore needs to placed last. There are three possible - // ServiceTypes: Regular, Primary, and Hidden, as defined in the HomeSpan library Settings.h file. ServiceType::Regular is used to - // specifiy a service that has no special settings. This is the default used if you don't specify any ServiceType, which means that in - // all our previous examples, ServiceType::Regular has been implied. - // - // Specifying ServiceType::Primary for a Service tells HomeSpan to set the "primary" attribute to "true" for that Service in the attribute - // database. HomeKit will use this information to set the icon for the tile. It wil also list that Service first once you click the tile - // to open the Accessory's controls. Specifying ServiceType::Hidden for a Service tells HomeSpan to set the "hidden" attribute to "true" - // for that Service. This signals HomeKit to completely hide the controls for this service in the HomeKit interface. We've not used - // this feature in any of our examples - it's generally only used when you need to create an internal Service to be used by other Services, - // but not by the actual end-user. - // + // in one of two ways: one way is to display each Service as a separate tile, so that our ceiling fan would show up as one standalone + // lightbulb tile for the LightBulb Service and one standalone fan tile for the Fan Service. The second way is for HomeKit to display + // our ceiling fan as a single tile that you click to open a new screen showing both the fan control and the lightbulb control side by side. + + // HomeSpan has no control over whether HomeKit displays multiple Services as separate tiles or as a single combined single tile. + // This is determined by the user from within the HomeKit iOS or MacOS application (the default is to use the combined tile mode). + // However, HomeSpan does have control over the which icon is used to display the ceiling fan in combined-tile mode. Should it be a + // lightbulb or a fan? + + // HomeKit determines which icon to show on the combined tile according to what is considered the Primary Service of the Accessory. + // This can be set directly in HomeSpan for any given Service by setting its "ServiceType". ServiceType is an optional argument to the + // Service object constructor. Note that as an optional argument, it needs to be placed last. + + // There are three possible ServiceTypes: Regular, Primary, and Hidden, as defined in the HomeSpan library Settings.h file. + // ServiceType::Regular is used to specifiy a service that has no special settings. This is the default used if you don't specify any + // ServiceType. In all our previous examples, ServiceType::Regular has been implied. + + // Specifying ServiceType::Primary for a Service tells HomeSpan to set the "primary" attribute for that Service to "true" in the attribute + // database. HomeKit will use this information to deermine which icon to display for a combined tile. HomeKit will also list the Primary Service + // first when you click a combined tile to open its controls. + + // Specifying ServiceType::Hidden for a Service tells HomeSpan to set the "hidden" attribute for that Service to "true". This tells HomeKit + // to completely hide the controls for this service in the HomeKit interface. We've not used this feature in any of our examples. It's generally + // only needed to create an "internal" Service to be used by other Services, but not by the actual end-user. + // As noted, ServiceType is an optional argument for the new Service constructor, so ServiceType::Primary can simply be added as the last - // argument whenever we create a new Service to indicate it's the Primary one for the Accessory. But this won't work for derived Services + // argument whenever we create a new Service to indicate it's the Primary one for the Accessory. But this won't work for the derived Services // we created, such as DEV_LED or DEV_DimmableLED. This is because when we created those derived classes we did not include an optional // ServiceType argument in the constructor. Doing so is very easy - you just need to modify the constructors, as shown for DEV_LED and // DEV_DimmableLED in the the DEV_LED.h file. We even modified the constructor for DEV_Identify in DEV_Identify.h so we can specify when - // it should be considered the Primary Service. There is a good use case for that as we'll see below. - // - // Once we've modified our constructors accordingly, we're ready to specify which Service are Primary in any given Accessory, as shown in the - // specific examples below. - + // it should be considered the Primary Service. There is a good use case for that as we'll see below. Please see DEV_Identify.h and DEV_LED.h + // for exactly how these new constructors are formulated. + + // Once we've modified our constructors accordingly, we're ready to specify which is the Primary Service for any given Accessory, as shown below. + // First, we need to initialize HomeSpan and define our Bridge Accessory as in the previous examples: Serial.begin(115200); @@ -64,15 +68,15 @@ void setup() { new Service::HAPProtocolInformation(); new Characteristic::Version("1.1.0"); - // Here we create two Accessories configured to control a Ceiling Fan containing a bi-directional, multi-speed fan AND a dimmable light. + // Next, we create two Accessories each configured to control a Ceiling Fan containing a bi-directional, multi-speed fan AND a dimmable light. // In Ceiling Fan #1 we specify the Dimmable LED as the Primary Service. In Ceiling Fan #2 we specify the Fan as the Primary Service. - // If you set HomeKit to display each of these Accessories as single tiles, you'll immediately see the difference. Ceiling Fan #1 shows as - // Lightbulb Tile, and the dimmable LED is the first listed when you open the controls. Ceiling Fan #2 shows as a Fan Tile, and the - // fan is the first listed when you open its controls. Nice and easy. + // If you set HomeKit to display each of these Accessories as combined tiles, you'll immediately see the difference. Ceiling Fan #1 shows as + // Lightbulb Tile with the dimmable LED listed first when you click open its controls. Ceiling Fan #2 shows as a Fan Tile with the fan listed first + // when you click open its controls. Nice and easy. new SpanAccessory(); new DEV_Identify("Ceiling Fan #1","HomeSpan","123-ABC","20mA LED","0.9",0); - new DEV_DimmableLED(0,17,ServiceType::Primary); + new DEV_DimmableLED(0,17,ServiceType::Primary); // Here we specify DEV_DimmableLED as the Primary Service new Service::Fan(); new Characteristic::Active(); new Characteristic::RotationDirection(); @@ -81,28 +85,63 @@ void setup() { new SpanAccessory(); new DEV_Identify("Ceiling Fan #2","HomeSpan","123-ABC","20mA LED","0.9",0); new DEV_DimmableLED(0,17); - new Service::Fan(ServiceType::Primary); + new Service::Fan(ServiceType::Primary); // Here we specify the Fan as the Primary Service new Characteristic::Active(); new Characteristic::RotationDirection(); new Characteristic::RotationSpeed(0); - // In addition to being able to specify a Service to be a Primary Service, HomeKit also allows you to give names to each individual Services. - // ... + ////////////////////////////////// + + // In addition to being able to specify which Service in an Accessory is the Primary Service, HomeKit also allows you to give names to each of + // the individual Services. This is done by instantiating a Name Characteristic for a Service. We've already used this feature in creating + // DEV_Identify --- the first argument is used to to name the Service (see DEV_Identify.h). In fact, the Name Characteristic is required + // for the AccessoryInformation Service, so we had to instantiate as part of DEV_Identify. + + // For all other Services the name Characteristic is optional. If not instantiated, the name will be defaulted to whatever name we specified + // in DEV_Identify, which means that if we have more than one operational Service in an Accessory, they will all be named the same. + // This is not necessarily a problem since names don't always come into play in the HomeKit interface. In the examples above, the only name + // that gets displayed in combined-tile mode is "Ceiling Fan #1" or "Ceiling Fan #2", which makes sense. When you click open the controls + // for either Accessory you see a lightbulb control and a fan control. They are not individually named, but the controls look different (one + // is a light control, the other a fan control) so there is no confusion. + + // If instead you set HomeKit to display the controls for these Accessories as separate tiles, you'll see that each of the light and fan controls + // has their own name. But since in the above examples we did not provide specific names for each of these Services, they will simply inherit the + // name "Ceiling Fan #1" or "Ceiling Fan #2". Again, there is no confusion since the light and fan controls each look different. + + // The situation becomes more interesting when you have an Accessory with 3 or more operational Services. Sometimes HomeKit will display the names + // of the Services on the control panel even in combined-tile mode. Consider our ceiling fan example above, but with the added feature of a night-light, + // which we will represent as a simple On/Off LED. Let's instantiate the Name Characteristic for each Service, as shown below. new SpanAccessory(); new DEV_Identify("Ceiling Fan #3","HomeSpan","123-ABC","20mA LED","0.9",0); new DEV_DimmableLED(0,17); - new Characteristic::Name("Main Light"); + new Characteristic::Name("Main Light"); // Here we create a name for the Dimmable LED new DEV_LED(16); - new Characteristic::Name("Night Light"); + new Characteristic::Name("Night Light"); // Here we create a name for the On/Off LED new Service::Fan(ServiceType::Primary); new Characteristic::Active(); new Characteristic::RotationDirection(); new Characteristic::RotationSpeed(0); - new Characteristic::Name("Fan"); + new Characteristic::Name("Fan"); // Here we create a name for the Fan + + // If you let HomeKit display this as a single, combined tile, you'll notice two things. The first is that the name of the tile is now "Fan" instead + // of "Ceiling Fan #3". Why is that? It's because we set Fan to be the Primary Service AND gave it a name --- this is the name that shows up + // on the combined tile. If we did not give it a name, it would have inherited the name "Ceiling Fan #3", which would have been the name of the tile + // as in the prior example. + + // The second thing you'll notice is that these names now appear next to each control if you click open the combined tile. It says "Fan" next to the Fan + // control, "Main Light" next to the Dimmable LED control, and "Night Light" next to the On/Off LED control. + + // If instead you tell HomeKit to display the controls for Ceiling Fan #3 as three separate tiles, you'll see that each tile contains the name specified + // above for that Service. In some circumstances that can be helpful, in others it can be confusing. For instance, if you had two ceiling fans that + // each had a main light and a night night, how would you know which "Main Light" is which? One solution is to create names like "Main Light #3" and + // "Main Light #4". The other solution is to keep them combined in a single tile, but keep the name "Ceiling Fan #3" for the combined tile, instead of + // having it over-ridden with the word "Fan" + + // This is easily done by specifying DEV_Identify as the Primary Service, instead of Fan, as follows: new SpanAccessory(); - new DEV_Identify("Ceiling Fan #4","HomeSpan","123-ABC","20mA LED","0.9",0,ServiceType::Primary); + new DEV_Identify("Ceiling Fan #4","HomeSpan","123-ABC","20mA LED","0.9",0,ServiceType::Primary); // specify DEV_Identify as the Primary Service new DEV_DimmableLED(0,17); new Characteristic::Name("Main Light"); new DEV_LED(16); @@ -113,7 +152,38 @@ void setup() { new Characteristic::RotationSpeed(0); new Characteristic::Name("Fan"); + // HomeKit now shows the name "Ceiling Fan #3" for the combined tile AND it still shows the individual names for each control when you click open the tile. + // The only downside to this configuration is that since the Fan is no longer specified as the Primary Service, the main icon on the combined tile now shows + // as a lightbulb, instead of the fan. HomeKit documentation is not clear on how the main icon is chosen under these circumstances, but I've found + // that changing the order of Services as they are instantiated can impact the icon. Here is the same example as above, but with the Fan + // instantiated as the first opertional Service, ahead of the Main Light and Night Night: + new SpanAccessory(); + new DEV_Identify("Ceiling Fan #5","HomeSpan","123-ABC","20mA LED","0.9",0,ServiceType::Primary); // specify DEV_Identify as the Primary Service + new Service::Fan(); + new Characteristic::Active(); + new Characteristic::RotationDirection(); + new Characteristic::RotationSpeed(0); + new Characteristic::Name("Fan"); + new DEV_DimmableLED(0,17); + new Characteristic::Name("Main Light"); + new DEV_LED(16); + new Characteristic::Name("Night Light"); + + // This seems to cause HomeKit to display the Fan icon on the combined tile, as well as to list the Fan Service control first when the tile is clicked + // open, almost as if it were the Primary Service in all ways except for its name. + + // As you can see, there is no right or wrong way for how to name your Accessories and Services, including whether or not to even bother naming your + // individual Services. It's purely a matter of taste - experiment and see which combinations best serve your purposes. + + // IMPORTANT: HomeKit tries to cache as many items as possible, and although it should know when configurations change, it does not always respond + // as expected. If you are experimenting and find that name changes are NOT being reflected in the HomeKit interface, simply unpair HomeSpan and + // re-pair. This often causes a refresh. If not, after unpairing you can additionally reset the HAP data in HomeSpan so that its HAP ID changes (see + // HomeSpan documentation for how to easily do this). This way when you re-pair, HomeKit should think this is a completely new device and start with + // a clean slate. *** In some limited circumstances, HomeKit may get so confused it refuses to operate the Accessories at all. You may get a No Response + // icon even though HomeSpan is operating correctly. Though it is possible to misconfigure HomeSpan in ways that cause this, if you do get this + // error please try the re-pairing methods above as it often fixes the problem. + } // end of setup() //////////////////////////////////////