24 个版本

0.1.0-pre.152022 年 8 月 12 日
0.1.0-pre.142021 年 12 月 25 日
0.1.0-pre.132021 年 10 月 28 日
0.1.0-pre.102021 年 7 月 12 日
0.0.3 2018 年 9 月 15 日

#138 in macOS 和 iOS API

MIT/Apache

2MB
47K SLoC

HAP (HomeKit Accessory Protocol)

CI crates.io docs.rs license: MIT/Apache-2.0

Rust 实现 Apple HomeKit 配件协议 (HAP)。

此软件包支持 Apple 当前在稳定 macOS 版本上实现的全部 HomeKit 服务和特性,并提供创建自定义特性、服务和配件的能力。

HomeKit 配件协议支持通过 IP 和蓝牙低功耗传输。当前此软件包中仅实现了 IP 传输。通过实现的 HAP 配件 HTTP 服务器暴露配件,并通过内置的 mDNS 宣告。

HomeKit 数据模型

HAP 将 HomeKit 兼容设备定义为虚拟 配件,这些配件由 服务 组成,而服务又由 特性 组成。

特性持有各种数据类型的价值以及可选的元数据,如最大/最小值或单位。服务将特性分组,并代表配件的功能。每个配件至少包含一个 配件信息服务 和任意数量的其他服务。例如,一个定制的吊扇配件可能包括一个 配件信息服务、一个 风扇服务 和一个 灯泡服务

Ceiling Fan Accessory
|
|-- Accessory Information Service
|   |-- Identify Characteristic
|   |-- Manufacturer Characteristic
|   |-- Model Characteristic
|   |-- Name Characteristic
|   |-- Serial Characteristic
|
|-- Fan Service
|   |-- Power State Characteristic
|   |-- Rotation Direction Characteristic
|   |-- Rotation Speed Characteristic
|
|-- Lightbulb Service
|   |-- Power State Characteristic
|   |-- Brightness Characteristic
|   |-- Hue Characteristic
|   |-- Saturation Characteristic

此软件包为 Apple 在 HomeKit 配件模拟器中预定义的每个服务提供预构建的配件,以及其他如电视等配件。可以创建、组装和使用自定义特性和服务。

有关预定义特性、服务和配件的完整列表,请参阅 文档Apple 的官方规范

使用示例

创建一个简单的灯泡配件并启动 IP 服务器

use tokio;

use hap::{
    accessory::{lightbulb::LightbulbAccessory, AccessoryCategory, AccessoryInformation},
    server::{IpServer, Server},
    storage::{FileStorage, Storage},
    Config,
    MacAddress,
    Pin,
    Result,
};

#[tokio::main]
async fn main() -> Result<()> {
    let lightbulb = LightbulbAccessory::new(1, AccessoryInformation {
        name: "Acme Lightbulb".into(),
        ..Default::default()
    })?;

    let mut storage = FileStorage::current_dir().await?;

    let config = match storage.load_config().await {
        Ok(mut config) => {
            config.redetermine_local_ip();
            storage.save_config(&config).await?;
            config
        },
        Err(_) => {
            let config = Config {
                pin: Pin::new([1, 1, 1, 2, 2, 3, 3, 3])?,
                name: "Acme Lightbulb".into(),
                device_id: MacAddress::new([10, 20, 30, 40, 50, 60]),
                category: AccessoryCategory::Lightbulb,
                ..Default::default()
            };
            storage.save_config(&config).await?;
            config
        },
    };

    let server = IpServer::new(config, storage).await?;
    server.add_accessory(lightbulb).await?;

    let handle = server.run_handle();

    std::env::set_var("RUST_LOG", "hap=debug");
    env_logger::init();

    handle.await
}

设置同步回调以响应远程值读取和更新

use hap::characteristic::CharacteristicCallbacks;

lightbulb.lightbulb.power_state.on_read(Some(|| {
    println!("power_state characteristic read");
    Ok(None)
}));

lightbulb.lightbulb.power_state.on_update(Some(|current_val: &bool, new_val: &bool| {
    println!("power_state characteristic updated from {} to {}", current_val, new_val);
    Ok(())
}));

设置异步回调以响应远程值读取和更新

use hap::characteristic::AsyncCharacteristicCallbacks;

lightbulb.lightbulb.power_state.on_read_async(Some(|| {
    async {
        println!("power_state characteristic read (async)");
        Ok(None)
    }
    .boxed()
}));

lightbulb.lightbulb.power_state.on_update_async(Some(|current_val: bool, new_val: bool| {
    async move {
        println!("power_state characteristic updated from {} to {} (async)", current_val, new_val);
        Ok(())
    }
    .boxed()
}));

直接设置特征值

use hap::{
    characteristic::HapCharacteristic,
    serde_json::Value,
};

lightbulb.lightbulb.power_state.set_value(Value::Bool(true)).await.unwrap();

与服务器中添加的配件交互

Server::add_accessory 返回一个指向配件的指针,可以使用如下方式

async {
    let accessory_ptr = server.add_accessory(accessory).await.unwrap();
}

指针后面的配件由 HapAccessory 特性表示。 HapAccessory::get_serviceHapAccessory::get_mut_service 方法提供了对配件服务的访问,这些服务由 HapService 特性表示。 HapService::get_characteristicHapService::get_mut_characteristic 方法提供了对服务特性的访问,这些特性由 HapCharacteristic 特性表示。所有服务和特性都通过它们的 HapType 识别。

访问并更改 power_state 特性,该特性属于 lightbulb 配件的 lightbulb 服务的样子如下

use hap::{HapType, serde_json::Value};

async {
    let mut lightbulb_accessory = lightbulb_ptr.lock().await;

    let lightbulb_service = lightbulb_accessory.get_mut_service(HapType::Lightbulb).unwrap();
    let power_state_characteristic = lightbulb_service.get_mut_characteristic(HapType::PowerState).unwrap();

    power_state_characteristic.set_value(Value::Bool(true)).await.unwrap();
}

完整的示例可以在这里找到。

(重新)确定要绑定的IP

通过 Config 结构的 hostport 字段设置要服务的IP和端口。在创建配置时,如果未显式设置,端口默认为 32000,IP设置为主机上检测到的第一个非环回网络接口的IP。然而,在创建配置之后,该IP不会隐式重新评估。为了做到这一点,实现者必须显式调用 Config 结构的 redetermine_local_ip() 方法。

以下是一个在每次程序重启时重新加载保存的配置的同时执行此操作的示例

let config = match storage.load_config().await {
    Ok(mut config) => {
        config.redetermine_local_ip(); // on config reload, the IP has to be explicitly redetermined
        let mut storage = FileStorage::current_dir().await?;
        config
    },
    Err(_) => {
        let config = Config {
            pin: Pin::new([1, 1, 1, 2, 2, 3, 3, 3])?,
            name: "Acme Outlet".into(),
            device_id: MacAddress::new([10, 20, 30, 40, 50, 60]),
            category: AccessoryCategory::Outlet,
            ..Default::default() // on config creation, the IP can be implicitly determined
        };
        let mut storage = FileStorage::current_dir().await?;
        config
    },
};

开发

代码生成由工作空间中的 codegen 软件包处理。生成的文件已检查入。要运行代码生成,请执行

cargo run --package hap-codegen
cargo +nightly fmt

许可证

HAP 在您的选择下受以下任一许可证的许可

依赖关系

~13–28MB
~397K SLoC