Homekit 笔记(一)

2017-09-17  本文已影响267人  Rui哥

简介

启用Homekit

创建Home 布局

self.homeManager = [[HMHomeManager alloc] init];
self.homeManager.delegate = self;

当你创建一个home manager对象时,HomeKit就开始从HomeKit数据库获取这些homes和相关对象,例如room和accessory对象。当HomeKit正在获取那些对象时,home manager 的primaryHome属性是nil,并且homes 属性是个空数组。

你的app应该处理用户还没有完成创建home的情况,但是app应该等待直到HomeKit完成初始化。当获取对象完成之后,HomeKit 会发送homeManagerDidUpdateHomes:消息给home manager的代理。

注意:当app进入前台或者在后台Home manager属性发生改变时,这个homeManagerDidUpdateHomes:方法就会被调用,详情请参阅Observing Changes to the Collection of Homes

通过home manager的primaryHome属性,可以得到primary home,代码如下:
HMHome *home = self.homeManager.primaryHome;
HMHome *home;
 for (home in self.homeManager.homes ){
  ... ...
}
HMHome *home = self.homeManager.primaryHome;
 HMRome *room;
 for (room in home.rooms){
   … ...
 }
HMAccessory *accessory;
 for (accessory in room.accessories){
   … ... 
 }

如果你要展示一个个accessory的相关信息或者允许用户控制它,可设置accessory的代理方法并实现这个代理方法,
一旦你获取到一个accessory对象,你就可以访问它的服务和对象

获取Home中的Accessories属性使用HMHome类中的accessories的方法,可以直接从Home对象中获取所有的accessory对象,而不用枚举home中的所有room对象

创建Homes和添加Accessories

想要观察其他应用程序启动时HomeKit对象的变化,请参阅:Observing HomeKit Database Changes。查阅异步消息完成处理后传过来的错误码的信息,请参阅:HomeKit Constants Reference.

对象命名规则
HomeKit对象的名字,例如home、room和zone对象都可以被Siri识别,这一点已经在文档中指出。以下几点是HomeKit对象的命名规则:

(一)创建Homes

[self.homeManager addHomeWithName:@"My Home" completionHandler:^(HMHome *home, NSError *error) {
if (error != nil) {
    // Failed to add a home
} else {
    // Successfully added a home
} }];

在else语句中,写入代码以更新你应的程序的视图。

(二)添加房间

NSString *roomName = @"Living Room";
[home addRoomWithName:roomName completionHandler:^(HMRoom*room, NSError *error) {
if (error != nil) {
    // Failed to add a room to a home
} else {
    // Successfully added a room to a home
} }];

在else语句中,写入代码更新应用程序的视图。

(三)发现配件
Accessories封装了物理配件的状态,因此它不能被用户创建。想要允许用户给家添加新的配件,我们可以使HMAccessoryBrowser对象找到一个与home没有关联的配件。HMAccessoryBrower
对象在后台搜寻配件,当它找到配件的时候,使用委托来通知你的应用程序。只有在startSearchingForNewAccessories方法调用之后或者stopSearchingForNewAccessories方法调用之前,HMAccessoryBrowserDelegate消息才被发送给代理对象。

1> 在你的类接口中添加配件浏览器委托协议,并且添加一个配件浏览器属性。代码如下:

@interface EditHomeViewController ()
@property HMAccessoryBrowser *accessoryBrowser;
@end

2> 创建配件浏览器对象,并设置它的代理

self.accessoryBrowser = [[HMAccessoryBrowser alloc] init];
self.accessoryBrowser.delegate = self;

3> 开始搜寻配件

[self.accessoryBrowser startSearchingForNewAccessories];

4> 将找到的配件添加到你的收藏里

- (void)accessoryBrowser:(HMAccessoryBrowser *)browser didFindNewAccessory:(HMAccessory *)accessory {
    // Update the UI per the new accessory; for example,reload a picker view.
    [self.accessoryPicker reloadAllComponents];
}

5> 停止搜寻配件

如果一个视图控制器正在开始搜寻配件,那么可以通过重写
-(void)viewWillDisappear:(BOOL)animated {
  [super viewWillDisappear: animated];
  [self.accessoryBrowser stopSearchingForNewAccessories];
}

注意: 在WiFi网络环境下,为了安全地获取新的并且能够被HomeKit发现的无线配件,请参阅External Accessory Framework Reference.

(四)为Home和room添加配件(Accessory)
配件归属于home,并且它可以被随意添加到home中的任意一个room中。使用addAccessory:completionHandler:这个异步方法可以在home中添加配件。这个配件的名字作为一个参数传递到上述异步方法中,并且这个名字在配件所属的home中必须是唯一的。
使用assignAccessory:toRoom:completionHandler: 这个异步方法可以给home中
的room添加配件。配件默认的room是roomForEntireHome这个方法返回值room。下面的代码演示了如何给home和room添加配件:

// Add an accesory to a home and a room
// 1. Get the home and room objects for the completion handlers.

__block HMHome *home = self.home;
__block HMRoom *room = roomInHome;

// 2. Add the accessory to the home
[home addAccessory:accessory completionHandler:^(NSError *error) {
    if (error) {
        // Failed to add accessory to home
     } else {
        if (accessory.room != room) {
            // 3. If successfully, add the accessory to the room
            [home assignAccessory:accessory toRoom:room completionHandler:^(NSError *error) {
                if (error) {
                // Failed to add accessory to room
                } 
            }];
        } 
    }
}];

配件可提供一项或者多项服务,这些服务的特性是由制造商定义。想了解配件的服务和特性目的,请参阅 Accessing Services and Characteristics.

[accessory updateName:@"Kid's Night Light" completionHandler:^(NSError *error) {
    if (error) {
        // Failed to change the name
    } else {
        // Successfully changed the name
    }
}];
1> 桥接口是配件中的一个特殊对象,它允许你和其他配件交流,但是不允许你直接和HomeKit交流。例如一个桥接口可以是控制多个灯
的枢纽,它使用的是自己的通信协议,而不是HomeKit配件通信协议。
2> 想要给home添加多个桥接口 ,你可以按照Adding Accessories to Homes and Rooms中所描述的步骤,添加
任何类型的配件到home中。
3> 当你给home添加一个桥接口时,在桥接口底层的配件也会被添加到home中。 
4> 正如Observing HomeKit Database Changes中所描述的那样,每次更改通知设计模,home的代理不会接收到桥接口的home:didAddAccessory: 
代理消息,而是接收一个有关于配件的home:didAddAccessory:代理消息。

在home中,要把桥接口后的配件和任何类型的配件看成一样的--例如,把它们加入配件列表的配置表中。相反的是,当你给room增添一个桥接口时,这个桥接口底层的配件并不会自动地添加到room中,原因是桥接口和它的的配件可以位于到不同的room中。

__block HMHome *home = self.home;
NSString *zoneName = @"Upstairs";
[home addZoneWithName:zoneName completionHandler:^(HMZone*zone, NSError *error){
    if (error) {
        // Failed to create zone
    } else {
        // Successfully created zone, now add the rooms
    }
}];

可使用addRoom:completionHandler:异步方法给分区添加一个room,代码如下:

__block HMRoom *room = roomInHome;
[zone addRoom:room completionHandler:^(NSError *error) {
    if (error) {
        // Failed to add room to zone
    } else {
        // Successfully added room to zone
    }
 }];

第五部分:观察HomeKit数据库的变化

每个Home都有一个HomeKit数据库。如下图所示,HomeKit数据库会安全地和home授权的用户的iOS设备以及潜在的客人的iOS设备进行同步。为了给用户展示当前最新的数据,你的应用需要观察HomeKit数据库的变化。


homekit_database.png

(一)Homekit代理方法

1> 一般来讲,如果你的应用程序调用了一个带有完成处理参数的HomeKit方法,并且这个方法被成功调用了,那么相关联的代理消息
就会被发送给其他HomeKit应用,无论这些应用是安装在同一台iOS设备上还是远程iOS设备上。
2> 这些应用甚至可以运行在客人的iOS设备上。如果你的应用发起了数据改变,但是代理消息并没有发送到你的应用,
那么添加代码到完成处理方法和相关联的代理方法中来刷新数据和更新视图就成为必须了。
3> 如果home布局发生了显著变化,那么就重新加载关于这个home的所有信息。在完成程序处理的情况下,请在更新应用之前检查那个方法是否成功。
4> Homkit也会调用代理方法来通知你的应用程序home网络状态的改变。

例如,下图演示了使用代理方法的过程:响应用户的操作,你的应用程序调用了addRoomWithName:completionHandler:方法,并且没有错误发生,完成处理程序应当更新home的所有视图。如果成功了,homeKit将会发送home:didAddRoom:消息给其他应用中homes的代理。因此,你实现的这个home:didAddRoom:方法也应该更新home的所有视图。

homedidAddRoom.png

应用程序只有在前台运行的时候才能接受代理消息。当你的应用在后台时,HomeKit数据库的改变并不会成批处理。也就是说,如果你的应用在后台,当其他的应用成功地添加一个room到home中的时候,你的应用程序并不会接收到home:didAddRoom: 消息。当你的应用程序到前台运行时,你的应用程序将会接收到homeManagerDidUpdateHomes:消息,这个消息是表示你的应用程序要重新加载所有的数据。

(二)观察Homes集合的改变

(三)观察homes的变化

@interface AppDelegate () 
@property (strong, nonatomic) HMHomeManager *homeManager;
@end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
    self.homeManager = [[HMHomeManager alloc] init];
    self.homeManager.delegate = self;
    return YES;
}
// 代理监听事件
- (void)homeManagerDidUpdateHomes:(HMHomeManager *)manager {
    // Send a notification to the other objects
    [[NSNotificationCenter defaultCenter]postNotificationName:@"UpdateHomesNotification" object:self];
}
// 通知分发具体任务
- (void)homeManagerDidUpdatePrimaryHome:(HMHomeManager*)manager {
    // Send a notification to the other objects
    [[NSNotificationCenter defaultCenter] postNotificationName:@"UpdatePrimaryHomeNotification" object:self];
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateHomes:) name:@"UpdateHomesNotification" object:nil];

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updatePrimaryHome:) name:@"UpdatePrimaryHomeNotification" object:nil];

(四)观察个别home的变化
展示home信息的视图控制器应该成为home对象的代理,并且当home发生改变时更新视图控制器的视图。

1> 在类接口中添加home代理协议。

@interface HomeViewController () @end

2> 设置配件代理

home.delegate = self;

3> 实现HMHomeDelegate]协议
例如:实现home:didAddAccessory:和home:didRemoveAccessory: 方法来更新展示配件的视图。用HMAccessory类的room属性可以获得配件所属的room。(对配件来说,默认的room是roomForEntireHome这个方法的返回值。

Bridge Note:当你为home添加桥接口时,桥接口底层的配件会自动被添加到home中。你的代理会接收到桥接口后每个配件的 home:didAddAccessory:消息,但是你的代理不会接收到桥接口的home:didAddAccessory:消息。

(五)观察个别home的变化
配件的状态可以在任何时间发生变化。配件可能不能被获得,可以被移除,或者被关闭。请更新用户界面以反映配件状态的更改,尤其是如果你的app允许用户控制配件时。

这以下步骤中,我们假设你已经从HomeKit数据库中检索到了配件对象,正如Getting the Accessories in a Room 中描述的那样。

(五.一)观察个别home的变化

@interface AccessoryViewController ()  
@end
accessory.delegate = self;
- (void)accessoryDidUpdateReachability:(HMAccessory *)accessory {
    if (accessory.reachable == YES) {
       // Can communicate with the accessory
    } else {
       // The accessory is out of range, turned off, etc
    }
}

如果你展示了配件的服务状态和特性,那么请执行以下代理方法来相应地更新其视图:

accessoryDidUpdateServices
accessory:service:didUpdateValueForCharacteristic:

六:访问服务和特性

服务HMService代表了一个配件(accessory)的某个功能和一些具有可读写的特性HMCharacteristic。
一个配件可以拥有多项服务,一个服务也可以有很多特性。比如一个车库开门器可能拥有一个照明和开关的服务。照明服务可能拥有打开/关闭和调节亮度的特性。

用户不能制造智能家电配件和它们的服务-配件制造商会制造配件和它们的服务-但是用户可以改变服务的特性。一些拥有可读写属性的特性代表着某种物理状态,比如,一个恒温器中的当前温度就是一个只可读的值,但是目标温度又是可读写的。苹果预先定义了一些服务和特性的名称,以便让Siri能够识别它们。

(一) 获得配件的服务和属性
在依照Getting the Accessroties in a Room中描述,你创建了一个配件对象之后,你可以获得配件的服务和特性。当然你也可以直接从home中按照类型获得不同的服务。

重要:不要暴露匿名服务-比如固件升级服务-给用户

NSArray *services = accessroy.services;
// Get all lights and thermostats in a home
NSArray *lightServices = [home servicesWithTypes:[HMServicesTypeLightbulb]];
NSArray *thermostatServices = [home servicesWithTypes:[HMServicesTypeThermostat]]
NSString *name = service.name;
NSArray *characteristics = service.characteristics
NSString *serviceType = service.serviceType;

(二)改变服务名称

[service updateName:@"Garage 1 Opener" completionHandler:^(NSError *error) {
    if (error) {
        // Failed to change the name
    } else {
        // Successfully changed the name
    }
}];

苹果定义了一些特性的类型,并能被Siri识别:

亮度(Brightness)
最近温度(Current temperature)
锁的状态(Lock state)
电源的状态(Power state)
目标状态(Target state)
目标温度(Target temperature)

使用readValueWithCompletionHandler:异步方法来读取一个特性的值。

[characteristic readValueWithCompletionHandler:^(NSError *error) {
    if (error == nil) {
        // Successfully read the value
        id value = characteristic.value;
    }
    else {
        // Unable to read the value
    } 
}];

在if语句块中,加入你的代码以更新app的视图。

[self.characteristic writeValue:@42 withCompletionHandler:^(NSError *error) {
    if (error == nil) {
       // Successfully wrote the value
    }
    else {
       // Unable to write the value
    }
 }];

不要以为函数调用完成就意味着写入成功,实际上只有在当完成回调执行并没有错误产生时才表示写入成功。比如,直到一个开关的特性改变之前都不要改变这个开关的状态。在if语句块中,加入你的代码,以更新app的视图。
另外,在别的app更新了特性的值时也需要更新视图,在Observing Changes to Accessories中有描述

(三)创建服务组
一个服务组[HMServiceGroup提供了控制不同配件的任意数量服务的快捷方式-比如,当用户离开家之后控制家中的某些灯。

serviceGroup.png
[self.home addServiceGroupWithName:@"Away Lights" completionHandler:^(HMServiceGroup *serviceGroup, NSError *error) {
    if (error == nil) {
       // Successfully created the service group
    } else {
       // Unable to create the service group
    }
}];
[serviceGroup addService:service completionHandler:^(NSError *error) {
    if (error == nil) {
       // Successfully added service to service group
    }else{
       // Unable to add the service to the service group
    }
}];
NSArray *serviceGroups = self.home.serviceGroups;
HMAccessory *accessory = service.accessory;

和配件类似,代理方法在别的app改变服务组时也会被调用。如果你的app使用了服务组,请阅读HMHomeDelegate Protocol Reference文档,获悉你应该实现哪些方法以观察这些变化。

七: 测试

八:创建动作集(Action Sets) 和 触发器(Triggers)

一个动作集合HMActionSet和触发器HMTimerTrigger允许你同时控制多个智能电器。比如,一个动作集合可能会在用户上床休息之前执行一组动作HMAction。一个写动作向一个特性写入了值。动作集合中的动作是以不确定的顺序执行的。一个触发器会在一个特定的时间出发一个动作集并可以重复执行。每一个动作集合在一个家庭中都有唯一的名称并可被Siri识别。

(一) 创建写入动作

写入动作会向一个服务的特性写入值并被加入到动作集合中去。HMAction类是HMCharacteristicWriteAction具体类的抽象基类。一个动作有一个相关联的特性对象,你可以通过Accessing Services and Characteristics中描述的来获取相关的服务和特性,然后创建这个HMCharacteristicWriteAction。

HMCharacteristicWriteAction *action = [[HMCharacteristicWriteAction alloc] 
initWithCharacteristic:characteristic 
targetValue:value];

在你的代码中,你使用对应的特性的期望来替换value参数,并使用对应的HMCharacteristic对象来替换characteristic参数。

(二)创建并执行动作集
一个动作集就是一个共同执行的动作的集合。比如一个夜间动作集合可能包含关闭电灯,调低恒温水平和锁上房门。

[self.home addActionSetWithName:@"NightTime" completionHandler:^(HMActionSet *actionSet, NSError *error) {
    if (error == nil) {
        // 成功添加了一个动作集
    } else {
        // 添加一个动作集失败
    }
}];
[actionSet addAction:action completionHandler:^(NSError *error) {
    if (error == nil) {
        // 成功添加了一个动作到动作集
    } else {
    // 添加一个动作到动作集失败
    }
}];

比如,用户希望控制所有的节日彩灯。我们就创建一个动作集来打开所有的节日彩灯,另外一个动作集来关闭所有的节日彩灯。为了打开所有的节日彩灯,发送executeActionSet:completionHandler:消息给home对象,并传递"打开节日彩灯"动作集。

九:创建并开启触发器

遵循下面几步来创建并启动一个定时触发器

self.trigger = [[HMTimerTrigger alloc] initWithName:name fireDate:fireDate timeZone:niL recurrence:nil recurrenceCalendar:nil];

触发时间必须设置在将来的某个时刻,第二个参数必须为0.如果你设置了一个周期,周期的最小值是5分钟,最大值是5周。关于如何使用NSDateComponents和NSCalendar来设置周期,请阅读Date and Time Programming Guide

使用HMTrigger基类方法addActionSet:completionHandler:,来添加一个动作集到触发器。
使用HMHome类中的addTrigger:completionHandler:方法来添加一个触发器到家庭。

十: 用户管理

创建home的用户是该home的管理员,可以执行所有操作,包括添加一个客人用户到home。任何管理员添加到这个home的用户(HMUser)都有一个有限的权限。客人不能更改家庭的布局,但是可以执行下面的动作:

比如,一个家庭的户主可以创建一个home布局并向其中添加家庭成员。每个家庭成员必须拥有一个iOS设备和Apple ID以及相关的iCloud账户。iCloud需要个人输入的Apple ID和户主提供的Apple ID相吻合,以便让他们访问这个home。考虑到隐私问题,Apple ID对你的App是不可见的。

管理员需要遵从以下步骤来添加一个客人到home中:

添加一个客人到home,需要在客人的iOS设备上做以下操作:

客人执行的操作可能会失败。如果一个异步方法中出现HMErrorCodeInsufficientPrivileges错误码的话,这就意味着用户没有足够的权限来执行动作-也许这个用户只是客人,而不是管理员。

添加和移除用户

[self.home addUserWithCompletionHandler:^(HMUser *user, NSError *error) {
    if (error == nil) {
        // Successfully added a user
    }
    else {
       // Unable to add a user
    }
 }];

获得用户名
出于隐私的考虑,你的app对用户名只有读得权限,并不能读写用户的Apple ID。使用HMHome对象的users属性来获取用户。使用HMUser类的name属性来获取用户名。

Homekit 笔记(二) https://www.jianshu.com/p/dc3fdbef74a1

上一篇 下一篇

猜你喜欢

热点阅读