Chapter 4. Managing Home Appliances with HomeKit

4.0. Introduction

A variety of accessories have been built that can interact with iOS over the years. Many of these accessories work in a home environment. For instance, the projector in a cinema room inside the house or an accessory that adjusts the temperature of the water in a swimming pool can be controlled via Apple’s new technology, appropriately named HomeKit. This is a framework that allows us to initiate contact with these accessories in the context of a home.

Now let’s have a look at a simple home setup. This house has four floors: a basement and the ground, first, and the second floors. The house has seven bedrooms on two different floors. The underground floor has a few things that excite us. One is a cinema room with a fantastic projector and some comfortable cinema seats. The chairs are reclining chairs whose positions can be adjusted to suit each viewer’s needs. In front of the projector, a few meters away, a white screen can descend from the ceiling to cover the wall so that the projector can project there, giving us a great picture while playing games or watching TV and videos.

We open the door from inside the cinema room and reach the indoor pool. There is a device that allows us to cover the swimming pool with a hard material that reclines as well, so that we can cover the pool when we don’t need it and makes sure the kids won’t fall into it at night or when not under supervision of an adult. We also have a Jacuzzi with a few switches to light up inside in various colors and to control the heat for the water.

All of these devices in the underground level of this house are rather complex to work with and require some sort of “techy-ness” before anybody can really adjust their settings. This is where HomeKit comes in. It allows us to build apps that can work with HomeKit-ready appliances inside the premises of our home and control them with ease. Throughout this chapter, I am going to use examples that come back to this basement setup.

Everything in this house can be controlled with a single iOS device. And this is not just inside the house. All these appliances can be controlled remotely. Let’s say you are on vacation and you have forgotten to turn the cinema room’s projector off. You can simply open up an app on your phone, which has been programmed to talk to all the appliances in your home, and turn that projector off. Alternatively, the company from whom you bought the projector might provide an app on the App Store that you can download to control the projector inside your cinema room.

HomeKit has more than 10 classes that we will learn about in this chapter. The master class in HomeKit is named HMHomeManager. This is our entry point to all the appliances inside our house. Every house is represented by the HMHome class and then divided into rooms, and every room (of type HMRoom) has appliances, or, as HomeKit calls them, accessories represented by the HMAccessory class.

Every accessory then has services that it can provide to the user. In the case of the Jacuzzi in the house, one of the services can be the bubble generator or the lights that can be adjusted to various colors. Services are represented by the HMService class.

Every zone has characteristics as well. For instance, take the projector in the cinema room. One of its services is to “project a movie on the white wall.” However, is this service on or off at the moment? This is a characteristic of that service. Another characteristic of this service might be “needs repair.” This may be a Boolean and, if set to true, indicates that the projector needs to be repaired because the “project movie on the white wall” service isn’t working as expected.

You can also define zones for this house. For instance, we can have a zone called “floor 1” and then many accessories under that zone so that we can, for instance, turn all the lights off in that particular zone. Zones are represented by the HMZone class. You can also define service groups in HomeKit, represented by the HMServiceGroup class. Service groups are great for giving a logical order to services from different accessories. For instance, if you have a “turn off” service for various appliances in your house, such as lights, computers, projectors, oven, and so on, you might decide to group that service together into a “turn off all appliances” service group. And when going outside the house, you can just run that service group to turn everything off—except for your fridge and freezer, that is. I have made that mistake before. Never again!

HomeKit integrates very well with Siri. That is why everything that you define using HomeKit has to have a unique name so that when you tell Siri, “Turn off the projector,” it knows that “projector” is a unique item in the house and can then call its “turn off” service. Therefore, you cannot have two accessories with the same name defined in the context of your house.

Integrating your app with HomeKit APIs requires a bit of setup, which we are going to see how to do in this chapter. One thing that you have to do before proceeding further with this chapter is to set up your provision profile to support HomeKit and then turn on HomeKit in the Capabilities section of your project. You can learn about setting up provision profiles that support HomeKit by reading Recipe 1.22.

4.1. Simulating HomeKit Accessories

Problem

You want to be able to use the HomeKit APIs without having to purchase a third-party appliance that is built to work with HomeKit, or alternatively, you want to speed up your development of an app that can speak with a third-party appliance that will eventually integrate with HomeKit at some point—but for the time being, you need to do some development of your own.

Note

This recipe uses the example house that we talked about in Recipe 4.0, so make sure you read that introduction before proceeding with this recipe to get a better understanding of what we are trying to achieve.

Solution

Download the HomeKit Accessory Simulator from Apple’s Developer website.

Discussion

The HomeKit Accessory Simulator has been very wisely developed by Apple to allow us to test our apps on simulated third-party accessories and be able to proceed with our development before purchasing an accessory that works with HomeKit. This is great stuff because not everybody can afford to purchase a real HomeKit integrated device while developing a simple application. However, you will always need to test your apps on real devices and with real accessories before sending them to Apple for approval, so I highly discourage working only with the iOS simulator and the HomeKit Accessory Simulator without trying your app with real devices.

Open the HomeKit Accessory Simulator, press the little add (+) button, and choose New Accessory from the list. This will allow you to create a new accessory in the simulator. We are going to create the cinema room’s projector right now. You will be presented with a dialog similar to that shown in Figure 4-1, so please enter the same values that I did and then press the Finish button. You can change the Manufacturer to whatever you want. I have used my limited company’s name for that.

Adding a new accessory to the HomeKit Accessory Simulator
Figure 4-1. Adding a new accessory to the HomeKit Accessory Simulator

Now you will see the information for your accessory that has been generated by the HomeKit Accessory Simulator (Figure 4-2).

Information for our accessory is shown in the HomeKit Accessory Simulator
Figure 4-2. Information for our accessory is shown in the HomeKit Accessory Simulator

Let’s go and add a service to our projector. Press the Add Service button and then choose the Add Switch option. This is the switch that turns our projector on or off. Change the On value of this service to No and rename it to Cinema Room Projector Switch. Now your simulator should look like that shown in Figure 4-3.

We added a switch service to our projector accessory
Figure 4-3. We added a switch service to our projector accessory

Now, under the Cinema Room Projector accessory, press the Add Characteristic button. A new page will appear on your screen where you will be able to change the characteristic that you are about to add to the accessory. From the Characteristic Type menu, choose Brightness and then enter a description of “This changes the projector’s brightness on the wall.” To allow users to raise the brightness to the screen’s maximum value, ensure the maximum value here is 100, while the minimum value must be 0. Use 1 as the step value. Your screen must now look like that shown in Figure 4-4. When you are done, press the Finish button to add the brightness characteristic to your projector accessory.

We created a brightness characteristic for the projector
Figure 4-4. We created a brightness characteristic for the projector

Return to the main screen of the HomeKit Accessory Simulator and change the current value of the Brightness characteristic of your projector to a value right in the middle, 50. We are going to play with this value later in this chapter. Now add another characteristic to the projector and this time, set the type of the characteristic to Custom. You will then be asked to provide a UUID for this characteristic. You can use the NSUUID class in your project to create a new UUID and use it here:

println(NSUUID().UUIDString)

Choose Float for the format of the characteristic and choose Celcius for its Units section. Ensure that the Readable and the Updatable checkmarks are checked, but uncheck the Writable checkmark. This means that we want to be able to read this characteristic but do not want to allow app programmers to write to it.

For the manufacturer description, enter “Current Heat Level.” For the maximum value, enter 60; for the minimum value, enter -10; and for the step value, enter 1. Your HomeKit Accessory Simulator should now look like that shown in Figure 4-5.

Adding a heat level characteristic to our projector
Figure 4-5. Adding a heat level characteristic to our projector

Now press the Finish button and change the current value of the heat level to 20 as shown in Figure 4-6.

We set default values for both our characteristics
Figure 4-6. We set default values for both our characteristics

Now that you have learned how to add accessories, services, and characteristics, create a Pool Area Cover accessory, add a Switch service to it, and set its value to Yes for now. Then add a characteristic to the switch of type Locked and give it a description that makes sense to you. We are going to pretend that this is the parental lock we have on the pool cover. So the pool cover has a switch that can be on or off, and it is set to on by default so that the pool is covered. We have also applied a parental lock characteristic on this switch so that we can lock it and prevent the kids from opening the pool cover if we don’t want them to. The default value of this lock is on, meaning that the parental lock has been applied. So now you should have something that looks like Figure 4-7.

We defined our pool area cover accessory in the HomeKit Accessory Simulator
Figure 4-7. We defined our pool area cover accessory in the HomeKit Accessory Simulator

The last thing we are going to do is add a new accessory to the list in HomeKit Accessory Simulator and call it Jacuzzi. Then add a switch service to it to identify whether the Jacuzzi is currently on or off. Add a custom characteristic of type Bool to the Jacuzzi itself, and give it a description of “Does the Jacuzzi have enough water?”. Make this property readable and updatable but not writable. Set the default value of this property to Yes to indicate that currently there is enough water in the Jacuzzi for it to function normally. Now your HomeKit Accessory Simulator screen should look like that shown in Figure 4-8:

We added a Jacuzzi as an accessory
Figure 4-8. We added a Jacuzzi as an accessory

Okay, that is perfect for now. We are going to base the rest of this chapter on what we have created in this recipe, so this was a very important step in getting you started with the HomeKit Accessory Simulator. Just as a side note, since it is really easy to mess up what we have created in the HomeKit Accessory Simulator, I suggest that you export all the accessories that you have created so far in the simulator and save them on disk. Simply select an accessory on the lefthand side of the HomeKit Accessory Simulator, choose File, and then choose the Export Accessory menu option. Do this for all three of the accessories that we created in this recipe and keep a copy of all of them in case something goes wrong in the HomeKit Accessory Simulator or you accidentally delete an accessory or its services or characteristics.

4.2. Managing the User’s Home in HomeKit

Problem

You want to be able to work with the user’s home objects in HomeKit and define a home if one does not exist. You would also like to be able to work with various HomeKit delegate methods pertaining to the user’s home, so that you can be notified when various aspects of the user’s home change, such as an accessory becoming available.

Solution

Instantiate an object of type HMHomeManager. After the home manager signals to you (through delegation) that the list of homes is ready to be read, read the instance’s primaryHome or homes propeties. If a home is defined for the user, you may want to become the delegate of the home manager object by conforming to the HMHomeManagerDelegate protocol.

Note

This recipe uses the example house that we talked about in Recipe 4.0, so to get a better understanding of what we are trying to achieve, please read that introduction before proceeding with this recipe.

Discussion

As soon as you instantiate an object of type HMHomeManager, ensure that you set its delegate property. This delegate must conform to the HMHomeManagerDelegate protocol. As soon as you do this, the user will be prompted to grant or reject access to HomeKit for your app. The dialog that the user will see will be similar to that shown in Figure 4-9.

The user is asked for permission to grant or reject access to our app to HomeKit
Figure 4-9. The user is asked for permission to grant or reject access to our app to HomeKit

Once you are granted permission to work with the HomeKit APIs, the first delegation call you may want to implement in the HMHomeManagerDelegate protocol is the homeManagerDidUpdateHomes: method. This method gets called whenever the home manager object detects a change to the list of homes that are registered for the user. You cannot begin reading the values of the primaryHome or the homes properties of the home manager until this delegate method is called on your delegate object. Before that, the state of the home manager is unknown.

In this recipe, let’s start building an app that can list the currently-registered homes for the user in a table view and allow the user to add homes to the list with another view controller. We will call the first view controller the ListHomesViewController and the second one AddHomeViewController. We want the first view controller to look like Figure 4-10.

List of homes is displayed on the first view controller
Figure 4-10. List of homes is displayed on the first view controller

So let’s begin with the most important variables that our view controller needs:

import UIKit
import HomeKit

class ListHomesViewController: UITableViewController, HMHomeManagerDelegate{

  let segueIdentifier = "addHome"

  struct TableViewValues{
    static let identifier = "Cell"
  }

  <# rest of the code #>

}

The segue name is a variable I defined to contain the name of the segue between this view controller and the next. Whenever this segue is triggered, I will catch that action and set the home manager property of the second view controller so that the view controller can save a home into HomeKit with the name that the user chooses. Don’t worry about this for now. We will see how it works in practice.

Then we need to define our home manager:

lazy var homeManager: HMHomeManager = {
  let manager = HMHomeManager()
  manager.delegate = self
  return manager
  }()

Then, for every home that we can find in the home manager, we will display a cell:

override func tableView(tableView: UITableView,
  numberOfRowsInSection section: Int) -> Int {
  return homeManager.homes.count
}

override func tableView(tableView: UITableView,
  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCellWithIdentifier(
      TableViewValues.identifier, forIndexPath: indexPath)
    as UITableViewCell

    let home = homeManager.homes[indexPath.row] as HMHome

    cell.textLabel.text = home.name

    return cell

}

As soon as the home manager is ready with the list of homes, we will update our table view:

func homeManagerDidUpdateHomes(manager: HMHomeManager!) {
  tableView.reloadData()
}

Last but not least, as soon as the segue between our plus (+) button is tapped on the navigation bar, we will set the home manager property of the second view controller so that it can save a new home in the manager:

override func prepareForSegue(segue: UIStoryboardSegue,
  sender: AnyObject!) {

    if segue.identifier == segueIdentifier{

      let controller = segue.destinationViewController
        as AddHomeViewController
      controller.homeManager = homeManager

    }

    super.prepareForSegue(segue, sender: sender)

}

OK, great stuff! Now let’s begin with the implementation of the second view controller, named AddHomeViewController. Here, we will have a reference to the home manager and our text field outlet:

import UIKit
import HomeKit

class AddHomeViewController: UIViewController {

  @IBOutlet weak var textField: UITextField!
  var homeManager: HMHomeManager!

  <# rest of the code #>

}

The text field is on the screen to allow the user to enter a name for the home they are about to add to HomeKit and the UI is going to look like that shown in Figure 4-11.

The user can add a new home to HomeKit
Figure 4-11. The user can add a new home to HomeKit

As soon as the view controller is displayed to the user, we will make the keyboard pop up for the text field, ready for the user to start typing:

override func viewDidAppear(animated: Bool) {
  super.viewDidAppear(animated)

  textField.becomeFirstResponder()

}

We have linked the Save button on our navigation bar with the addHome method of our view controller. Here, we will use the addHomeWithName:completionHandler: method of the home manager. This method adds a new home with a given name and then calls the completion handler on the current thread. In the completion handler, you will be given an error object that you can check to ensure that the home was added successfully:

func displayAlertWithTitle(title: String, message: String){
  let controller = UIAlertController(title: title,
    message: message,
    preferredStyle: .Alert)

  controller.addAction(UIAlertAction(title: "OK",
    style: .Default,
    handler: nil))

  presentViewController(controller, animated: true, completion: nil)

}

@IBAction func addHome(){

  if countElements(textField.text) == 0{
    displayAlertWithTitle("Home name", message: "Please enter the home name")
    return
  }

  homeManager.addHomeWithName(textField.text,
    completionHandler: {[weak self] (home: HMHome!, error: NSError!) in

      let strongSelf = self!

      if error != nil{
        strongSelf.displayAlertWithTitle("Error happened",
          message: "\(error)")
      } else {
        strongSelf.navigationController!.popViewControllerAnimated(true)
      }

    })

}

The next thing we are going to do is to add deletion capabilities to the first view controller so that the user can delete homes if she wants to. In order to delete a home from the home manager, invoke the removeHome:completionHandler: method of the home manager. The completion handler will be a closure that gets an instance of the NSError indicating whether the home was successfully removed. So let’s modify our first view controller to make it look more like that shown in Figure 4-12.

We can now remove homes from the list
Figure 4-12. We can now remove homes from the list

We want to allow the user to push the Edit button on the navigation bar and start deleting homes from the list if she wants to. Since we are going to use the UIAlertController class every now and then, I am going to create an extension on this class to give us a type method that we can use to display alert messages to the user with ease:

extension UIAlertController{
  class func showAlertControllerOnHostController(
    hostViewController: UIViewController,
    title: String,
    message: String,
    buttonTitle: String){

      let controller = UIAlertController(title: title,
        message: message,
        preferredStyle: .Alert)

      controller.addAction(UIAlertAction(title: buttonTitle,
        style: .Default,
        handler: nil))

      hostViewController.presentViewController(controller,
        animated: true,
        completion: nil)

  }
}

Then I am going to display an Edit button to the user on the navigation bar. When the Edit button is pressed, I will disable the add (+) button so that the user can focus only on the task of editing the list of homes:

override func setEditing(editing: Bool, animated: Bool) {
  super.setEditing(editing, animated: animated)

  /* Don't let the user add another home while they are editing
  the list of homes. This makes sure the user focuses on the task
  at hand */
  self.navigationItem.rightBarButtonItem!.enabled = !editing
}

override func viewWillAppear(animated: Bool) {
  super.viewWillAppear(animated)

  navigationItem.leftBarButtonItem = editButtonItem()

}

When the user presses the Delete action on any of the cells, we will intercept that action and delete the selected home from the list of homes using the removeHome:completionHandler: of the home manager:

override func tableView(tableView: UITableView,
  commitEditingStyle editingStyle: UITableViewCellEditingStyle,
  forRowAtIndexPath indexPath: NSIndexPath) {

    if editingStyle == .Delete{

      let home = homeManager.homes[indexPath.row] as HMHome
      homeManager.removeHome(home, completionHandler: {[weak self]
        (error: NSError!) in

        let strongSelf = self!

        if error != nil{
          UIAlertController.showAlertControllerOnHostController(strongSelf,
            title: "Error",
            message: "An error occurred = \(error)",
            buttonTitle: "OK")
        } else {

          tableView.deleteRowsAtIndexPaths([indexPath],
            withRowAnimation: .Automatic)

        }

        })

    }

}

Also, when we have no homes in the home manager, we should disable the Edit button because it makes no sense to have it there:

  override func tableView(tableView: UITableView,
    numberOfRowsInSection section: Int) -> Int {
      let numberOfRows = homeManager.homes.count

      if numberOfRows == 0 && editing{
        setEditing(!editing, animated: true)
      }

      editButtonItem().enabled = (numberOfRows > 0)

      return numberOfRows
  }

This code snippet will also take the view controller out of editing mode if the last cell has just been deleted. Great stuff! Now give this a go in your simulator or on a device and see how you get on.

4.3. Adding Rooms to the User’s Home

Problem

You want to be able to add rooms to a home that you have already defined for the user.

Solution

Use the addRoomWithName:completionHandler: method of your home object (see Recipe 4.2) and you will receive either an error or the created room of type HMRoom.

Note

In this recipe, I am going to base our solution on what we learned in Recipe 4.2 and just expand the same app to allow the user to add rooms to houses. So it is beneficial to read the aforementioned recipe first, before proceeding with this one.

Discussion

Every home can have rooms. Every room is represented by the HMRoom class in HomeKit. You can read the list of rooms that every home can have by checking the value of the rooms property of the home. Every home object also has a delegate of type HMHomeDelegate. This delegate will receive updates on the state of the home. One of the important methods that this delegate has is the home:didAddRoom: method that gets called whenever a new room is added to the home. The other method is the home:didRemoveRoom: method which, as you can guess, gets called whenever a room is removed from a home.

In this recipe, we are going to expand the same app that we wrote in Recipe 4.2, but this time allow the user to add rooms to a home and delete those rooms if she wants to. So we are going to have two new view controllers. One is going to show the list of rooms for a home and the other is going to allow us to add a room to a home by entering the room’s name. The first view controller will be called ListRoomsTableViewController and all it has to do is to read the list of the rooms that a home has and display it on the table view. I am assuming that the ListHomesViewController class that we coded in the previously mentioned recipe is passing the home manager reference, along with the home object that was tapped, to this view controller so that we have a reference to both and will be able to add rooms to the home if needed.

import UIKit
import HomeKit

class ListRoomsTableViewController: UITableViewController, HMHomeDelegate {

  var homeManager: HMHomeManager!
  var home: HMHome!{
  didSet{
    home.delegate = self
  }
  }

  struct TableViewValues{
    static let identifier = "Cell"
  }

  let addRoomSegueIdentifier = "addRoom"

  <# rest of the code #>

}

Can you see how I am setting the delegate of the home object? This delegate is getting set to our view controller as soon as the value of the home object changes. This value is passed to us from the ListHomesViewController class like so:

override func prepareForSegue(segue: UIStoryboardSegue,
  sender: AnyObject!) {

    if segue.identifier == addHomeSegueIdentifier{

      let controller = segue.destinationViewController
        as AddHomeViewController
      controller.homeManager = homeManager

    }

    else if segue.identifier == showRoomsSegueIdentifier{
      let controller = segue.destinationViewController
        as ListRoomsTableViewController
      controller.homeManager = homeManager

      let home = homeManager.homes[tableView.indexPathForSelectedRow()!.row]
        as HMHome

      controller.home = home
    }

    super.prepareForSegue(segue, sender: sender)

}

We can also add the delegate methods for our home to get notified whenever a room gets added or deleted:

func home(home: HMHome!, didAddRoom room: HMRoom!) {
  println("Added a new room to the home")
}

func home(home: HMHome!, didRemoveRoom room: HMRoom!) {
  println("A room has been removed from the home")
}

When the table view is displayed, we will read the value of the rooms property of the home variable and then display all the rooms that have currently been saved in the home, in our table view:

override func tableView(tableView: UITableView,
  numberOfRowsInSection section: Int) -> Int {
  return home.rooms.count

}

override func tableView(tableView: UITableView,
  cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{

    let cell = tableView.dequeueReusableCellWithIdentifier(
      TableViewValues.identifier, forIndexPath: indexPath)
      as UITableViewCell

    let room = home.rooms[indexPath.row] as HMRoom

    cell.textLabel.text = room.name

    return cell

}

We will also allow the user to delete a room in the selected home. To delete a room, we will use the removeRoom:completionHandler: method of the instance of the HMHome object:

override func tableView(tableView: UITableView,
  commitEditingStyle editingStyle: UITableViewCellEditingStyle,
  forRowAtIndexPath indexPath: NSIndexPath) {

    if editingStyle == .Delete{

      let room = home.rooms[indexPath.row] as HMRoom
      home.removeRoom(room, completionHandler: {[weak self]
        (error: NSError!) in

        let strongSelf = self!

        if error != nil{
          UIAlertController.showAlertControllerOnHostController(strongSelf,
            title: "Error",
            message: "An error occurred = \(error)",
            buttonTitle: "OK")
        } else {

          tableView.deleteRowsAtIndexPaths([indexPath],
            withRowAnimation: .Automatic)

        }

        })

    }

}

So our “list rooms” view controller is going to look like Figure 4-13.

When the user taps on the little add (+) button on the navigation bar, we will display the Add Room view controller, which is going to look like Figure 4-14.

We can now see and edit the list of rooms for a home
Figure 4-13. We can now see and edit the list of rooms for a home
We can add rooms to a home
Figure 4-14. We can add rooms to a home

In this view controller, we will simply use the addRoomWithName:completionHandler: method of our home instance and add the room to it:

@IBAction func addRoom(){

  if countElements(textField.text) == 0{
    UIAlertController.showAlertControllerOnHostController(self,
      title: "Room name", message: "Please enter the room name",
      buttonTitle: "OK")
    return
  }

  home.addRoomWithName(textField.text, completionHandler: {[weak self]
    (room: HMRoom!, error: NSError!) in

    let strongSelf = self!

    if error != nil{
      UIAlertController.showAlertControllerOnHostController(strongSelf,
        title: "Error happened", message: "\(error)",
        buttonTitle: "OK")
    } else {
      strongSelf.navigationController!.popViewControllerAnimated(true)
    }

    })

}

4.4. Specifying Zones in the User’s Home

Problem

You want to define virtual zones in the user’s home to group various rooms into logical groups. For instance, you want to group all kids’ rooms into a zone called Kids’ Rooms and all the bathrooms into a zone called Bathrooms so that you can later interact with accessories only in these zones.

Solution

Use the addZoneWithName:completionHandler: method of your home object to add a new zone to the home. To remove a zone, use the removeZone:completionHandler: method of the home. The created zone will be of type HMZone and you can interact with the various methods that it provides.

Every zone has a name and a rooms property. The name property is a string and the rooms property is an array of HMRoom instances. To add a room to a zone, use its addRoom:completionHandler: method and to remove a room (correct, you guessed it!), use the removeRoom:completionHandler: method. Both these methods accept a completion handler whose only parameter is an instance of NSError, which indicates whether the room could be added or removed successfully.

Discussion

Zones are very useful if you want to bring more logic than rooms into a house. We can categorize “things” inside a house into zones. These “things” are usually appliances but can also be rooms. For instance, let’s say that you defined all the rooms in the house using what we learned in Recipe 4.3. Now the user wants to turn off all the lights in the kids’ bedrooms, but not in other rooms. This is where zones are really useful. You can add the kids’ bedrooms to a zone with a name similar to Kids’ Bedrooms, for instance, and then see that zone in your app and allow the user to interact with various accessories that are present in rooms added to that zone.

So let’s have a look at an example. We want to do the following:

  1. Create a home object with a random name.

  2. Add three rooms to the home, two of which are bedrooms and the other one a gaming room.

  3. Add a Bedrooms zone to the home and add the two bedrooms to the zone.

  4. At the end, delete the zones and the home altogether.

Let’s start with the most basic variables that we need in our view controller:

import UIKit
import HomeKit

class ViewController: UIViewController, HMHomeManagerDelegate{

  /* We use this name to search in the list of rooms added to the home
  and read their names and find this string in them. If we
  find this string, we are sure that the room is a bedroom indeed */
  let bedroomKeyword = "bedroom"
  var numberOfBedroomsAddedSoFar = 0
  let numberOfBedroomsToAdd = 2
  var home: HMHome!
  var bedroomZone: HMZone!

  <# rest of the code #>

}

Then we will generate a random name for the house:

var randomHomeName: String = {
  return "Home \(arc4random_uniform(UInt32.max))"
  }()

var homeManager: HMHomeManager!

As soon as our view is loaded, we will start up the home manager:

override func viewDidLoad() {
  super.viewDidLoad()

  homeManager = HMHomeManager()
  homeManager.delegate = self

}

This will then go through all the homes that have been defined for the current user and call our homeManagerDidUpdateHomes: method. In that method, I am going to add a home to the home manager’s database using what we learned in Recipe 4.2. Then if that goes well, I will add a Bedrooms zone to the home:

func homeManagerDidUpdateHomes(manager: HMHomeManager!) {

  manager.addHomeWithName(randomHomeName, completionHandler: {
    [weak self](home: HMHome!, error :NSError!) in

    let strongSelf = self!

    if error != nil{
      println("Failed to add the home. Error = \(error)")
      return
    }

    strongSelf.home = home

    /* Now let's add the Bedrooms zone to the home */
    home.addZoneWithName("Bedrooms", completionHandler:
      strongSelf.addZoneCompletionHandler)

    })

}

The completion handler for adding the zone is set to the addZoneCompletionHandler closure of this view controller. That closure has to take in a parameter of type HMZone, which will be the zone that we added to the home and an error parameter of type NSError. In this closure, if the zone could be created, we will add three rooms to the home before we add them to the zone. It is important to note that rooms that you are trying to add to a zone must have already been added to a home to which the zone also belongs.

func addZoneCompletionHandler(zone: HMZone!, error: NSError!){

  if error != nil{
    println("Failed to add the zone. Error = \(error)")
    return
  } else {
    println("Successfully added the zone")
    println("Adding bedrooms to the home now...")
  }

  bedroomZone = zone

  /* Now add rooms to this home */
  home.addRoomWithName("Master bedroom",
    completionHandler: self.roomAddedToHomeCompletionHandler)

  home.addRoomWithName("Kids' bedroom",
    completionHandler: self.roomAddedToHomeCompletionHandler)

  home.addRoomWithName("Gaming room",
    completionHandler: self.roomAddedToHomeCompletionHandler)
}

The completion handler of the addRoomWithName:completionHandler: method of the home is set to the roomAddedToHomeCompletionHandler closure of this view controller. This closure must take in a parameter of type HMRoom and one of type NSError. After the rooms are added to the home, we will check for the ones that have the name “bedroom” in them and then if we find them, we will add them to the Bedrooms zone:

func roomAddedToHomeCompletionHandler(room: HMRoom!, error: NSError!){

  if error != nil{
    println("Failed to add room to home. Error = \(error)")
  } else {
    if (room.name as NSString).rangeOfString(bedroomKeyword,
      options: .CaseInsensitiveSearch).location != NSNotFound{
        println("A bedroom is added to the home")
        println("Adding it to the zone...")
        bedroomZone.addRoom(room, completionHandler:
          self.roomAddedToZoneCompletionHandler)
    } else {
      println("The room that is added is not a bedroom")
    }
  }

}

Now when we add the rooms to our zone, the completion handler of the addRoom:completionHandler: of the zone is set to the roomAddedToZoneCompletionHandler closure of our view controller. In that closure, we will check to make sure we have added all the bedrooms that we intended to add (numberOfBedroomsToAdd), and if yes, we will then start removing the zone and subsequently the home from the HomeKit database:

func roomAddedToZoneCompletionHandler(error: NSError!){

  if error != nil{
    println("Failed to add room to zone. Error = \(error)")
  } else {
    println("Successfully added a bedroom to the Bedroom zone")
    numberOfBedroomsAddedSoFar++
  }

  if numberOfBedroomsAddedSoFar == numberOfBedroomsToAdd{
    home.removeZone(bedroomZone, completionHandler: {[weak self]
      (error: NSError!) in

      let strongSelf = self!

      if error != nil{
        println("Failed to remove the zone")
      } else {
        println("Successfully removed the zone")
        println("Removing the home now...")

        strongSelf.homeManager.removeHome(strongSelf.home,
          completionHandler: {(error: NSError!) in

            if error != nil{
              println("Failed to remove the home")
            } else {
              println("Removed the home")
            }

          })

      }

      })
  }

}

If you now run this on your simulator or on a device, you will see results similar to this printed to the console:

Successfully added the zone
Adding bedrooms to the home now...
A bedroom is added to the home
Adding it to the zone...
A bedroom is added to the home
Adding it to the zone...
The room that is added is not a bedroom
Successfully added a bedroom to the bedroom zone
Successfully added a bedroom to the bedroom zone
Successfully removed the zone
Removing the home now...
Removed the home

4.5. Discovering and Managing HomeKit Enabled Accessories

Problem

You want to write an app that is able to discover all HomeKit-enabled accessories on the current network to which the user’s iOS device is connected.

Solution

Instantiate an object of type HMAccessoryBrowser and then assign your own view controller or any other object as the delegate of the accessory browse. Once you are ready to start discovering accessories that are reachable on the network, call the startSearchingForNewAccessories method of your accessory browser. This will then call the accessoryBrowser:didFindNewAccessory: delegate method of the HMAccessoryBrowserDelegate protocol for every accessory that has been found. Equally, it will call the accessoryBrowser:didRemoveNewAccessory: method for accessories that are either not discoverable anymore or have now been added to a home.

Discussion

Accessories that are HomeKit compatible are connected to a network and can be discovered by an instance of the HMAccessoryBrowser class. For an instance of this class to work, the current device running your app must have an active and enabled Internet connection. Sometimes your device might seem like it is connected to the Internet but in reality is stuck behind a firewall. For instance, you might go to a hotel and see that your phone is connected to the “free” Wifi. Then you attempt to open a web page and you see a login screen from the hotel asking for your username and password. Even though you thought you were on the Internet, you are still on a closed network.

In this recipe, we are going to have a look at a simple application that is based on the accessories we set up in Recipe 4.1, in the HomeKit Accessory Simulator. As soon as our app runs, we will use the home manager to get access to the user’s HomeKit database and then create a home object with a random name. Then we will create a room and add it to the home. After that, we will add a few accessories to the home and then assign them to the room using the assignAccessory:toRoom:completionHandler: method of the home. After the accessories are assigned to a room, we will go through the services that they offer. In each service, we will find all the characteristics of the service and print the information to the screen.

Let us begin by defining our accessory browser, the home manager, and the home and room name variables:

import UIKit
import HomeKit

class ViewController: UIViewController, HMHomeManagerDelegate,
HMAccessoryBrowserDelegate {

  var accessories = [HMAccessory]()
  var home: HMHome!
  var room: HMRoom!

  lazy var accessoryBrowser: HMAccessoryBrowser = {
    let browser = HMAccessoryBrowser()
    browser.delegate = self
    return browser
    }()

  var randomHomeName: String = {
    return "Home \(arc4random_uniform(UInt32.max))"
    }()

  let roomName = "Bedroom 1"

  var homeManager: HMHomeManager!

  <# rest of the code #>

}

As soon as our view is created, we will start our home manager to ask the user for permission to use HomeKit in our app:

override func viewDidLoad() {
  super.viewDidLoad()
  homeManager = HMHomeManager()
  homeManager.delegate = self
}

When the home manager has finished finding all the homes in the user’s HomeKit database, it will call the homeManagerDidUpdateHomes: method of its delegate (our view controller). In this method, we will add our home to the list of homes that already exist. If the home can be added, we will add a room to it as well:

func homeManagerDidUpdateHomes(manager: HMHomeManager!) {

  manager.addHomeWithName(randomHomeName, completionHandler: {[weak self]
    (home: HMHome!, error: NSError!) in

    if error != nil{
      println("Could not add the home")
    } else {
      let strongSelf = self!
      strongSelf.home = home
      println("Successfully added a home")
      println("Adding a room to the home...")
      home.addRoomWithName(strongSelf.roomName, completionHandler: {
        (room: HMRoom!, error: NSError!) in

        if error != nil{
          println("Failed to add a room...")
        } else {
          strongSelf.room = room
          println("Successfully added a room.")
          println("Discovering accessories now...")
          strongSelf.accessoryBrowser.startSearchingForNewAccessories()
        }

        })

    }

    })

}

You can see that as soon as our room can be added to the home, we use the startSearchingForNewAccessories method of our accessory browser. As soon as the accessory browser finds a new accessory in reach, it calls the accessoryBrowser:didFindNewAccessory: method of our view controller. This method is defined in the HMAccessoryBrowserDelegate protocol, which our view controller conforms to. In this method, as soon as the accessory is found, we add it to the home using the addAccessory:completionHandler: method of the home object. After the accessory is added to the home, we will use the assignAccessory:toRoom:completionHandler: of our home object to assign the accessory to the room we created:

func accessoryBrowser(browser: HMAccessoryBrowser!,
  didFindNewAccessory accessory: HMAccessory!) {

    println("Found a new accessory")
    println("Adding it to the home...")
    home.addAccessory(accessory, completionHandler: {[weak self]
      (error: NSError!) in

      let strongSelf = self!

      if error != nil{
        println("Failed to add the accessory to the home")
        println("Error = \(error)")
      } else {
        println("Successfully added the accessory to the home")
        println("Assigning the accessory to the room...")
        strongSelf.home.assignAccessory(accessory,
          toRoom: strongSelf.room,
          completionHandler: {(error: NSError!) in

            if error != nil{
              println("Failed to assign the accessory to the room")
              println("Error = \(error)")
            } else {
              println("Successfully assigned the accessory to the room")

              strongSelf.findServicesForAccessory(accessory)

            }

          })
      }

      })

}

When the accessory is successfully assigned to the room, we are calling the findServicesForAccessory: method of our view controller. This method goes through all the services that this accessory has and prints their information to the screen.

func findServicesForAccessory(accessory: HMAccessory){
  println("Finding services for this accessory...")
  for service in accessory.services as [HMService]{
    println(" Service name = \(service.name)")
    println(" Service type = \(service.serviceType)")

    println(" Finding the characteristics for this service...")
    findCharacteristicsOfService(service)
  }
}

When a new service is found in this code, we will call the findCharacteristicsOfService: method of the view controller to go through the characteristic of each one of the services:

func findCharacteristicsOfService(service: HMService){
  for characteristic in service.characteristics as [HMCharacteristic]{
    println("   Characteristic type = " +
      "\(characteristic.characteristicType)")
  }
}

We will also implement the accessoryBrowser:didRemoveNewAccessory: method of the view controller, which is defined in the HMAccessoryBrowserDelegate protocol:

func accessoryBrowser(browser: HMAccessoryBrowser!,
  didRemoveNewAccessory accessory: HMAccessory!){

    println("An accessory has been removed")

}

This method gets called on the delegate of the accessory browser whenever an accessory is moved out of range and/or cannot be accessed anymore. It will also get called if the accessory has just been added to a home.

Having created all the accessories that we talked about in Recipe 4.1, I run this code on my simulator and first see the screen in Figure 4-15.

iOS asking for pairing code for an accessory
Figure 4-15. iOS asking for pairing code for an accessory

Every HomeKit accessory has a code that is similar to Bluetooth pairing codes that you are probably familiar with. Before an app on a device can start working with an accessory, the user has to enter this code. In the HomeKit Accessory Simulator earlier, when we created the accessories (see Recipe 4.1), you might have noticed that every accessory had a setup code generated for it (Figure 4-16).

The accessory setup code is displayed on top of the screen
Figure 4-16. The accessory setup code is displayed on top of the screen

This code will be different for you, so please have a look at your HomeKit Accessory Simulator and then enter the relevant codes for every accessory that is being discovered. As you carry out this experiment for yourself in Xcode, have a look at the logs that are being printed to the console. You will then be able to see information for every accessory and its services and the characteristics of every service. Later in this chapter, we will talk more about services and characteristics of services for accessories.

4.6. Interacting with HomeKit Accessories

Problem

You want to be able to discover and modify the value of the characteristics of a service inside an accessory. For instance, you may want to change the brightness (a characteristic) of a cinema room projector (an accessory). The cinema room projector that we created in Recipe 4.1 is both a service and an accessory, and the brightness of the projector was a characteristic of the projector accessory. You can change the value of any characteristic of any service as long as that value is writable.

Solution

First, find the characteristic of a service inside an accessory whose value you want to change. Then go through the properties property of the characteristic and look for the HMCharacteristicPropertyReadable value. If this value exists, it means that you can read the value of the characteristic. When you are sure you can read the value of the characteristic, issue the readValueWithCompletionHandler: method of the characteristic to prepare its value for reading. Then read the value from the value property of the characteristic.

To be able to write to a characteristic, you need to ensure that the HMCharacteristicPropertyWritable property exists for this characteristic. When you are sure that you can write a value, issue the writeValue:completionHandler: method of the characteristic to change the value.

Discussion

What we are going to do in this recipe is quite simple: we are going to create a home in the user’s HomeKit database and then add a room to it. Then we are going to add the Cinema Room Projector accessory that we created in Recipe 4.1 to the home and assign it to the room. The second time the user opens the app, we are going to retrieve the same home, room, and accessory instead of creating a new home. So the first time, we will set a random name on the home and store that name in the user defaults. The next time the user opens the app, we will read the home name from user defaults and attempt to find it. If it doesn’t exist, we will create it.

If you remember from Recipe 4.1, our projector accessory had a brightness characteristic, which was a floating point value. In this recipe, we will find that characteristic and reduce its value by 1. So if the brightness is currently set to 50, our app will change it to 49. Every time the app runs, it will reduce this value by 1 until the value reaches the minimum value that was specified for it.

As part of this recipe, we will read the value of the brightness characteristic and reduce it. Before we read the value, we need to find out whether this characteristic is readable and before writing to it, we need to make sure it is writable. You can find this information out by going through the properties property of the characteristic instance as mentioned in the Solution of this recipe. Since this is a very important part of the project, I’ve decided to extend the HMCharacteristic class and add two functions that allow us to find out with ease whether a characteristic is readable and/or writable:

import UIKit
import HomeKit

extension HMCharacteristic{

  func containsProperty(paramProperty: String) -> Bool{
    if let propeties = self.properties{
      for property in properties as [String]{
        if property == paramProperty{
          return true
        }
      }
    }
    return false
  }

  func isReadable() -> Bool{
    return containsProperty(HMCharacteristicPropertyReadable)
  }

  func isWritable() -> Bool{
    return containsProperty(HMCharacteristicPropertyWritable)
  }

}

Now in your view controller, define your home, room, and projector accessory variables:

class ViewController: UIViewController, HMHomeManagerDelegate,
HMAccessoryBrowserDelegate {

  var home: HMHome!
  var room: HMRoom!
  var projectorAccessory: HMAccessory!

  <# rest of the code #>

}

Then let’s create a computed property that can generate a random home name for our home, if we have to create one. This property has to be able to persist its value between app runs, so it has to save the value and be able to read it back:

/* The first time it is read, it will generate a random string.
The next time it is read, it will give you the string that it created
the first time. Works between application runs and persist the
data in user defaults */
var homeName: String = {
  let homeNameKey = "HomeName"

  let defaults = NSUserDefaults.standardUserDefaults()

  /* Can we find the old value? */
  if let name = defaults.stringForKey(homeNameKey){
    if countElements(name) > 0 {
      return name
    }
  }

  /* Create a new name */
  let newName = "Home \(arc4random_uniform(UInt32.max))"
  defaults.setValue(newName, forKey: homeNameKey)
  return newName
  }()

We will also define our room name, which is part of the home, and the accessory name that we are going to be looking for. Again, remember that we created this accessory as part of Recipe 4.1:

lazy var accessoryBrowser: HMAccessoryBrowser = {
  let browser = HMAccessoryBrowser()
  browser.delegate = self
  return browser
  }()

let roomName = "Bedroom"
let accessoryName = "Cinema Room Projector"

var homeManager: HMHomeManager!

As soon as our view is loaded into memory, we start looking for existing home objects:

override func viewDidLoad() {

  homeManager = HMHomeManager()
  homeManager.delegate = self

}

This will call the homeManagerDidUpdateHomes: method of the home manager delegate. In that method, we will try to find the existing home and the bedroom in it.

func homeManagerDidUpdateHomes(manager: HMHomeManager!) {

  for home in manager.homes as [HMHome]{
    if home.name == homeName{

      println("Found the home")
      self.home = home

      for room in home.rooms as [HMRoom]{
        if room.name == roomName{
          println("Found the room")
          self.room = room
          findCinemaRoomProjectorAccessory()
        }
      }

      if self.room == nil{
        /* We have to create the room */
        println("The room doesn't exist. Creating it...")
        createRoom()
      }

    }
  }

  if home == nil{
    println("Home doesn't exist. Creating it...")
    createHome()
  }

}

If we could not find the home, we created it by calling the createHome method of our view controller. If we found the home but couldn’t find the bedroom in it, we called the createRoom method of our view controller. And if we found both the home and the bedroom, we called the findCinemaRoomProjectorAccessory method. First let’s see how we create a home if one doesn’t exist:

func createHome(){

  homeManager.addHomeWithName(homeName, completionHandler: {
    [weak self](home: HMHome!, error: NSError!) in

    if error != nil{
      println("Failed to create the home")
    } else {
      println("Successfully created the home")
      let strongSelf = self!
      strongSelf.home = home
      println("Creating the room...")
      strongSelf.createRoom()
    }

    })

}

This is what we have already learned in Recipe 4.2, so it isn’t new to us. If the home doesn’t exist, it means that its bedroom doesn’t exist either, so let’s create the room now.

func createRoom(){

  home.addRoomWithName(roomName, completionHandler: {
    [weak self](room: HMRoom!, error: NSError!) in

    if error != nil{
      println("Failed to create the room")
    } else {
      println("Successuflly created the room")
      let strongSelf = self!
      strongSelf.room = room
      strongSelf.findCinemaRoomProjectorAccessory()
    }

    })

}

When we have the home and the room, we will reach the findCinemaRoomProjectorAccessory function. In this method, we will find the “Cinema Room Projector” accessory that we had created in Recipe 4.1 by going through accessories properties of the room which we found. If we cannot find this accessory, we have to start looking for that accessory by using the accessory browser, which we learned about in Recipe 4.5:

func findCinemaRoomProjectorAccessory(){

  if let accessories = room.accessories{
    for accessory in accessories as [HMAccessory]{
      if accessory.name == accessoryName{
        println("Found the projector accessory in the room")
        self.projectorAccessory = accessory
      }
    }
  }

  /* Start searching for accessories */
  if self.projectorAccessory == nil{
    println("Could not find the projector accessory in the room")
    println("Starting to search for all available accessories")
    accessoryBrowser.startSearchingForNewAccessories()
  } else {
    lowerBrightnessOfProjector()
  }

}

If we can find the accessory attached to the room, we immediately attempt to lower its brightness by 1. But if we cannot find it in the room, our accessory browser will find it in the HomeKit Accessory Simulator. The browser will then call the accessoryBrowser:didFindNewAccessory: method of its delegate (our view controller) and let us know that an accessory was found. Then we will check the name of the accessory to make sure that we found the projector because there may be other accessories that are being discovered, but we are interested only in the projector.

func accessoryBrowser(browser: HMAccessoryBrowser!,
  didFindNewAccessory accessory: HMAccessory!) {

    println("Found an accessory...")

    if accessory.name == accessoryName{
      println("Discovered the projector accessory")
      println("Adding it to the home")
      home.addAccessory(accessory, completionHandler: {
        [weak self](error: NSError!) in

        if error != nil{
          println("Failed to add it to the home")
        } else {
          println("Successfully added it to home")
          println("Assigning the projector to the room...")
          let strongSelf = self!
          strongSelf.home.assignAccessory(accessory,
            toRoom: strongSelf.room,
            completionHandler: {(error: NSError!) in

              if error != nil{
                println("Failed to assign the projector to the room")
              } else {
                strongSelf.projectorAccessory = accessory
                println("Successfully assigned the projector to the room")
                strongSelf.lowerBrightnessOfProjector()
              }

            })

        }

      })
    }

}

Now off to the lowerBrightnessOfProjector method of our view controller. When we get to this method, we already have the home, the room, and the projector accessory as properties of our view controller. All we have to do is this:

  1. Go through the services of the projector using its services array property and find the characteristic of type HMCharacteristicTypeBrightness.

  2. After the brightness characteristic is found, find out whether it is readable. If it is readable, read its value.

  3. If we can read the value, find out whether the characteristic is writable. If writable, reduce the read value by 1 and write the value back to the characteristic.

  4. Ensure that we handle all the errors appropriately.

func lowerBrightnessOfProjector(){

  var brightnessCharacteristic: HMCharacteristic!

  println("Finding the brightness characteristic of the projector...")

  for service in projectorAccessory.services as [HMService]{
    for characteristic in service.characteristics as [HMCharacteristic]{
      if characteristic.characteristicType == HMCharacteristicTypeBrightness{
        println("Found it")
        brightnessCharacteristic = characteristic
      }
    }
  }

  if brightnessCharacteristic == nil{
    println("Cannot find it")
  } else {

    if brightnessCharacteristic.isReadable() == false{
      println("Cannot read the value of the brightness characteristic")
      return
    }

    println("Reading the value of the brightness characteristic...")

    brightnessCharacteristic.readValueWithCompletionHandler{[weak self]
      (error: NSError!) in

      if error != nil{
        println("Cannot read the brightness value")
      } else {
        println("Read the brightness value. Setting it now...")

        if brightnessCharacteristic.isWritable(){
          let newValue = (brightnessCharacteristic.value as Float) - 1.0
          brightnessCharacteristic.writeValue(newValue,
            completionHandler: {(error: NSError!) in

              if error != nil{
                println("Failed to set the brightness value")
              } else {
                println("Successfully set the brightness value")
              }

            })
        } else {
          println("The brightness characteristic is not writable")
        }

      }

    }

    if brightnessCharacteristic.value is Float{

    } else {
      println("The value of the brightness is not Float. Cannot set it")
    }

  }

}

Run this code for yourself and experiment a little bit with the characteristic’s value. Note that if the value of the brightness characteristic of the projector is already set to its minimum, our code will fail to set the value and will print an appropriate message to the console informing you of this fact.

See Also

Recipe 4.1

4.7. Grouping Services of HomeKit Accessories

Problem

You want to group a few services together for easy access later, or you want to allow the user to access those services with ease using Siri on their iOS devices.

Solution

Go through all the service groups of type HMServiceGroup in the serviceGroups property of the home object of type HMHome. If you cannot find one, create one using the addServiceGroupWithName:completionHandler: method of the home. This will either give you an error in the completion handler or a service group of type HMServiceGroup. If you successfully add a service group to the home, you can start adding services to it using its addService:completionHandler: method.

After the service group has some services, you can then enumerate through its services using its services property and perform actions on those services as you wish.

Discussion

Service groups are useful if you want to perform actions on services that are similar to each other. Let’s say that you have a “Switch” service on all your HomeKit accessories and when you go on holidays, you want to switch everything off. You can therefore create a service group and call it, for example, “Switches off all accessories.” Then you can go through the services in this group in one go and turn all the switches off instead of finding all accessories in the home and then finding their services.

I am going to base my example now on the code that we wrote in Recipe 4.6 except here, after we get our home and room reference, I am going to try to find my “All Switches” service group. If I cannot find it, I will create it:

func findOrCreateSwitchServiceGroup(){

  /* Find out whether we already have our switch service group */
  if let groups = home.serviceGroups{
    for serviceGroup in groups as [HMServiceGroup]{
      if serviceGroup.name == switchServiceGroupName{
        switchServiceGroup = serviceGroup
      }
    }
  }

  if switchServiceGroup == nil{
    println("Cannot find the switch service group. Creating it...")
    home.addServiceGroupWithName(switchServiceGroupName,
      completionHandler: {[weak self ]
        (group: HMServiceGroup!, error: NSError!) in

        if error != nil{
          println("Failed to create the switch service group")
        } else {
          let strongSelf = self!
          println("The switch service group was created successfully")
          strongSelf.switchServiceGroup = group
          strongSelf.discoverServicesInServiceGroup(group)
        }

      })
  } else {
    println("Found an existing switch service group")
    discoverServicesInServiceGroup(switchServiceGroup)

  }

  /* First, start finding new accessories */
  println("Finding new accessories...")
  accessoryBrowser.startSearchingForNewAccessories()

}

When the switch has been found or created, I call the discoverServicesInServiceGroup: method of my view controller, which will go through all the accessories in the only room in the house. If any of those accessories has a switch, it will attempt to add that switch to the group. Eventually, it will enumerate all the services in the group:

func discoverServicesInServiceGroup(serviceGroup: HMServiceGroup){

  addAllSwitchesToServiceGroup(serviceGroup, completionHandler: {
    [weak self](error: NSError!) in

    if error != nil{
      println("Failed to add the switch to the service group")
    } else {
      let strongSelf = self!
      strongSelf.enumerateServicesInServiceGroup(serviceGroup)
    }

    })

  enumerateServicesInServiceGroup(serviceGroup)

}

The enumeration of services in the group is purely done by accessing the services property of the group:

func enumerateServicesInServiceGroup(serviceGroup: HMServiceGroup){
  println("Discovering all the services in this service group...")
  if let services = serviceGroup.services{
    for service in services as [HMService]{
      println(service)
    }
  }
}

We find all the switch services in all accessories of the room by enumerating the accessories in the room, finding the switch services, and adding them to the group using the group’s addService:completionHandler: method.

func addAllSwitchesToServiceGroup(serviceGroup: HMServiceGroup,
  completionHandler: ((NSError!) -> Void)?){

  if let accessories = room.accessories{
    for accessory in accessories as [HMAccessory]{
      if let services = accessory.services{
        for service in services as [HMService]{
          if (service.name as NSString).rangeOfString("switch",
            options: .CaseInsensitiveSearch).location != NSNotFound{
              /* This is a switch, add it to the service group */
              println("Found a switch service. Adding it to the group...")
              serviceGroup.addService(service,
                completionHandler: completionHandler)
          }
        }
      }
    }

  }

}

Give this a go for yourself and see how it goes. You will then have a service group that contains all the switch services of all accessories that we could find in reach:

let switchServiceGroupName = "All Switches"

Get iOS 8 Swift Programming Cookbook now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.