Last active
January 12, 2018 00:01
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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