Skip to content

Commit

Permalink
Remove the model interface and import characteristics/services from m…
Browse files Browse the repository at this point in the history
…etadata file

There was no benefit in using the model interface in contrast to the actual structs
and their public fields. Therefore the model package is removed. This changes the
import paths of the accessory/service/characteristic packages, which are now in the
library's root directory.

Also the code for all services and characteristics is now auto-generated by importing
the data from the metadata plist file in the HAPAccessoryKit.framework (see gen and
cmd package). We can now import new types more easily in the future.

Existing code should migrate to the new version by replace import paths containing
github.com/brutella/hc/model* with github.com/brutella/hc/*
  • Loading branch information
brutella committed Apr 7, 2016
1 parent d8c223d commit 7c8a170
Show file tree
Hide file tree
Showing 244 changed files with 6,066 additions and 2,687 deletions.
86 changes: 38 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@

[![Build Status](https://travis-ci.org/brutella/hc.svg)](https://travis-ci.org/brutella/hc)

[HomeControl][homecontrol] is an implementation of the [HomeKit][homekit] Accessory Protocol (HAP) to create your own HomeKit accessory and bridges. HomeKit bridges make non-HomeKit accessories available to HomeKit by acting as a middleman.
[HomeControl][homecontrol] is an implementation of the [HomeKit][homekit] Accessory Protocol (HAP) to create your own HomeKit accessory in [Go](https://golang.org). [HomeKit][homekit] is a set of protocols and libraries to access devices for Home Automation. The actual protocol documentation is only available to MFi members.

## Overview
You can use this library to make existing Home Automation devices HomeKit compatible. I've already developed the following HomeKit bridges with in:

[HomeKit][homekit] is a set of protocols and libraries to access accessories for Home Automation. Unfortunately the protocol is not open source and the official documentation is only available to MFi members. HomeControl is a complete implementation of the protocol in Go and does not depend on any OS.
- [LIFX](https://github.com/brutella/hklifx/)
- [UVR1611](https://github.com/brutella/hkuvr1611)

## HomeKit Client
## HomeKit on iOS

I've made an app for iPhone, iPad and Apple Watch called [Home][home] to control any HomeKit accessory. If you purchase Home on the [App Store][home-appstore], you not only support my work but also get an awesome iOS app. Thank you.
HomeKit is fully integrated since iOS 8. Developers can use the HomeKit framework to communicate with HomeKit using high-level APIs.
I've developed the [Home][home] app (for iPhone, iPad, Apple Watch) to control HomeKit accessories. If you [purchase Home][home-appstore] on the App Store, you not only support my work but also get an awesome iOS app. Thank you.

Once you've setup HomeKit, you can use Siri to interact with your accessories using voice command (*Hey Siri, turn off the lights in the living room*).

[home]: http://selfcoded.com/home/
[home-appstore]: http://itunes.apple.com/app/id995994352

## Features

- Full implementation of the HomeKit Accessory Protocol in Go
- Support for switch, outlet, light bulb, thermometer, and thermostat accessory
- Full implementation of the HAP in Go
- Built-in service announcement via mDNS using [bonjour](http://github.com/oleksandr/bonjour)
- Optional logging with https://github.com/brutella/log
- Runs on multiple platforms (already in use on Linux and OS X)
- Documentation: http://godoc.org/github.com/brutella/hc

Expand All @@ -45,26 +47,25 @@ I've made an app for iPhone, iPad and Apple Watch called [Home][home] to control

## API Example

Create a simple on/off switch which is accessible via IP and secured using the pin *00102003*.
Create a simple on/off switch, which is accessible via IP and secured using the pin *00102003*.

```go
package main

import (
"log"
"github.com/brutella/hc/hap"
"github.com/brutella/hc/model"
"github.com/brutella/hc/model/accessory"
"github.com/brutella/hc/accessory"
)

func main() {
info := model.Info{
info := accessory.Info{
Name: "Lamp",
}
sw := accessory.NewSwitch(info)
acc := accessory.NewSwitch(info)

config := hap.Config{Pin: "00102003"}
t, err := hap.NewIPTransport(config, sw.Accessory)
t, err := hap.NewIPTransport(config, acc.Accessory)
if err != nil {
log.Fatal(err)
}
Expand All @@ -80,7 +81,7 @@ func main() {
You should change some default values for your own needs

```go
info := model.Info{
info := accessory.Info{
Name: "Lamp",
SerialNumber: "051AC-23AAM1",
Manufacturer: "Apple",
Expand All @@ -94,7 +95,7 @@ info := model.Info{
You get a callback when the power state of a switch changed by a client.

```go
sw.OnStateChanged(func(on bool) {
acc.Switch.On.OnValueRemoteUpdate(func(on bool) {
if on == true {
log.Println("Client changed switch to on")
} else {
Expand All @@ -105,10 +106,30 @@ sw.OnStateChanged(func(on bool) {

When the switch is turned on "the analog way", you should set the state of the accessory.

sw.SetOn(true)
acc.Switch.On.SetValue(true)

A complete example is available in `_example/example.go`.

## Model

The HomeKit model hierarchy looks like this:

Accessory
|-- Accessory Info Service
| |-- Identify Characteristic
| |-- Manufacturer Characteristic
| |-- Model Characteristic
| |-- Name Characteristic
| |-- Serial Characteristic
|
|-- * Service
| |-- * Characteristic

HomeKit accessories are container for services. Every accessory must provide the `Accessory Information Service`. Every service provides one or more characteristics (a characteristic might be the power state of an outlet). HomeKit has predefined service and characteristic types, which are supported by iOS. You can define your own service and characteristic types, but it's recommended to use predefined ones.

This library provides all HomeKit characteristics (see `characteristic` package) and services (see `service` package).
You can also find common accessory types like lightbulbs, outlets, thermostats in the `accessory` package.

## Dependencies

HomeControl depends on the following libraries
Expand All @@ -120,37 +141,6 @@ HomeControl depends on the following libraries
- `github.com/gosexy/to` for type conversion
- `github.com/oleksandr/bonjour` for mDNS

## HomeKit Accessories

HomeControl currently supports the following accessory types

- Switch
- Outlet
- Light Bulb
- Thermostat
- Thermometer (same as the Thermostat accessory which just readonly services)

The metdata dump in iOS 8.3 (found by [@KhaosT](https://twitter.com/khaost/status/567621750494474241)) includes a list of required and optional characteristics.

<table>
<tr><th>Service</th><th>Required</th><th>Optional</th><tr>
<tr><td>Accessory Information</td><td>name, manufacturer, model, serial-number, identify</td><td>firmware.revision, hardware.revision, software.revision</td><tr>
<tr><td>Switch</td><td>on</td><td>name</td><tr>
<tr><td>Outlet</td><td>on, outlet-in-use</td><td>name</td><tr>
<tr><td>Fan</td><td>on</td><td>name, rotation.direction, rotation.speed</td><tr>
<tr><td>Thermostat</td><td>heating-cooling.current, heating-cooling.target, temperature.current, temperature.target, temperature.units</td><td>name, relative-humidity.current, relative-humidity.target, temperature.cooling-threshold, temperature.heating-threshold</td><tr>
<tr><td>Garage Door Opener</td><td>door-state.current, door-state.target, obstruction-detected</td><td>lock-mechanism.current-state, lock-mechanism.target-state, name</td><tr>
<tr><td>Light Bulb</td><td>on</td><td>name, brightness, hue, saturation</td><tr>
<tr><td>Lock Management</td><td>version, lock-management.control-point</td><td>administrator-only-access, audio-feedback, door-state.current, lock-management.auto-secure-timeout, lock-mechanism.last-known-action, logs, motion-detected</td><tr>
<tr><td>Lock Mechanism</td><td>lock-mechanism.current-state, lock-mechanism.target-state</td><td>name</td><tr>
</table>

The HomeKit framework on iOS uses the same order as in the json. I assume that clients displays them in the same order to the user.

### iOS 9

iOS 9 supports new type of accessories and includes new service and characteristic types. The new types are already available in *model/service/constants.go* and *model/characteristic/constants.go*.

# Contact

Matthias Hochgatterer
Expand Down
4 changes: 2 additions & 2 deletions _example/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func pairVerify(b io.Reader) (io.Reader, error) {
}

func sendTLV8(b io.Reader, endpoint string) (io.Reader, error) {
url := fmt.Sprintf("http://127.0.0.1:49624/%s", endpoint)
url := fmt.Sprintf("http://127.0.0.1:64521/%s", endpoint)
resp, err := http.Post(url, netio.HTTPContentTypePairingTLV8, b)
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("Invalid status code %v", resp.StatusCode)
Expand All @@ -30,7 +30,7 @@ func sendTLV8(b io.Reader, endpoint string) (io.Reader, error) {
func main() {
database, _ := db.NewDatabase("./data")
c, _ := netio.NewDevice("Golang Client", database)
client := pair.NewSetupClientController("740-51-881", c, database)
client := pair.NewSetupClientController("336-02-620", c, database)
pairStartRequest := client.InitialPairingRequest()

pairStartResponse, err := pairSetup(pairStartRequest)
Expand Down
1 change: 1 addition & 0 deletions _example/db/66363a39303a39343a31623a31663a3639.entity
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"Name":"f6:90:94:1b:1f:69","PublicKey":"VIhNCwFtZABNorFIgprhRk/zxcTD8j2K9SXNv6xpl98=","PrivateKey":"NzA3ZjUzNGZhYzg1M2ZiMDNkOTE2MWZhZDRkZTBmZTFUiE0LAW1kAE2isUiCmuFGT/PFxMPyPYr1Jc2/rGmX3w=="}
1 change: 1 addition & 0 deletions _example/db/uuid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
f6:90:94:1b:1f:69
19 changes: 9 additions & 10 deletions _example/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,27 @@ package main

import (
"github.com/brutella/hc/hap"
"github.com/brutella/hc/model"
"github.com/brutella/hc/model/accessory"
"github.com/brutella/hc/accessory"

"log"
"time"
)

func main() {
switchInfo := model.Info{
switchInfo := accessory.Info{
Name: "Lamp",
}
sw := accessory.NewSwitch(switchInfo)
acc := accessory.NewSwitch(switchInfo)

config := hap.Config{Pin: "12344321", Port: "12345", StoragePath: "./db"}
t, err := hap.NewIPTransport(config, sw.Accessory)
t, err := hap.NewIPTransport(config, acc.Accessory)

if err != nil {
log.Fatal(err)
}

// Log to console when client (e.g. iOS app) changes the value of the on characteristic
sw.OnStateChanged(func(on bool) {
acc.Switch.On.OnValueRemoteUpdate(func(on bool) {
if on == true {
log.Println("[INFO] Client changed switch to on")
} else {
Expand All @@ -34,13 +33,13 @@ func main() {
// Periodically toggle the switch's on characteristic
go func() {
for {
on := !sw.IsOn()
on := !acc.Switch.On.GetValue()
if on == true {
log.Println("[INFO] Switch on")
log.Println("[INFO] Switch is on")
} else {
log.Println("[INFO] Switch off")
log.Println("[INFO] Switch is off")
}
sw.SetOn(on)
acc.Switch.On.SetValue(on)
time.Sleep(5 * time.Second)
}
}()
Expand Down
100 changes: 44 additions & 56 deletions model/accessory/accessory.go → accessory/accessory.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package accessory

import (
"github.com/brutella/hc/model"
"github.com/brutella/hc/model/characteristic"
"github.com/brutella/hc/model/service"
"net"
"github.com/brutella/hc/service"
)

type Info struct {
Name string
SerialNumber string
Manufacturer string
Model string
}

// Accessory implements the model.Accessory interface and contains the data
// structures to communicate with HomeKit.
//
Expand All @@ -17,30 +21,54 @@ type Accessory struct {
ID int64 `json:"aid"`
Services []*service.Service `json:"services"`

Type AccessoryType `json:"-"`
Info *service.AccessoryInfo `json:"-"`
Type AccessoryType `json:"-"`
Info *service.AccessoryInformation `json:"-"`

idCount int64
onIdentify func()
}

// New returns an accessory which implements model.Accessory.
func New(info model.Info, t AccessoryType) *Accessory {
i := service.NewInfo(info)
a := &Accessory{
func New(info Info, typ AccessoryType) *Accessory {
svc := service.NewAccessoryInformation()

if name := info.Name; len(name) > 0 {
svc.Name.SetValue(name)
} else {
svc.Name.SetValue("undefined")
}

if serial := info.SerialNumber; len(serial) > 0 {
svc.SerialNumber.SetValue(serial)
} else {
svc.SerialNumber.SetValue("undefined")
}

if manufacturer := info.Manufacturer; len(manufacturer) > 0 {
svc.Manufacturer.SetValue(manufacturer)
} else {
svc.Manufacturer.SetValue("undefined")
}

if model := info.Model; len(model) > 0 {
svc.Model.SetValue(model)
} else {
svc.Model.SetValue("undefined")
}

acc := &Accessory{
idCount: 1,
Info: i,
ID: model.InvalidID,
Type: t,
Info: svc,
Type: typ,
}

a.AddService(i.Service)
acc.AddService(acc.Info.Service)

i.Identify.OnConnChange(func(conn net.Conn, c *characteristic.Characteristic, new, old interface{}) {
a.Identify()
svc.Identify.OnValueRemoteUpdate(func(value bool) {
acc.Identify()
})

return a
return acc
}

func (a *Accessory) SetID(id int64) {
Expand All @@ -59,46 +87,6 @@ func (a *Accessory) GetServices() []*service.Service {
return result
}

func (a *Accessory) Name() string {
return a.Info.Name.Name()
}

func (a *Accessory) SerialNumber() string {
return a.Info.Serial.SerialNumber()
}

func (a *Accessory) Manufacturer() string {
return a.Info.Manufacturer.Manufacturer()
}

func (a *Accessory) Model() string {
return a.Info.Model.Model()
}

func (a *Accessory) Firmware() string {
firmware := a.Info.Firmware
if firmware != nil {
return firmware.Revision()
}
return ""
}

func (a *Accessory) Hardware() string {
hardware := a.Info.Hardware
if hardware != nil {
return hardware.Revision()
}
return ""
}

func (a *Accessory) Software() string {
software := a.Info.Software
if software != nil {
return software.Revision()
}
return ""
}

func (a *Accessory) OnIdentify(fn func()) {
a.onIdentify = fn
}
Expand Down
2 changes: 1 addition & 1 deletion model/accessory/constant.go → accessory/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const (
TypeBridge AccessoryType = 2
TypeFan AccessoryType = 3
TypeGarageDoorOpener AccessoryType = 4
TypeLightBulb AccessoryType = 5
TypeLightbulb AccessoryType = 5
TypeDoorLock AccessoryType = 6
TypeOutlet AccessoryType = 7
TypeSwitch AccessoryType = 8
Expand Down
Loading

0 comments on commit 7c8a170

Please sign in to comment.