MCPcopy Index your code
hub / github.com/ewilken/hap-rs

github.com/ewilken/hap-rs @v0.0.10

Chat with this repo
repository ↗ · DeepWiki ↗ · release v0.0.10 ↗ · + Follow
323 symbols 645 edges 40 files 93 documented · 29% updated 1y agov0.1.0-pre.15 · 2022-08-12★ 21617 open issues
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

HAP (HomeKit Accessory Protocol)

Build Status Latest Version docs license: MIT/Apache-2.0

Rust implementation of the Apple HomeKit Accessory Protocol (HAP).

This crate supports all HomeKit Services and Characteristics currently implemented by Apple and provides the ability to create custom Characteristics, Services and Accessories.

The HomeKit Accessory Protocol supports transports over IP and Bluetooth LE. Currently only the transport over IP is implemented in this crate. Accessories are exposed by the implemented HAP Accessory HTTP server and announced via built-in mDNS.

HomeKit Data Model

The HAP defines HomeKit enabled devices as virtual Accessories that are composed of Services that are composed of Characteristics.

Characteristics hold values of various data types as well as optional metadata like max/min values or units. Services group Characteristics and represent features of the Accessory. Every Accessory consist of at least one Accessory Information Service and any number of additional Services. For example a custom ceiling fan Accessory may consist of an Accessory Information Service, a Fan Service and a Lightbulb Service.

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

This crate provides a pre-built Accessory for every Service predefined by Apple. Custom Characteristics and Services can be created, assembled and used alongside the predefined ones.

For a full list of the predefined Characteristics, Services and Accessories, see the docs or Apple's official specification.

Usage Examples

Creating a simple outlet Accessory and starting the IP transport:

use hap::{
    transport::{Transport, IpTransport},
    accessory::{Category, Information, outlet},
    Config,
};

fn main() {
    let info = Information {
        name: "Outlet".into(),
        ..Default::default()
    };

    let outlet = outlet::new(info).unwrap();

    let config = Config {
        name: "Outlet".into(),
        category: Category::Outlet,
        ..Default::default()
    };

    let mut ip_transport = IpTransport::new(config).unwrap();
    ip_transport.add_accessory(outlet).unwrap();
    ip_transport.start().unwrap();
}

Dynamically adding and removing Accessories:

use hap::{
    transport::{Transport, IpTransport},
    accessory::{Category, Information, bridge, outlet},
    Config,
};

fn main() {
  let bridge = bridge::new(Information {
        name: "Acme Bridge".into(),
        ..Default::default()
    }).unwrap();

    let first_outlet = outlet::new(Information {
        name: "Outlet 1".into(),
        ..Default::default()
    }).unwrap();

    let second_outlet = outlet::new(Information {
        name: "Outlet 2".into(),
        ..Default::default()
    }).unwrap();

    let mut ip_transport = IpTransport::new(Config {
        name: "Acme".into(),
        category: Category::Outlet,
        ..Default::default()
    }).unwrap();

    let _bridge = ip_transport.add_accessory(bridge).unwrap();
    let _first_outlet = ip_transport.add_accessory(first_outlet).unwrap();
    let second_outlet = ip_transport.add_accessory(second_outlet).unwrap();
    ip_transport.remove_accessory(&second_outlet).unwrap();

    ip_transport.start().unwrap();
}

Using the Readable and Updatable traits to react to remote value reads and updates:

use hap::{
    transport::{Transport, IpTransport},
    accessory::{Category, Information, outlet},
    characteristic::{Readable, Updatable},
    Config,
    HapType,
};

#[derive(Clone)]
pub struct VirtualOutlet {
    on: bool,
}

impl Readable<bool> for VirtualOutlet {
    fn on_read(&mut self, _: HapType) -> Option<bool> {
        println!("On read.");
        Some(self.on)
    }
}

impl Updatable<bool> for VirtualOutlet {
    fn on_update(&mut self, old_val: &bool, new_val: &bool, _: HapType) {
        println!("On updated from {} to {}.", old_val, new_val);
        if new_val != old_val { self.on = *new_val; }
    }
}

fn main() {
    let info = Information {
        name: "Outlet".into(),
        ..Default::default()
    };

    let mut outlet = outlet::new(info).unwrap();

    let virtual_outlet = VirtualOutlet { on: false };
    outlet.inner.outlet.inner.on.set_readable(virtual_outlet.clone()).unwrap();
    outlet.inner.outlet.inner.on.set_updatable(virtual_outlet).unwrap();

    let config = Config {
        name: "Outlet".into(),
        category: Category::Outlet,
        ..Default::default()
    };

    let mut ip_transport = IpTransport::new(config).unwrap();
    ip_transport.add_accessory(outlet).unwrap();
    ip_transport.start().unwrap();
}

Setting a Characteristic value directly:

outlet.inner.outlet.inner.on.set_value(true).unwrap();

Change dependent Characteristics on value changes:

use std::{rc::Rc, cell::RefCell};

use hap::{
    transport::{Transport, IpTransport},
    accessory::{Category, Information, door},
    characteristic::{Characteristic, Readable, Updatable},
    Config,
    HapType,
};

pub struct VirtualDoorInner {
    current_position: u8,
    target_position: u8,
}

#[derive(Clone)]
pub struct VirtualDoor {
    inner: Arc<Mutex<VirtualDoorInner>>,
    current_position: Characteristic<u8>,
}

impl VirtualDoor {
    pub fn new(inner: VirtualDoorInner, current_position: Characteristic<u8>) -> VirtualDoor {
        VirtualDoor { inner: Arc::new(Mutex::new(inner)), current_position }
    }
}

impl Readable<u8> for VirtualDoor {
    fn on_read(&mut self, hap_type: HapType) -> Option<u8> {
        match hap_type {
            HapType::CurrentPosition => {
                println!("Current position read.");
                Some(self.inner.borrow().current_position)
            },
            HapType::TargetPosition => {
                println!("Target position read.");
                Some(self.inner.borrow().target_position)
            },
            _ => None,
        }
    }
}

impl Updatable<u8> for VirtualDoor {
    fn on_update(&mut self, old_val: &u8, new_val: &u8, hap_type: HapType) {
        match hap_type {
            HapType::CurrentPosition => {
                println!("Current position updated from {} to {}.", old_val, new_val);
                if new_val != old_val {
                    self.inner.borrow_mut().current_position = *new_val;
                }
            },
            HapType::TargetPosition => {
                println!("Target position updated from {} to {}.", old_val, new_val);
                if new_val != old_val {
                    {
                        let mut inner = self.inner.borrow_mut();
                        inner.target_position = *new_val;
                        inner.current_position = *new_val;
                    }
                    self.current_position.set_value(*new_val).unwrap();
                }
            },
            _ => {},
        }
    }
}

fn main() {
    let mut door = door::new(Information {
        name: "Door".into(),
        ..Default::default()
    }).unwrap();
    let virtual_door = VirtualDoor::new(
        VirtualDoorInner { current_position: 0, target_position: 0 },
        door.inner.door.inner.current_position.clone(),
    );
    door.inner.door.inner.current_position.set_readable(virtual_door.clone()).unwrap();
    door.inner.door.inner.current_position.set_updatable(virtual_door.clone()).unwrap();
    door.inner.door.inner.target_position.set_readable(virtual_door.clone()).unwrap();
    door.inner.door.inner.target_position.set_updatable(virtual_door).unwrap();

    let config = Config {
        name: "Door".into(),
        category: Category::Door,
        ..Default::default()
    };
    let mut ip_transport = IpTransport::new(config).unwrap();
    ip_transport.add_accessory(door).unwrap();
    ip_transport.start().unwrap();
}

License

HAP is licensed under either of

  • Apache License, Version 2.0, (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
  • MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT)

at your option.

Extension points exported contracts — how you extend this code

HapAccessory (Interface)
`HapAccessory` is implemented by the inner type of every `Accessory`. [6 implementers]
src/accessory/mod.rs
Encodable (Interface)
`Encodable` is implemented by types that can be encoded to a to a `Vec ` of concatenated TLVs. [2 implementers]
src/protocol/tlv.rs
JsonHandler (Interface)
(no doc) [4 implementers]
src/transport/http/handler/mod.rs
Readable (Interface)
`Readable` can be implemented to react to the remote read of a `Characteristic`. [2 implementers]
src/characteristic/mod.rs
HapService (Interface)
`HapService` is implemented by the inner type of every `Service`. [1 implementers]
src/service/mod.rs
AccessoryListMember (Interface)
`AccessoryListMember` is implemented by members of an `AccessoryList`. [1 implementers]
src/db/accessory_list.rs
HapAccessoryService (Interface)
`HapAccessoryService` is implemented by every `Service` inside of an `Accessory`. [1 implementers]
src/accessory/mod.rs
BigArray (Interface)
see https://github.com/serde-rs/serde/issues/631
src/protocol/device.rs

Core symbols most depended-on inside this repo

new
called by 149
src/accessory/defined/lock.rs
new
called by 41
src/pin.rs
write
called by 40
src/transport/tcp.rs
as_bytes
called by 17
src/protocol/device.rs
set_value
called by 12
src/characteristic/mod.rs
to_string
called by 8
src/transport/http/mod.rs
status_response
called by 7
src/transport/http/mod.rs
get_mut_characteristics
called by 7
src/service/mod.rs

Shape

Method 186
Class 51
Function 50
Enum 22
Interface 14

Languages

Rust100%

Modules by API surface

src/characteristic/mod.rs33 symbols
build.rs21 symbols
src/transport/tcp.rs20 symbols
src/db/file_storage.rs15 symbols
src/accessory/mod.rs14 symbols
src/transport/http/mod.rs13 symbols
src/service/mod.rs13 symbols
src/protocol/tlv.rs13 symbols
src/db/database.rs12 symbols
src/transport/http/handler/pairings.rs11 symbols
src/transport/http/handler/pair_setup.rs11 symbols
src/config.rs11 symbols

For agents

$ claude mcp add hap-rs \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact