-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathheos.go
147 lines (123 loc) · 2.9 KB
/
heos.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package heosapi
import (
"bufio"
"fmt"
"net"
"github.com/pkg/errors"
)
// Heos is low-level API for communicating with Denon HEOS speaker family
type Heos struct {
conn net.Conn
host string
}
// NewHeos returns Heos instancec
func NewHeos(host string) Heos {
var heos Heos
heos.host = host
return heos
}
// Connect initialises connection to the speaker
func (heos *Heos) Connect() error {
conn, err := newConn(heos.host)
if err != nil {
return err
}
heos.conn = conn
return nil
}
func newConn(host string) (net.Conn, error) {
return net.Dial("tcp", host)
}
// Disconnect disconnects from the speaker
func (heos *Heos) Disconnect() error {
return heos.conn.Close()
}
// Send sends given command with parameters to the speaker
func (heos *Heos) Send(cmd Command, params map[string]string) (Response, error) {
_, err := fmt.Fprintf(heos.conn, "heos://%s/%s?%s\r\n", cmd.Group, cmd.Command, paramsToStr(params))
if err != nil {
return Response{}, err
}
return heos.readNextResp()
}
func responseSplit(data []byte, atEOF bool) (advance int, token []byte, err error) {
for i := 0; i < len(data); i++ {
if len(data) < i+1 {
return 0, nil, nil
}
if data[i] == '\r' && data[i+1] == '\n' {
return i + 2, data[:i], nil
}
}
return 0, data, bufio.ErrFinalToken
}
func (heos *Heos) readNextResp() (Response, error) {
scanner := bufio.NewScanner(heos.conn)
scanner.Split(responseSplit)
if !scanner.Scan() {
return Response{}, fmt.Errorf("no response")
}
if err := scanner.Err(); err != nil {
return Response{}, errors.Wrap(err, "reading input")
}
return parseResponse([]byte(scanner.Text()))
}
// EventStream provides channel with responses from the speaker.
// Also provides error channel
func (heos *Heos) EventStream() (<-chan Response, <-chan error) {
errCh := make(chan error, 1)
ch := make(chan Response)
success := true
var resp Response
var err error
newHeos := NewHeos(heos.host)
if connErr := newHeos.Connect(); err != nil {
errCh <- connErr
goto done
}
resp, err = newHeos.Send(Command{
Group: "system",
Command: "register_for_change_events",
}, map[string]string{
"enable": "on",
})
if err != nil {
errCh <- err
goto done
}
if resp.Heos.Result == "fail" {
respErr, ok := resp.Heos.Message["text"]
if !ok {
errCh <- fmt.Errorf("register_for_change_events failed")
goto done
}
errCh <- fmt.Errorf("register_for_change_events failed: %s", respErr)
goto done
}
go eventStream(newHeos, ch, errCh)
success = true
done:
if !success {
close(errCh)
}
return ch, errCh
}
func eventStream(heos Heos, ch chan<- Response, errCh chan<- error) {
scanner := bufio.NewScanner(heos.conn)
scanner.Split(responseSplit)
for scanner.Scan() {
resp, err := parseResponse([]byte(scanner.Text()))
if err != nil {
errCh <- err
goto done
}
ch <- resp
}
if err := scanner.Err(); err != nil {
errCh <- err
goto done
}
done:
close(ch)
close(errCh)
}