mirror of
https://github.com/AlexxIT/go2rtc.git
synced 2025-10-05 16:26:50 +08:00
174 lines
3.3 KiB
Go
174 lines
3.3 KiB
Go
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
|
|
}
|