Xamarin.iOS 中的 HomeKit

HomeKit 是 Apple 用于控制家庭自动化设备的框架。 本文介绍 HomeKit,并介绍如何在 HomeKit 配件模拟器中配置测试配件,以及如何编写简单的 Xamarin.iOS 应用来与这些配件进行交互。

已启用 HomeKit 的示例应用

Apple 在 iOS 8 中引入了 HomeKit,以此将来自各种供应商的多个家庭自动化设备无缝集成到单个连贯的单元中。 通过推广用于发现、配置和控制家庭自动化设备的通用协议,HomeKit 允许来自非相关供应商的设备协同工作,所有这些协议都无需单个供应商协调工作。

使用 HomeKit,可以创建一个 Xamarin.iOS 应用,用于控制已启用 HomeKit 的任何设备,而无需使用供应商提供的 API 或应用。 可以使用 HomeKit 执行以下操作:

  • 发现启用了 HomeKit 的新家庭自动化设备,并将其添加到将在所有用户 iOS 设备中保留的数据库。
  • 在 HomeKit 家庭配置数据库中设置、配置、显示和控制任何设备。
  • 与任何预配置的 HomeKit 设备通信,并命令其执行各自的操作或协同工作,例如打开厨房中的所有灯。

除了在家庭配置数据库中为启用了 HomeKit 的应用提供设备外,HomeKit 还提供对 Siri 语音命令的访问权限。 如果已适当配置 HomeKit 设置,用户可以发出语音命令,例如“Siri,打开客厅灯。”

家庭配置数据库

HomeKit 将给定位置中的所有自动化设备组织到家庭集合中。 此集合为用户提供了一种将家庭自动化设备分组到具有有意义且人类可读标签的逻辑排列单元的方法。

家庭集合存储在家庭配置数据库中,该数据库将自动备份且同步到所有用户的 iOS 设备。 HomeKit 提供以下类,用于处理家庭配置数据库:

  • HMHome - 这是一个顶层容器,用于保存单个物理位置(例如单个家庭住宅)中所有家庭自动化设备的所有信息和配置。 用户可能拥有多个住宅,例如其主要住所和度假屋。 或者,他们可能有同一属性的不同“房子”,如主要住所和车库上方的客房。 无论哪种方式,都必须设置并存储至少一个 HMHome 对象,然后才能输入任何其他 HomeKit 信息。
  • HMRoom - 虽然是可选的,但 HMRoom 允许用户定义家庭 (HMHome) 内的特定房间例如:厨房、浴室、车库或客厅。 用户可以将房屋中特定位置的所有家庭自动化设备分组到一个 HMRoom,并将其作为一个单元执行操作。 例如,要求 Siri 关闭车库灯。
  • HMAccessory - 这表示已在用户住所中安装的已启用 HomeKit 的实体自动化设备(如智能恒温器)。 每个 HMAccessory 分配到一个 HMRoom。 如果用户未配置任何房间,HomeKit 会将配件分配给特殊的默认房间。
  • HMService - 表示给定 HMAccessory 提供的服务,例如灯光的开/关状态或颜色(如果支持变色)。 每个 HMAccessory 可以有多个服务,如车库开门装置,它也包含一盏灯。 此外,给定 HMAccessory 可能有固件更新等服务,不受用户控制。
  • HMZone - 允许用户将 HMRoom 对象集合分组到逻辑区域,例如楼上、楼下或地下室。 虽然是可选的,但允许交互,如要求 Siri 关闭楼下的所有灯。

预配 HomeKit 应用

由于 HomeKit 实施了安全性要求,因此必须在 Apple 开发人员门户和 Xamarin.iOS 项目文件中正确配置使用 HomeKit 框架的 Xamarin.iOS 应用。

请执行以下操作:

  1. 登录到 Apple 开发人员门户

  2. 单击“证书、标识符和配置文件”。

  3. 如果尚未执行此操作,请单击“标识符”并为应用创建 ID(例如 com.company.appname),否则请编辑现有 ID。

  4. 确保已为给定 ID 检查 HomeKit 服务:

    为给定 ID 启用 HomeKit 服务

  5. 保存所做更改。

  6. 单击“预配配置文件”>“开发”并为应用创建新的开发预配配置文件:

    为应用创建新开发预配配置文件

  7. 下载并安装新的预配配置文件,或使用 Xcode 下载并安装配置文件。

  8. 编辑 Xamarin.iOS 项目选项,并确保使用刚刚创建的预配配置文件:

    选择刚刚创建的预配配置文件

  9. 接下来,编辑 Info.plist 文件,并确保使用用于创建预配配置文件的应用 ID:

    设置应用 ID

  10. 最后,编辑 Entitlements.plist 文件,并确保已选择 HomeKit 权利:

    启用 HomeKit 权利

  11. 保存对所有文件的更改。

进行这些设置后,应用程序即可访问 HomeKit 框架 API。 有关预配的详细信息,请参阅我们的设备预配预配应用指南。

重要

测试已启用 HomeKit 的应用需要一个已正确进行开发预配的真实 iOS 设备。 无法从 iOS 模拟器测试 HomeKit。

HomeKit 配件模拟器

为了提供一种方法来测试所有可能的家庭自动化设备和服务,而无需拥有物理设备,Apple 创建了 HomeKit 配件模拟器。 使用此模拟器,可以设置和配置虚拟 HomeKit 设备。

安装模拟器

Apple 提供的 HomeKit 配件模拟器可从 Xcode 单独下载,因此,在继续操作之前,需要安装它。

请执行以下操作:

  1. 在 Web 浏览器中,访问 Apple 开发人员的下载

  2. 下载 Xcode xxx 的其他工具(其中 xxx 是已安装的 Xcode 的版本):

    下载 Xcode 的其他工具

  3. 打开磁盘映像,并在应用程序目录中安装工具。

安装 HomeKit 配件模拟器后,可以创建虚拟配件进行测试。

创建虚拟配件

若要启动 HomeKit 配件模拟器并创建一些虚拟配件,请执行以下操作:

  1. 从“应用程序”文件夹中,启动 HomeKit 配件模拟器:

    HomeKit 配件模拟器

  2. 单击 + 按钮并选择“新建配件...”:

    添加新配件

  3. 填写有关新配件的信息,然后单击“完成”按钮:

    填写有关新配件的信息

  4. 单击“添加服务...”按钮,然后从下拉列表中选择服务类型:

    从下拉列表中选择服务类型

  5. 为服务提供名称,然后单击“完成”按钮:

    输入服务的名称

  6. 可以通过单击“添加特征”按钮并配置所需设置来为服务提供可选特征:

    配置所需的设置

  7. 重复上述步骤,创建 HomeKit 支持的每种虚拟家庭自动化设备之一。

创建并配置了一些示例虚拟 HomeKit 配件后,现在可以从 Xamarin.iOS 应用使用并控制这些设备。

配置 Info.plist 文件

对于 iOS 10(及更高版本),开发人员需要将 NSHomeKitUsageDescription 键添加到应用的 Info.plist 文件中,并提供一个字符串,声明应用为何想要访问用户的 HomeKit 数据库。 此字符串将在用户首次运行应用时向用户显示:

HomeKit 权限对话框

若要设置此键,请执行以下操作:

  1. 解决方案资源管理器中双击 Info.plist 文件,打开它进行编辑。

  2. 在屏幕底部,切换到“”视图。

  3. 将新条目添加到列表。

  4. 从下拉列表中,选择“隐私 - HomeKit 使用情况说明”:

    选择“隐私 - HomeKit 使用说明”

  5. 输入应用想要访问用户 HomeKit 数据库的原因说明:

    输入描述

  6. 保存对文件所做的更改。

重要

未能在 Info.plist 文件中设置 NSHomeKitUsageDescription 键将导致应用静默失败(在运行时由系统关闭),在 iOS 10(或更高版本)中运行时不会出错。

连接到 HomeKit

若要与 HomeKit 通信,Xamarin.iOS 应用需要先实例化 HMHomeManager 类的实例。 家庭管理器是 HomeKit 的中心入口点,负责提供可用家庭列表、更新和维护该列表并返回用户的主要住所

HMHome 对象包含有关给定家庭的所有信息,包括它可能包含的任何房间、组或区域,以及已安装的任何家庭自动化配件。 在 HomeKit 中执行任何操作之前,必须至少创建一个 HMHome 并将其分配为主要住所。

应用负责检查主要住所是否存在并创建和分配一个(如果不存在)。

添加家庭管理器

若要将 HomeKit 感知添加到 Xamarin.iOS 应用,请编辑 AppDelegate.cs 文件以对其进行编辑,使其如下所示:

using HomeKit;
...

public HMHomeManager HomeManager { get; set; }
...

public override void FinishedLaunching (UIApplication application)
{
    // Attach to the Home Manager
    HomeManager = new HMHomeManager ();
    Console.WriteLine ("{0} Home(s) defined in the Home Manager", HomeManager.Homes.Count());

    // Wire-up Home Manager Events
    HomeManager.DidAddHome += (sender, e) => {
        Console.WriteLine("Manager Added Home: {0}",e.Home);
    };

    HomeManager.DidRemoveHome += (sender, e) => {
        Console.WriteLine("Manager Removed Home: {0}",e.Home);
    };
    HomeManager.DidUpdateHomes += (sender, e) => {
        Console.WriteLine("Manager Updated Homes");
    };
    HomeManager.DidUpdatePrimaryHome += (sender, e) => {
        Console.WriteLine("Manager Updated Primary Home");
    };
}

首次运行应用程序时,系统会询问用户是否希望允许它访问其 HomeKit 信息:

系统将询问用户是否希望允许其访问 HomeKit 信息

如果用户回答“确定”,则应用程序将能够使用其 HomeKit 配件,否则不会使用,并且对 HomeKit 的任何调用都将失败并出现错误。

有了家庭管理器,接下来,应用程序需要查看是否已配置主要住所,如果尚未配置,请为用户提供创建和分配主要住所的方法。

访问主要住所

如上所述,必须在 HomeKit 可用之前创建和配置主要住所,并且应用有责任为用户提供创建和分配主要住所(如果尚不存在)。

当应用首次从后台启动或返回时,它需要监视 HMHomeManager 类的 DidUpdateHomes 事件,以检查是否存在主要住所。 如果不存在,它应提供一个界面供用户创建。

可以将以下代码添加到视图控制器,以检查主要住所:

using HomeKit;
...

public AppDelegate ThisApp {
    get { return (AppDelegate)UIApplication.SharedApplication.Delegate; }
}
...

// Wireup events
ThisApp.HomeManager.DidUpdateHomes += (sender, e) => {

    // Was a primary home found?
    if (ThisApp.HomeManager.PrimaryHome == null) {
        // Ask user to add a home
        PerformSegue("AddHomeSegue",this);
    }
};

当家庭管理器与 HomeKit 建立连接时,将触发 DidUpdateHomes 事件,任何现有家庭将加载到管理器的家庭集合,如果可用,将加载主要住所。

添加主要住所

如果 HMHomeManagerPrimaryHome 属性在 DidUpdateHomes 事件后 null,则需要为用户提供创建和分配主要住所的方法,然后继续操作。

通常,应用将显示一个表单,供用户命名一个新家庭,然后传递给家庭管理器以设置为主要住所。 对于 HomeKitIntro 示例应用,模式视图是在 Xcode Interface Builder 中创建的,并由应用主界面中的 AddHomeSegue segue 调用。

它提供了一个文本字段,供用户输入新家庭的名称和用于添加家庭的按钮。 当用户点击“添加家庭”按钮时,以下代码会调用家庭管理器添加家庭:

// Add new home to HomeKit
ThisApp.HomeManager.AddHome(HomeName.Text,(home,error) =>{
    // Did an error occur
    if (error!=null) {
        // Yes, inform user
        AlertView.PresentOKAlert("Add Home Error",string.Format("Error adding {0}: {1}",HomeName.Text,error.LocalizedDescription),this);
        return;
    }

    // Make the primary house
    ThisApp.HomeManager.UpdatePrimaryHome(home,(err) => {
        // Error?
        if (err!=null) {
            // Inform user of error
            AlertView.PresentOKAlert("Add Home Error",string.Format("Unable to make this the primary home: {0}",err.LocalizedDescription),this);
            return ;
        }
    });

    // Close the window when the home is created
    DismissViewController(true,null);
});

AddHome 方法将尝试创建新家庭并将其返回到给定的回调例程。 如果 error 属性不是 null,则已发生错误,应向用户显示该错误。 最常见的错误是由非唯一的家庭名称或无法与 HomeKit 通信的家庭管理器引起的。

如果已成功创建家庭,则需要调用 UpdatePrimaryHome 方法来将新家庭设置为主要住所。 同样,如果 error 属性不是 null,则已发生错误,应向用户显示该错误。

还应监视家庭管理器 DidAddHomeDidRemoveHome 事件,并根据需要更新应用的用户界面。

重要

上述示例代码中使用的 AlertView.PresentOKAlert 方法是 HomeKitIntro 应用程序中的帮助程序类,可更轻松地使用 iOS 警报。

查找新配件

从家庭管理器定义或加载主要住所后,Xamarin.iOS 应用可以调用 HMAccessoryBrowser 来查找任何新的家庭自动化配件并将其添加到家庭。

调用 StartSearchingForNewAccessories 方法以开始查找新配件,在完成后调用 StopSearchingForNewAccessories 方法。

重要

StartSearchingForNewAccessories 不应长时间运行,因为这会对 iOS 设备的电池寿命和性能产生负面影响。 Apple 建议在一分钟后调用 StopSearchingForNewAccessories,或仅在向用户显示“查找配件”UI 时搜索。

发现新配件时,将调用 DidFindNewAccessory 事件,并将添加到配件浏览器中的 DiscoveredAccessories 列表中。

DiscoveredAccessories 列表将包含 HMAccessory 对象集合,此集合定义一个已启用 HomeKit 的家庭自动化设备及其可用服务,例如灯或车库门控制。

找到新配件后,应向用户显示它,以便他们可以选择它并将其添加到家庭。 示例:

查找新的配件

调用 AddAccessory 方法,以将所选配件添加到家庭集合中。 例如:

// Add the requested accessory to the home
ThisApp.HomeManager.PrimaryHome.AddAccessory (_controller.AccessoryBrowser.DiscoveredAccessories [indexPath.Row], (err) => {
    // Did an error occur
    if (err !=null) {
        // Inform user of error
        AlertView.PresentOKAlert("Add Accessory Error",err.LocalizedDescription,_controller);
    }
});

如果 err 属性不是 null,则已发生错误,应向用户显示该错误。 否则,系统将要求用户输入设备要添加的设置代码:

输入要添加的设备的设置代码

在 HomeKit 配件模拟器中,可以在“设置代码”字段下找到此数字:

HomeKit 配件模拟器中的“设置代码”字段

对于真实的 HomeKit 配件,设置代码印在设备本身的标签上、产品包装盒或配件的用户手册上。

应监视配件浏览器的 DidRemoveNewAccessory 事件并更新用户界面,以在用户将其添加到其家庭集合后,从可用列表中移除配件。

使用配件

建立了主要住所并已向其中添加配件,则可以提供供用户使用的配件(以及可选房间)列表。

HMRoom 对象包含有关给定房间及其所含任何配件的所有信息。 可以选择将房间组织到一个或多个区域。 HMZone 包含有关给定区域及其所含所有房间的所有信息。

在本示例中,我们将保持简单,直接使用家庭配件,而不是将它们组织到房间或区域中。

HMHome 对象包含一个可呈现给其 Accessories 属性中的用户的已分配配件列表。 例如:

示例配件

可以在此处选择给定的配件,并使用它提供的服务。

使用服务

当用户与启用了给定 HomeKit 的家庭自动化设备交互时,通常是通过它提供的服务进行。 HMAccessory 类的 Services 属性包含定义设备提供的服务的 HMService 对象集合。

服务包括灯、恒温器、车库开门装置、开关或锁等。 某些设备(如车开门装置)将提供多个服务,如灯和开关门的能力。

除了给定配件提供的特定服务之外,每个配件还包含一个 Information Service,它定义其名称、制造商、型号和序列号等属性。

配件服务类型

可通过 HMServiceType 枚举获取以下服务类型:

  • AccessoryInformation - 提供有关给定家庭自动化设备(配件)的信息。
  • AirQualitySensor - 定义空气质量传感器。
  • Battery - 定义配件电池的状态。
  • CarbonDioxideSensor - 定义二氧化碳检测器。
  • CarbonMonoxideSensor - 定义一氧化碳检测器。
  • ContactSensor - 定义接触传感器(例如正在打开或关闭的窗户)。
  • Door - 定义门状态传感器(如开启或关闭)。
  • Fan - 定义远程控制的风扇。
  • GarageDoorOpener - 定义车库开门装置。
  • HumiditySensor - 定义湿度传感器。
  • LeakSensor - 定义泄漏传感器(如热水器或洗衣机)。
  • LightBulb - 定义独立的灯或属于其他配件(如车库开门装置)一部分的灯。
  • LightSensor - 定义灯传感器。
  • LockManagement - 定义管理自动门锁的服务。
  • LockMechanism - 定义远程控制锁(如门锁)。
  • MotionSensor - 定义运动传感器。
  • OccupancySensor - 定义占用传感器。
  • Outlet - 定义远程控制的墙壁插座。
  • SecuritySystem - 定义家庭安全系统。
  • StatefulProgrammableSwitch - 定义在一旦触发仍处于给定状态的可编程开关(如拍动式开关)。
  • StatelessProgrammableSwitch - 定义在触发后返回到原始状态的可编程开关(如按压式开关)。
  • SmokeSensor - 定义烟雾传感器。
  • Switch - 定义打开/关闭开关,如标准墙壁开关。
  • TemperatureSensor - 定义温度传感器。
  • Thermostat - 定义用于控制 HVAC 系统的智能恒温器。
  • Window - 定义可远程打开或关闭的自动化窗户。
  • WindowCovering - 定义远程控制的窗帘,如可以打开或关闭的百叶窗。

显示服务信息

加载 HMAccessory 后,可以查询它提供的各个 HNService 对象并向用户显示该信息:

显示服务信息

在尝试处理之前,应始终检查 HMAccessoryReachable 属性。 如果用户不在设备范围内或者已拔掉电源,则无法访问配件。

选择服务后,用户可以查看或修改该服务的一个或多个特征,以监视或控制给定的家庭自动化设备。

处理特征

每个 HMService 对象可以包含 HMCharacteristic 对象集合,它可以提供有关服务状态的信息(如门正在打开或关闭),或者允许用户调整状态(如设置灯光颜色)。

HMCharacteristic 不仅提供有关特征及其状态的信息,还提供通过特征元数据 (HMCharacteristisMetadata) 处理状态的方法。 此元数据可以提供在向用户显示信息或允许用户修改状态时有用的属性(例如最小值范围和最大值范围)。

HMCharacteristicType 枚举提供一组特征元数据值,可以如下所示定义或修改这些值:

  • AdminOnlyAccess
  • AirParticulateDensity
  • AirParticulateSize
  • AirQuality
  • AudioFeedback
  • BatteryLevel
  • 亮度
  • CarbonDioxideDetected
  • CarbonDioxideLevel
  • CarbonDioxidePeakLevel
  • CarbonMonoxideDetected
  • CarbonMonoxideLevel
  • CarbonMonoxidePeakLevel
  • ChargingState
  • ContactState
  • CoolingThreshold
  • CurrentDoorState
  • CurrentHeatingCooling
  • CurrentHorizontalTilt
  • CurrentLightLevel
  • CurrentLockMechanismState
  • CurrentPosition
  • CurrentRelativeHumidity
  • CurrentSecuritySystemState
  • CurrentTemperature
  • CurrentVerticalTilt
  • FirmwareVersion
  • HardwareVersion
  • HeatingCoolingStatus
  • HeatingThreshold
  • HoldPosition
  • Hue
  • 确定
  • InputEvent
  • LeakDetected
  • LockManagementAutoSecureTimeout
  • LockManagementControlPoint
  • LockMechanismLastKnownAction
  • 日志
  • 制造商
  • 模型
  • MotionDetected
  • 名称
  • ObstructionDetected
  • OccupancyDetected
  • OutletInUse
  • OutputState
  • PositionState
  • PowerState
  • RotationDirection
  • RotationSpeed
  • 饱和度
  • SerialNumber
  • SmokeDetected
  • SoftwareVersion
  • StatusActive
  • StatusFault
  • StatusJammed
  • StatusLowBattery
  • StatusTampered
  • TargetDoorState
  • TargetHeatingCooling
  • TargetHorizontalTilt
  • TargetLockMechanismState
  • TargetPosition
  • TargetRelativeHumidity
  • TargetSecuritySystemState
  • TargetTemperature
  • TargetVerticalTilt
  • TemperatureUnits
  • 版本

处理特征的值

若要确保应用具有给定特征的最新状态,请调用 HMCharacteristic 类的 ReadValue 方法。 如果 err 属性不是 null,则已发生错误,可能会也可能不会向用户显示该错误。

特性的 Value 属性包含给定特征的当前状态 NSObject,因此不能直接在 C# 中使用。

为了读取该值,已将以下帮助程序类添加到 HomeKitIntro 示例应用程序:

using System;
using Foundation;
using System.Globalization;
using CoreGraphics;

namespace HomeKitIntro
{
    /// <summary>
    /// NS object converter is a helper class that helps to convert NSObjects into
    /// C# objects
    /// </summary>
    public static class NSObjectConverter
    {
        #region Static Methods
        /// <summary>
        /// Converts to an object.
        /// </summary>
        /// <returns>The object.</returns>
        /// <param name="nsO">Ns o.</param>
        /// <param name="targetType">Target type.</param>
        public static Object ToObject (NSObject nsO, Type targetType)
        {
            if (nsO is NSString) {
                return nsO.ToString ();
            }

            if (nsO is NSDate) {
                var nsDate = (NSDate)nsO;
                return DateTime.SpecifyKind ((DateTime)nsDate, DateTimeKind.Unspecified);
            }

            if (nsO is NSDecimalNumber) {
                return decimal.Parse (nsO.ToString (), CultureInfo.InvariantCulture);
            }

            if (nsO is NSNumber) {
                var x = (NSNumber)nsO;

                switch (Type.GetTypeCode (targetType)) {
                case TypeCode.Boolean:
                    return x.BoolValue;
                case TypeCode.Char:
                    return Convert.ToChar (x.ByteValue);
                case TypeCode.SByte:
                    return x.SByteValue;
                case TypeCode.Byte:
                    return x.ByteValue;
                case TypeCode.Int16:
                    return x.Int16Value;
                case TypeCode.UInt16:
                    return x.UInt16Value;
                case TypeCode.Int32:
                    return x.Int32Value;
                case TypeCode.UInt32:
                    return x.UInt32Value;
                case TypeCode.Int64:
                    return x.Int64Value;
                case TypeCode.UInt64:
                    return x.UInt64Value;
                case TypeCode.Single:
                    return x.FloatValue;
                case TypeCode.Double:
                    return x.DoubleValue;
                }
            }

            if (nsO is NSValue) {
                var v = (NSValue)nsO;

                if (targetType == typeof(IntPtr)) {
                    return v.PointerValue;
                }

                if (targetType == typeof(CGSize)) {
                    return v.SizeFValue;
                }

                if (targetType == typeof(CGRect)) {
                    return v.RectangleFValue;
                }

                if (targetType == typeof(CGPoint)) {
                    return v.PointFValue;
                }
            }

            return nsO;
        }

        /// <summary>
        /// Convert to string
        /// </summary>
        /// <returns>The string.</returns>
        /// <param name="nsO">Ns o.</param>
        public static string ToString(NSObject nsO) {
            return (string)ToObject (nsO, typeof(string));
        }

        /// <summary>
        /// Convert to date time
        /// </summary>
        /// <returns>The date time.</returns>
        /// <param name="nsO">Ns o.</param>
        public static DateTime ToDateTime(NSObject nsO){
            return (DateTime)ToObject (nsO, typeof(DateTime));
        }

        /// <summary>
        /// Convert to decimal number
        /// </summary>
        /// <returns>The decimal.</returns>
        /// <param name="nsO">Ns o.</param>
        public static decimal ToDecimal(NSObject nsO){
            return (decimal)ToObject (nsO, typeof(decimal));
        }

        /// <summary>
        /// Convert to boolean
        /// </summary>
        /// <returns><c>true</c>, if bool was toed, <c>false</c> otherwise.</returns>
        /// <param name="nsO">Ns o.</param>
        public static bool ToBool(NSObject nsO){
            return (bool)ToObject (nsO, typeof(bool));
        }

        /// <summary>
        /// Convert to character
        /// </summary>
        /// <returns>The char.</returns>
        /// <param name="nsO">Ns o.</param>
        public static char ToChar(NSObject nsO){
            return (char)ToObject (nsO, typeof(char));
        }

        /// <summary>
        /// Convert to integer
        /// </summary>
        /// <returns>The int.</returns>
        /// <param name="nsO">Ns o.</param>
        public static int ToInt(NSObject nsO){
            return (int)ToObject (nsO, typeof(int));
        }

        /// <summary>
        /// Convert to float
        /// </summary>
        /// <returns>The float.</returns>
        /// <param name="nsO">Ns o.</param>
        public static float ToFloat(NSObject nsO){
            return (float)ToObject (nsO, typeof(float));
        }

        /// <summary>
        /// Converts to double
        /// </summary>
        /// <returns>The double.</returns>
        /// <param name="nsO">Ns o.</param>
        public static double ToDouble(NSObject nsO){
            return (double)ToObject (nsO, typeof(double));
        }
        #endregion
    }
}

每当应用程序需要读取特征的当前状态时,都使用 NSObjectConverter。 例如:

var value = NSObjectConverter.ToFloat (characteristic.Value);

上面的行将值转换为 float,随后可在 Xamarin C# 代码中使用。

若要修改 HMCharacteristic,请调用其 WriteValue 方法并在 NSObject.FromObject 调用中包装新值。 例如:

Characteristic.WriteValue(NSObject.FromObject(value),(err) =>{
    // Was there an error?
    if (err!=null) {
        // Yes, inform user
        AlertView.PresentOKAlert("Update Error",err.LocalizedDescription,Controller);
    }
});

如果 err 属性不是 null,则已发生错误,应向用户显示该错误。

测试特征值更改

使用 HMCharacteristics 和模拟配件时,可以在 HomeKit 配件模拟器内监视对 Value 属性的修改。

HomeKitIntro 应用在真实 iOS 设备硬件上运行时,应该在 HomeKit 配件模拟器中立即看到对特征值的更改。 例如,更改 iOS 应用中灯的状态:

更改 iOS 应用中的光线状态

应更改 HomeKit 配件模拟器中灯的状态。 如果值未更改,请检查写入新特征值时错误消息的状态,并确保配件仍可访问。

高级 HomeKit 功能

本文介绍了在 Xamarin.iOS 应用中使用 HomeKit 配件所需的基本功能。 但是,有几个 HomeKit 高级功能在本简介中未介绍:

  • 房间 - 启用了 HomeKit 的配件可以选择由最终用户组织到房间中。 这样,HomeKit 就可以以用户易于理解和使用的方式呈现配件。 有关创建和维护房间的详细信息,请参阅 Apple 的 HMRoom 文档。
  • 区域 - 可以选择由最终用户将房间组织到区域中。 区域是指用户可将其视为单个单元的房间集合。 例如:楼上、楼下或地下室。 同样,这允许 HomeKit 以对最终用户有意义的方式演示和处理配件。 有关创建和维护区域的详细信息,请参阅 Apple 的 HMZone 文档。
  • 操作和操作集 - 操作会修改配件服务特征,并且可以分组到集中。 操作集充当脚本来控制一组配件并协调其操作。 例如,“Watch TV”脚本可能会关闭百叶窗、调暗灯光并打开电视及其音响系统。 有关创建和维护操作和操作集的详细信息,请参阅 Apple 的 HMActionHMActionSet 文档。
  • 触发器 - 满足给定条件集时,触发器可以激活一个或多个操作集。 例如,打开走廊灯和在天变暗时锁上所有外部门。 有关创建和维护触发器的详细信息,请参阅 Apple 的 HMTrigger 文档。

由于这些功能使用上述相同技术,因此应遵循 Apple 的 HomeKit 开发人员指南HomeKit 用户界面指南HomeKit 框架引用,轻松实现这些功能。

HomeKit 应用评审指南

在将已启用 HomeKit 的 Xamarin.iOS 应用提交到 iTunes Connect,以便在 iTunes App Store 中发布之前,请确保遵循 Apple 为已启用 HomeKit 的应用制定的指南:

  • 如果使用 HomeKit 框架,应用的主要用途必须是家庭自动化。
  • 应用的营销文本必须能够让用户指导正在使用 HomeKit,并且必须提供隐私策略。
  • 严格禁止收集用户信息或使用 HomeKit 打广告。

有关完整评审指南,请参阅 Apple 的 App Store 评审指南

iOS 9 中的新增功能

Apple 已对适用于 iOS 9 的 HomeKit 进行了以下更改和添加:

  • 维护现有对象 - 如果修改了现有配件,家庭管理器 (HMHomeManager) 将通知你修改的特定项。
  • 持久标识符 - 所有相关 HomeKit 类现在包含一个 UniqueIdentifier 属性,用于在已启用 HomeKit 的应用(或同一应用的实例)中唯一标识给定项。
  • 用户管理 - 添加了一个内置视图控制器,用于为有权访问用户主要住所中 HomeKit 设备的用户提供用户管理。
  • 用户功能 - HomeKit 用户现在拥有一组权限,可控制他们在 HomeKit 和已启用 HomeKit 的配件中使用哪些功能。 应用应仅向当前用户显示相关功能。 例如,只有管理员才能维护其他用户。
  • 预定义场景 - 已为平通 HomeKit 用户身边发生的四个常见事件创建预定义场景:起床、离开、返回、睡觉。 无法从家庭中删除这些预定义场景。
  • 场景和 Siri - Siri 对 iOS 9 中的场景提供更深入的支持,并且可以识别 HomeKit 中定义的任何场景的名称。 用户只需将场景的名称说给 Siri 即可执行该场景。
  • 配件类别 - 已将一组预定义类别添加到所有配件,有助于识别要添加到家庭或从应用中处理的配件类型。 这些新类别在配件设置期间可用。
  • Apple Watch 支持 - HomeKit 现在可用于 watchOS,Apple Watch 将能够在没有 iPhone 靠近手表的情况下控制已启用 HomeKit 的设备。 适用于 watchOS 的 HomeKit 支持以下功能:查看家庭、控制配件和执行场景。
  • 新事件触发器类型 - 除了 iOS 8 中支持的计时器类型触发器外,iOS 9 现在还支持基于配件状态(例如传感器数据)或地理位置的事件触发器。 事件触发器使用 NSPredicates 为执行设置条件。
  • 远程访问 - 使用远程访问时,用户现在可以在远离家的远程位置控制已启用 HomeKit 的家庭自动化配件。 在 iOS 8 中,仅当用户在家里拥有第三代 Apple TV 时,才支持此功能。 在 iOS 9 中,通过 iCloud 和 HomeKit 配件协议 (HAP) 解除了此限制并支持远程访问。
  • 新蓝牙低功耗 (BLE) 能力 - HomeKit 现在支持更多的配件类型,这些类型可以通过蓝牙低功耗 (BLE) 协议进行通信。 使用 HAP 安全隧道,HomeKit 配件可以通过 Wi-Fi 公开另一个蓝牙配件(如果超出蓝牙范围)。 在 iOS 9 中,BLE 配件完全支持通知和元数据。
  • 新配件类别 - Apple 在 iOS 9 中添加了以下新的配件类别:窗帘、电动门窗、警报系统、传感器和可编程开关。

有关 iOS 9 中 HomeKit 新功能的详细信息,请参阅 Apple 的 HomeKit 索引HomeKit 新增功能视频。

总结

本文介绍了 Apple 的 HomeKit 家庭自动化框架。 其中介绍了如何使用 HomeKit 配件模拟器设置和配置测试设备,以及如何创建简单的 Xamarin.iOS 应用来使用 HomeKit 发现、控制家庭自动化设备并与之通信。