forked from vshmoylov/golang-tuya-api
-
Notifications
You must be signed in to change notification settings - Fork 0
/
net.go
183 lines (170 loc) · 4.66 KB
/
net.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Copyright 2019 py60800.
// Use of this source code is governed by Apache-2 licence
// license that can be found in the LICENSE file.
// Tuya low level communication library
package tuya
import (
"io"
"log"
"net"
"time"
)
// -------------------------------
type query struct {
cmd int
data []byte
}
//
// helpers
func ui2b(v uint, n int) []byte {
b := make([]byte, n)
for v > 0 {
n = n - 1
b[n] = byte(v & 0xff)
v = v >> 8
}
return b
}
func uiRd(b []byte) uint {
r := uint(0)
for i := 0; i < 4; i++ {
r = r<<8 | (uint(b[i]) & 0xff)
}
return r
}
// -------------------------------
// receiver coroutine run continuously until communication error
// ccmd chan is used to signal a crash
func (a *Appliance) tcpReceiver(cnx net.Conn, ccmd chan int) {
var bye = func() {
ccmd <- 0
}
defer bye()
var err error
for {
header := make([]byte, 4*4)
if _, err = io.ReadFull(cnx, header); err != nil {
log.Println("Rcv error:", err)
return
}
if int(uiRd(header)) != 0x55aa {
log.Println("Sync error:", err)
return
}
code := int(uiRd(header[8:]))
sz := int(uiRd(header[12:]))
if sz > 10000 {
log.Println("Dubious big response")
return
}
response := make([]byte, sz)
if _, err = io.ReadFull(cnx, response); err != nil {
log.Println("Read failed", err)
return
}
a.processResponse(code, response[:sz-8])
}
}
// -------------------------------
// Create a connection,
// waits for the IP broadcast by the appliance(first time)
// connects
// retries 3 times in case of failer
func (a *Appliance) getConnection(rcvFailed chan int) (net.Conn, error) {
var cnx net.Conn = nil
var err error
var addr string
for ctrial := 0; cnx == nil && ctrial < 3; ctrial++ {
// get IP (that can change)
a.cnxMutex.Lock()
for len(a.Ip) == 0 {
a.cnxSignal.Wait()
}
addr = a.Ip + ":6668"
a.cnxMutex.Unlock()
cnx, err = net.DialTimeout("tcp", addr, time.Second*5)
if err == nil {
go a.tcpReceiver(cnx, rcvFailed)
return cnx, nil
break
} else {
time.Sleep(3 * time.Second)
cnx = nil
}
}
log.Printf("Connection to <%v> failed %v\n", addr, err)
return nil, err
}
// -------------------------------
// Master TCP connection coroutine
// Receives messages to be sent from query channel
func (a *Appliance) tcpConnManager(c chan query) {
var cnx net.Conn
var err error
rcvFailed := make(chan int)
cnx, _ = a.getConnection(rcvFailed)
q := query{CodeMsgStatus, a.initialStatusMsg()}
for {
// Status message is sent the first time => send it before retrieving next cmd
sendloop:
for trial := 0; trial < 3; trial++ {
if cnx == nil {
cnx, err = a.getConnection(rcvFailed)
if cnx == nil {
break sendloop // Connection failed
}
}
err = tcpSend(cnx, q.cmd, q.data)
if err != nil {
cnx.Close() // => Receiver will "crash"
<-rcvFailed // wait for receiver crash confirm
cnx = nil
} else {
//Success!
break sendloop
}
}
loop:
// wait for something to do
for {
select {
case q = <-c: // New message to be sent
break loop
case <-rcvFailed:
// Read error : Receive thread aborted => Need reconnection
// No hurry => wait for another message or ping
cnx.Close()
cnx = nil
case <-time.After(15 * time.Second):
// Send a Ping message when nothing occurs
if cnx != nil {
q = query{CodeMsgPing, []byte{}}
} else {
// Broken connection => restart with a status message
q = query{CodeMsgStatus, a.initialStatusMsg()}
}
break loop
}
}
}
}
// -------------------------------
// sends a message over TCP
func tcpSend(cnx net.Conn, cmd int, data []byte) error {
// simple appliances are expected to reply quickly
now := time.Now()
cnx.SetWriteDeadline(now.Add(10 * time.Second))
// tuya appliances cannot handle multiple read!!
// => fill a buffer and write it
b := make([]byte, 0, 16)
b = append(b, ui2b(uint(0x55aa), 4)...)
b = append(b, ui2b(uint(cmd), 8)...)
b = append(b, ui2b(uint(len(data)+8), 4)...)
b = append(b, data...)
b = append(b, ui2b(uint(0xaa55), 8)...)
if _, err := cnx.Write(b); err != nil {
log.Println(err)
return err
}
return nil
}