Skip to content

Instantly share code, notes, and snippets.

@nickrw
Last active January 12, 2018 00:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nickrw/c99b13920fa511a209ddec15cd4c7696 to your computer and use it in GitHub Desktop.
Save nickrw/c99b13920fa511a209ddec15cd4c7696 to your computer and use it in GitHub Desktop.
A curtains HomeKit Accessory hello world using homecontrol https://github.com/brutella/hc
package main
import (
"fmt"
"log"
"sync"
"time"
"github.com/brutella/hc"
"github.com/brutella/hc/accessory"
"github.com/brutella/hc/characteristic"
"github.com/brutella/hc/service"
)
type Motor interface {
GetPosition() int
GetTargetPosition() int
SetTargetPosition(int)
GetState() int
SubscribeGetPosition(func(int))
SubscribeGetState(func(int))
Shutdown()
}
type MotorImpl struct {
position int
targetPosition int
state int
positionCallback func(int)
stateCallback func(int)
mutex *sync.Mutex
done chan struct{}
confirmDone chan struct{}
}
func (m *MotorImpl) GetPosition() int {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.position
}
func (m *MotorImpl) GetTargetPosition() int {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.targetPosition
}
func (m *MotorImpl) SetTargetPosition(i int) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.targetPosition = i
}
func (m *MotorImpl) GetState() int {
m.mutex.Lock()
defer m.mutex.Unlock()
return m.position
}
func (m *MotorImpl) SubscribeGetPosition(f func(int)) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.positionCallback = f
}
func (m *MotorImpl) SubscribeGetState(f func(int)) {
m.mutex.Lock()
defer m.mutex.Unlock()
m.stateCallback = f
}
func (m *MotorImpl) Shutdown() {
m.done <- struct{}{}
<-m.confirmDone
}
func (m *MotorImpl) tick() {
m.mutex.Lock()
defer m.mutex.Unlock()
var (
newPos int
stop bool
)
switch {
case m.targetPosition > m.position:
if m.state != 1 {
m.state = 1
if m.stateCallback != nil {
go m.stateCallback(m.state)
}
}
case m.targetPosition < m.position:
if m.state != -1 {
m.state = -1
if m.stateCallback != nil {
go m.stateCallback(m.state)
}
}
default:
if m.state != 0 {
m.state = 0
if m.stateCallback != nil {
go m.stateCallback(m.state)
}
}
return
}
newPos = m.position + m.state
switch {
case newPos > 100:
newPos = 100
stop = true
case newPos < 0:
newPos = 0
stop = true
case newPos == m.targetPosition:
stop = true
}
if newPos != m.position {
m.position = newPos
if m.positionCallback != nil {
go m.positionCallback(newPos)
}
}
if stop {
m.state = 0
if m.stateCallback != nil {
go m.stateCallback(m.state)
}
}
}
func (m *MotorImpl) Run() {
ticker := time.Tick(time.Millisecond * 250)
for {
select {
case <-ticker:
m.tick()
case <-m.done:
m.mutex.Lock()
defer m.mutex.Unlock()
m.confirmDone <- struct{}{}
return
}
}
}
func NewMotorImpl() *MotorImpl {
m := &MotorImpl{}
m.mutex = &sync.Mutex{}
m.done = make(chan struct{})
m.confirmDone = make(chan struct{})
go m.Run()
return m
}
type Curtain struct {
*accessory.Accessory
WindowCovering *service.WindowCovering
Motor Motor
}
func NewCurtain(info accessory.Info) *Curtain {
acc := Curtain{}
acc.Motor = NewMotorImpl()
acc.Accessory = accessory.New(info, accessory.TypeWindowCovering)
acc.WindowCovering = service.NewWindowCovering()
acc.AddService(acc.WindowCovering.Service)
acc.WindowCovering.CurrentPosition.SetValue(0)
acc.WindowCovering.PositionState.SetValue(characteristic.PositionStateStopped)
acc.Motor.SubscribeGetPosition(func(i int) {
fmt.Printf("[%s] Now at position: %d\n", acc.Info.Name.GetValue(), i)
acc.WindowCovering.CurrentPosition.SetValue(i)
})
acc.Motor.SubscribeGetState(func(i int) {
tr := map[int]int{
-1: characteristic.PositionStateDecreasing,
0: characteristic.PositionStateIncreasing,
1: characteristic.PositionStateStopped,
}
value, _ := tr[i]
acc.WindowCovering.PositionState.SetValue(value)
if i == 0 {
fmt.Printf("[%s] Stopped as position: %d\n", acc.Info.Name.GetValue(), acc.Motor.GetPosition())
}
})
acc.WindowCovering.TargetPosition.OnValueRemoteUpdate(func(i int) {
fmt.Printf("[%s] New target position: %d\n", acc.Info.Name.GetValue(), i)
acc.Motor.SetTargetPosition(i)
})
return &acc
}
func main() {
west := NewCurtain(accessory.Info{
Name: "West Facing Curtains",
})
north := NewCurtain(accessory.Info{
Name: "North Facing Curtains",
})
config := hc.Config{Pin: "12341234"}
t, err := hc.NewIPTransport(config, north.Accessory, west.Accessory)
if err != nil {
log.Panic(err)
}
hc.OnTermination(func() {
<-t.Stop()
west.Motor.Shutdown()
north.Motor.Shutdown()
})
t.Start()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment