-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathraw.go
123 lines (98 loc) · 2.45 KB
/
raw.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
package yeelight
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"net"
"os"
)
// CallResponse contains raw response from device.
type CallResponse struct {
ID int `json:"id"`
Result json.RawMessage `json:"result"`
Error json.RawMessage `json:"error"`
}
// ToError checks if the answer contains an error.
func (rr CallResponse) ToError() error {
if rr.Error == nil {
return nil
}
if bytes.Equal(rr.Error, json.RawMessage(`{"code":-1, "message":"method not supported"}`)) {
return ErrMethodNotSupported
}
return UnknownError(rr.Error)
}
// Bind response result to provided variable
func (rr CallResponse) Bind(target interface{}) error {
err := json.Unmarshal(rr.Result, target)
if err != nil {
return ErrResponseJSONSyntax
}
return nil
}
// Call method of device with provided parameters
func (c Client) Call(ctx context.Context, method string, params ...interface{}) (CallResponse, error) {
if params == nil {
params = []interface{}{}
}
// TODO: remove hardcoded id
payload := map[string]interface{}{"id": 123, "method": method, "params": params}
b, err := json.Marshal(payload)
if err != nil {
return CallResponse{}, err
}
r, err := c.transport(ctx, c.host, string(b))
if err != nil {
return CallResponse{}, err
}
var target CallResponse
if err := json.Unmarshal(r, &target); err != nil {
return CallResponse{}, ErrResponseJSONSyntax
}
return target, nil
}
func (c Client) rawWithOk(ctx context.Context, method string, params ...interface{}) error {
d, err := c.Call(ctx, method, params...)
if err != nil {
return err
}
return d.ToError()
}
func defaultTransport(ctx context.Context, host string, raw string) ([]byte, error) {
const crlf = "\r\n"
var d net.Dialer
conn, err := d.DialContext(ctx, "tcp", host)
if err != nil {
return nil, processDialError(err)
}
defer conn.Close()
if _, err := fmt.Fprint(conn, raw+crlf); err != nil {
return nil, err
}
res, err := bufio.NewReader(conn).ReadBytes('\n')
if err != nil {
return nil, fmt.Errorf("cannot read command result %w", err)
}
return res, nil
}
func processDialError(err error) error {
e, ok := err.(*net.OpError)
if !ok {
// return as isRaw
return err
}
if ae, ok := e.Err.(*net.AddrError); ok {
if ae.Err == "missing port in address" {
return ErrMissingPortInAddress
}
}
if se, ok := e.Err.(*os.SyscallError); ok {
if se.Syscall == "connect" {
return ErrConnect
}
}
// return as isRaw
return err
}