-
Notifications
You must be signed in to change notification settings - Fork 290
/
client.go
173 lines (141 loc) · 3.28 KB
/
client.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
package iot
import (
"crypto/md5"
"crypto/tls"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/AlexxIT/go2rtc/pkg/mqtt"
"github.com/rs/zerolog/log"
"net"
"net/rpc"
"net/url"
"time"
)
type Codec struct {
mqtt *mqtt.Client
devTopic string
devKey string
body json.RawMessage
}
type dps struct {
Dps struct {
Req string `json:"101,omitempty"`
Res string `json:"102,omitempty"`
} `json:"dps"`
T uint32 `json:"t"`
}
type response struct {
ID uint64 `json:"id"`
Result json.RawMessage `json:"result"`
Error struct {
Code int `json:"code"`
Message string `json:"message"`
} `json:"error"`
}
func (c *Codec) WriteRequest(r *rpc.Request, v any) error {
if v == nil {
v = "[]"
}
ts := uint32(time.Now().Unix())
msg := dps{T: ts}
msg.Dps.Req = fmt.Sprintf(
`{"id":%d,"method":"%s","params":%s}`, r.Seq, r.ServiceMethod, v,
)
payload, err := json.Marshal(msg)
if err != nil {
return err
}
log.Printf("[roborock] send: %s", payload)
payload = c.Encrypt(payload, ts, ts, ts)
return c.mqtt.Publish("rr/m/i/"+c.devTopic, payload)
}
func (c *Codec) ReadResponseHeader(r *rpc.Response) error {
for {
// receive any message from MQTT
_, payload, err := c.mqtt.Read()
if err != nil {
return err
}
// skip if it is not PUBLISH message
if payload == nil {
continue
}
// decrypt MQTT PUBLISH payload
if payload, err = c.Decrypt(payload); err != nil {
continue
}
// skip if we can't decrypt this payload (ex. binary payload)
if payload == nil {
continue
}
log.Printf("[roborock] recv %s", payload)
// get content from response payload:
// {"t":1676871268,"dps":{"102":"{\"id\":315003,\"result\":[\"ok\"]}"}}
var msg dps
if err = json.Unmarshal(payload, &msg); err != nil {
continue
}
var res response
if err = json.Unmarshal([]byte(msg.Dps.Res), &res); err != nil {
continue
}
r.Seq = res.ID
if res.Error.Code != 0 {
r.Error = res.Error.Message
} else {
c.body = res.Result
}
return nil
}
}
func (c *Codec) ReadResponseBody(v any) error {
switch vv := v.(type) {
case *[]byte:
*vv = c.body
case *string:
*vv = string(c.body)
case *bool:
*vv = string(c.body) == `["ok"]`
}
return nil
}
func (c *Codec) Close() error {
return c.mqtt.Close()
}
func Dial(rawURL string) (*rpc.Client, error) {
link, err := url.Parse(rawURL)
if err != nil {
return nil, err
}
// dial to MQTT
conn, err := net.DialTimeout("tcp", link.Host, time.Second*5)
if err != nil {
return nil, err
}
// process MQTT SSL
conf := &tls.Config{ServerName: link.Hostname()}
sconn := tls.Client(conn, conf)
if err = sconn.Handshake(); err != nil {
return nil, err
}
query := link.Query()
// send MQTT login
uk := md5.Sum([]byte(query.Get("u") + ":" + query.Get("k")))
sk := md5.Sum([]byte(query.Get("s") + ":" + query.Get("k")))
user := hex.EncodeToString(uk[1:5])
pass := hex.EncodeToString(sk[8:])
c := &Codec{
mqtt: mqtt.NewClient(sconn),
devKey: query.Get("key"),
devTopic: query.Get("u") + "/" + user + "/" + query.Get("did"),
}
if err = c.mqtt.Connect("com.roborock.smart:mbrriot", user, pass); err != nil {
return nil, err
}
// subscribe on device topic
if err = c.mqtt.Subscribe("rr/m/o/" + c.devTopic); err != nil {
return nil, err
}
return rpc.NewClientWithCodec(c), nil
}