This commit is contained in:
seydx
2025-05-12 22:55:55 +02:00
parent 05c12b34e5
commit 6d8d6a91ef
6 changed files with 300 additions and 300 deletions
+1 -1
View File
@@ -89,7 +89,7 @@ func main() {
roborock.Init() // roborock source roborock.Init() // roborock source
homekit.Init() // homekit source homekit.Init() // homekit source
ring.Init() // ring source ring.Init() // ring source
tuya.Init() // tuya source tuya.Init() // tuya source
nest.Init() // nest source nest.Init() // nest source
bubble.Init() // bubble source bubble.Init() // bubble source
expr.Init() // expr source expr.Init() // expr source
+1 -1
View File
@@ -62,7 +62,7 @@ func ServiceCameraRTPStreamManagement() *hap.Service {
VideoAttrs: []VideoAttrs{ VideoAttrs: []VideoAttrs{
{Width: 1920, Height: 1080, Framerate: 30}, {Width: 1920, Height: 1080, Framerate: 30},
{Width: 1280, Height: 720, Framerate: 30}, // important for iPhones {Width: 1280, Height: 720, Framerate: 30}, // important for iPhones
{Width: 320, Height: 240, Framerate: 15}, // apple watch {Width: 320, Height: 240, Framerate: 15}, // apple watch
}, },
}, },
}, },
+1 -1
View File
@@ -292,7 +292,7 @@ func dial(req *http.Request, brand, username, password string) (net.Conn, *http.
return nil, nil, err return nil, nil, err
} }
_, _ = io.Copy(io.Discard, res.Body) // discard leftovers _, _ = io.Copy(io.Discard, res.Body) // discard leftovers
_ = res.Body.Close() // ignore response body _ = res.Body.Close() // ignore response body
auth := res.Header.Get("WWW-Authenticate") auth := res.Header.Get("WWW-Authenticate")
+235 -235
View File
@@ -17,146 +17,146 @@ import (
) )
type TuyaClient struct { type TuyaClient struct {
httpClient *http.Client httpClient *http.Client
mqtt *TuyaMQTT mqtt *TuyaMQTT
apiURL string apiURL string
rtspURL string rtspURL string
hlsURL string hlsURL string
sessionId string sessionId string
clientId string clientId string
clientSecret string clientSecret string
deviceId string deviceId string
accessToken string accessToken string
refreshToken string refreshToken string
expireTime int64 expireTime int64
uid string uid string
motoId string motoId string
auth string auth string
skill *Skill skill *Skill
iceServers []pionWebrtc.ICEServer iceServers []pionWebrtc.ICEServer
medias []*core.Media medias []*core.Media
hasBackchannel bool hasBackchannel bool
} }
type Token struct { type Token struct {
UID string `json:"uid"` UID string `json:"uid"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
ExpireTime int64 `json:"expire_time"` ExpireTime int64 `json:"expire_time"`
} }
type AudioAttributes struct { type AudioAttributes struct {
CallMode []int `json:"call_mode"` // 1 = one way, 2 = two way CallMode []int `json:"call_mode"` // 1 = one way, 2 = two way
HardwareCapability []int `json:"hardware_capability"` // 1 = mic, 2 = speaker HardwareCapability []int `json:"hardware_capability"` // 1 = mic, 2 = speaker
} }
type OpenApiICE struct { type OpenApiICE struct {
Urls string `json:"urls"` Urls string `json:"urls"`
Username string `json:"username"` Username string `json:"username"`
Credential string `json:"credential"` Credential string `json:"credential"`
TTL int `json:"ttl"` TTL int `json:"ttl"`
} }
type WebICE struct { type WebICE struct {
Urls string `json:"urls"` Urls string `json:"urls"`
Username string `json:"username,omitempty"` Username string `json:"username,omitempty"`
Credential string `json:"credential,omitempty"` Credential string `json:"credential,omitempty"`
} }
type P2PConfig struct { type P2PConfig struct {
Ices []OpenApiICE `json:"ices"` Ices []OpenApiICE `json:"ices"`
} }
type Skill struct { type Skill struct {
WebRTC int `json:"webrtc"` WebRTC int `json:"webrtc"`
Audios []struct { Audios []struct {
Channels int `json:"channels"` Channels int `json:"channels"`
DataBit int `json:"dataBit"` DataBit int `json:"dataBit"`
CodecType int `json:"codecType"` CodecType int `json:"codecType"`
SampleRate int `json:"sampleRate"` SampleRate int `json:"sampleRate"`
} `json:"audios"` } `json:"audios"`
Videos []struct { Videos []struct {
StreamType int `json:"streamType"` // streamType = 2 => main stream - streamType = 4 => sub stream StreamType int `json:"streamType"` // streamType = 2 => main stream - streamType = 4 => sub stream
ProfileId string `json:"profileId"` ProfileId string `json:"profileId"`
Width int `json:"width"` Width int `json:"width"`
CodecType int `json:"codecType"` CodecType int `json:"codecType"`
SampleRate int `json:"sampleRate"` SampleRate int `json:"sampleRate"`
Height int `json:"height"` Height int `json:"height"`
} `json:"videos"` } `json:"videos"`
} }
type WebRTConfig struct { type WebRTConfig struct {
AudioAttributes AudioAttributes `json:"audio_attributes"` AudioAttributes AudioAttributes `json:"audio_attributes"`
Auth string `json:"auth"` Auth string `json:"auth"`
ID string `json:"id"` ID string `json:"id"`
MotoID string `json:"moto_id"` MotoID string `json:"moto_id"`
P2PConfig P2PConfig `json:"p2p_config"` P2PConfig P2PConfig `json:"p2p_config"`
ProtocolVersion string `json:"protocol_version"` ProtocolVersion string `json:"protocol_version"`
Skill string `json:"skill"` Skill string `json:"skill"`
SupportsWebRTCRecord bool `json:"supports_webrtc_record"` SupportsWebRTCRecord bool `json:"supports_webrtc_record"`
SupportsWebRTC bool `json:"supports_webrtc"` SupportsWebRTC bool `json:"supports_webrtc"`
VedioClaritiy int `json:"vedio_clarity"` VedioClaritiy int `json:"vedio_clarity"`
VideoClaritiy int `json:"video_clarity"` VideoClaritiy int `json:"video_clarity"`
VideoClarities []int `json:"video_clarities"` VideoClarities []int `json:"video_clarities"`
} }
type OpenIoTHubConfig struct { type OpenIoTHubConfig struct {
Url string `json:"url"` Url string `json:"url"`
ClientID string `json:"client_id"` ClientID string `json:"client_id"`
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
SinkTopic struct { SinkTopic struct {
IPC string `json:"ipc"` IPC string `json:"ipc"`
} `json:"sink_topic"` } `json:"sink_topic"`
SourceSink struct { SourceSink struct {
IPC string `json:"ipc"` IPC string `json:"ipc"`
} `json:"source_topic"` } `json:"source_topic"`
ExpireTime int `json:"expire_time"` ExpireTime int `json:"expire_time"`
} }
type WebRTCConfigResponse struct { type WebRTCConfigResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Result WebRTConfig `json:"result"` Result WebRTConfig `json:"result"`
Msg string `json:"msg,omitempty"` Msg string `json:"msg,omitempty"`
Code int `json:"code,omitempty"` Code int `json:"code,omitempty"`
} }
type TokenResponse struct { type TokenResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Result Token `json:"result"` Result Token `json:"result"`
Msg string `json:"msg,omitempty"` Msg string `json:"msg,omitempty"`
Code int `json:"code,omitempty"` Code int `json:"code,omitempty"`
} }
type AllocateRequest struct { type AllocateRequest struct {
Type string `json:"type"` Type string `json:"type"`
} }
type AllocateResponse struct { type AllocateResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Result struct { Result struct {
URL string `json:"url"` URL string `json:"url"`
} `json:"result"` } `json:"result"`
Msg string `json:"msg,omitempty"` Msg string `json:"msg,omitempty"`
Code int `json:"code,omitempty"` Code int `json:"code,omitempty"`
} }
type OpenIoTHubConfigRequest struct { type OpenIoTHubConfigRequest struct {
UID string `json:"uid"` UID string `json:"uid"`
UniqueID string `json:"unique_id"` UniqueID string `json:"unique_id"`
LinkType string `json:"link_type"` LinkType string `json:"link_type"`
Topics string `json:"topics"` Topics string `json:"topics"`
} }
type OpenIoTHubConfigResponse struct { type OpenIoTHubConfigResponse struct {
Success bool `json:"success"` Success bool `json:"success"`
Result OpenIoTHubConfig `json:"result"` Result OpenIoTHubConfig `json:"result"`
Msg string `json:"msg,omitempty"` Msg string `json:"msg,omitempty"`
Code int `json:"code,omitempty"` Code int `json:"code,omitempty"`
} }
const ( const (
defaultTimeout = 5 * time.Second defaultTimeout = 5 * time.Second
) )
func NewTuyaClient(openAPIURL string, deviceId string, uid string, clientId string, clientSecret string, streamMode string) (*TuyaClient, error) { func NewTuyaClient(openAPIURL string, deviceId string, uid string, clientId string, clientSecret string, streamMode string) (*TuyaClient, error) {
@@ -164,7 +164,7 @@ func NewTuyaClient(openAPIURL string, deviceId string, uid string, clientId stri
httpClient: &http.Client{Timeout: defaultTimeout}, httpClient: &http.Client{Timeout: defaultTimeout},
mqtt: &TuyaMQTT{waiter: core.Waiter{}}, mqtt: &TuyaMQTT{waiter: core.Waiter{}},
apiURL: openAPIURL, apiURL: openAPIURL,
sessionId: core.RandString(6, 62), sessionId: core.RandString(6, 62),
clientId: clientId, clientId: clientId,
deviceId: deviceId, deviceId: deviceId,
clientSecret: clientSecret, clientSecret: clientSecret,
@@ -202,7 +202,7 @@ func (c *TuyaClient) Close() {
c.httpClient.CloseIdleConnections() c.httpClient.CloseIdleConnections()
} }
func(c *TuyaClient) Request(method string, url string, body any) ([]byte, error) { func (c *TuyaClient) Request(method string, url string, body any) ([]byte, error) {
var bodyReader io.Reader var bodyReader io.Reader
if body != nil { if body != nil {
jsonBody, err := json.Marshal(body) jsonBody, err := json.Marshal(body)
@@ -249,7 +249,7 @@ func(c *TuyaClient) Request(method string, url string, body any) ([]byte, error)
return res, nil return res, nil
} }
func(c *TuyaClient) InitToken() (err error) { func (c *TuyaClient) InitToken() (err error) {
url := fmt.Sprintf("https://%s/v1.0/token?grant_type=1", c.apiURL) url := fmt.Sprintf("https://%s/v1.0/token?grant_type=1", c.apiURL)
c.accessToken = "" c.accessToken = ""
@@ -259,7 +259,7 @@ func(c *TuyaClient) InitToken() (err error) {
if err != nil { if err != nil {
return err return err
} }
var tokenResponse TokenResponse var tokenResponse TokenResponse
err = json.Unmarshal(body, &tokenResponse) err = json.Unmarshal(body, &tokenResponse)
if err != nil { if err != nil {
@@ -277,139 +277,139 @@ func(c *TuyaClient) InitToken() (err error) {
return nil return nil
} }
func(c *TuyaClient) InitDevice() (err error) { func (c *TuyaClient) InitDevice() (err error) {
url := fmt.Sprintf("https://%s/v1.0/users/%s/devices/%s/webrtc-configs", c.apiURL, c.uid, c.deviceId) url := fmt.Sprintf("https://%s/v1.0/users/%s/devices/%s/webrtc-configs", c.apiURL, c.uid, c.deviceId)
body, err := c.Request("GET", url, nil) body, err := c.Request("GET", url, nil)
if err != nil { if err != nil {
return err return err
} }
var webRTCConfigResponse WebRTCConfigResponse var webRTCConfigResponse WebRTCConfigResponse
err = json.Unmarshal(body, &webRTCConfigResponse) err = json.Unmarshal(body, &webRTCConfigResponse)
if err != nil { if err != nil {
return err return err
} }
if !webRTCConfigResponse.Success { if !webRTCConfigResponse.Success {
return fmt.Errorf("error: %s", webRTCConfigResponse.Msg) return fmt.Errorf("error: %s", webRTCConfigResponse.Msg)
} }
c.motoId = webRTCConfigResponse.Result.MotoID c.motoId = webRTCConfigResponse.Result.MotoID
c.auth = webRTCConfigResponse.Result.Auth c.auth = webRTCConfigResponse.Result.Auth
c.skill = &Skill{ c.skill = &Skill{
Audios: []struct { Audios: []struct {
Channels int `json:"channels"` Channels int `json:"channels"`
DataBit int `json:"dataBit"` DataBit int `json:"dataBit"`
CodecType int `json:"codecType"` CodecType int `json:"codecType"`
SampleRate int `json:"sampleRate"` SampleRate int `json:"sampleRate"`
}{}, }{},
Videos: []struct { Videos: []struct {
StreamType int `json:"streamType"` StreamType int `json:"streamType"`
ProfileId string `json:"profileId"` ProfileId string `json:"profileId"`
Width int `json:"width"` Width int `json:"width"`
CodecType int `json:"codecType"` CodecType int `json:"codecType"`
SampleRate int `json:"sampleRate"` SampleRate int `json:"sampleRate"`
Height int `json:"height"` Height int `json:"height"`
}{}, }{},
} }
if webRTCConfigResponse.Result.Skill != "" { if webRTCConfigResponse.Result.Skill != "" {
_ = json.Unmarshal([]byte(webRTCConfigResponse.Result.Skill), c.skill) _ = json.Unmarshal([]byte(webRTCConfigResponse.Result.Skill), c.skill)
} }
var audioDirection string var audioDirection string
if contains(webRTCConfigResponse.Result.AudioAttributes.CallMode, 2) && if contains(webRTCConfigResponse.Result.AudioAttributes.CallMode, 2) &&
contains(webRTCConfigResponse.Result.AudioAttributes.HardwareCapability, 1) { contains(webRTCConfigResponse.Result.AudioAttributes.HardwareCapability, 1) {
audioDirection = core.DirectionSendRecv audioDirection = core.DirectionSendRecv
c.hasBackchannel = true c.hasBackchannel = true
} else { } else {
audioDirection = core.DirectionRecvonly audioDirection = core.DirectionRecvonly
c.hasBackchannel = false c.hasBackchannel = false
} }
c.medias = make([]*core.Media, 0)
if len(c.skill.Audios) > 0 {
// Use the first Audio-Codec
audio := c.skill.Audios[0]
c.medias = append(c.medias, &core.Media{ c.medias = make([]*core.Media, 0)
Kind: core.KindAudio,
Direction: audioDirection,
Codecs: []*core.Codec{
{
Name: "PCMU",
ClockRate: uint32(audio.SampleRate),
Channels: uint8(audio.Channels),
},
},
})
} else {
// Use default values for Audio
c.medias = append(c.medias, &core.Media{
Kind: core.KindAudio,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{
Name: "PCMU",
ClockRate: uint32(8000),
Channels: uint8(1),
},
},
})
}
if len(c.skill.Videos) > 0 { if len(c.skill.Audios) > 0 {
// Use the first Video-Codec // Use the first Audio-Codec
video := c.skill.Videos[0] audio := c.skill.Audios[0]
c.medias = append(c.medias, &core.Media{ c.medias = append(c.medias, &core.Media{
Kind: core.KindVideo, Kind: core.KindAudio,
Direction: core.DirectionRecvonly, Direction: audioDirection,
Codecs: []*core.Codec{ Codecs: []*core.Codec{
{
Name: core.CodecH265,
ClockRate: uint32(video.SampleRate),
PayloadType: 96,
},
{ {
Name: core.CodecH264, Name: "PCMU",
ClockRate: uint32(video.SampleRate), ClockRate: uint32(audio.SampleRate),
PayloadType: 96, Channels: uint8(audio.Channels),
}, },
}, },
}) })
} else { } else {
// Use default values for Video // Use default values for Audio
c.medias = append(c.medias, &core.Media{ c.medias = append(c.medias, &core.Media{
Kind: core.KindVideo, Kind: core.KindAudio,
Direction: core.DirectionRecvonly, Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{ Codecs: []*core.Codec{
{ {
Name: core.CodecH264, Name: "PCMU",
ClockRate: uint32(90000), ClockRate: uint32(8000),
PayloadType: 96, Channels: uint8(1),
}, },
}, },
}) })
} }
iceServersBytes, err := json.Marshal(&webRTCConfigResponse.Result.P2PConfig.Ices) if len(c.skill.Videos) > 0 {
if err != nil { // Use the first Video-Codec
return err video := c.skill.Videos[0]
}
c.iceServers, err = webrtc.UnmarshalICEServers([]byte(iceServersBytes)) c.medias = append(c.medias, &core.Media{
if err != nil { Kind: core.KindVideo,
return err Direction: core.DirectionRecvonly,
} Codecs: []*core.Codec{
{
Name: core.CodecH265,
ClockRate: uint32(video.SampleRate),
PayloadType: 96,
},
{
Name: core.CodecH264,
ClockRate: uint32(video.SampleRate),
PayloadType: 96,
},
},
})
} else {
// Use default values for Video
c.medias = append(c.medias, &core.Media{
Kind: core.KindVideo,
Direction: core.DirectionRecvonly,
Codecs: []*core.Codec{
{
Name: core.CodecH264,
ClockRate: uint32(90000),
PayloadType: 96,
},
},
})
}
return nil iceServersBytes, err := json.Marshal(&webRTCConfigResponse.Result.P2PConfig.Ices)
if err != nil {
return err
}
c.iceServers, err = webrtc.UnmarshalICEServers([]byte(iceServersBytes))
if err != nil {
return err
}
return nil
} }
func(c *TuyaClient) GetStreamUrl(streamType string) (err error) { func (c *TuyaClient) GetStreamUrl(streamType string) (err error) {
url := fmt.Sprintf("https://%s/v1.0/devices/%s/stream/actions/allocate", c.apiURL, c.deviceId) url := fmt.Sprintf("https://%s/v1.0/devices/%s/stream/actions/allocate", c.apiURL, c.deviceId)
request := &AllocateRequest{ request := &AllocateRequest{
@@ -443,7 +443,7 @@ func(c *TuyaClient) GetStreamUrl(streamType string) (err error) {
return nil return nil
} }
func(c *TuyaClient) LoadHubConfig() (config *OpenIoTHubConfig, err error) { func (c *TuyaClient) LoadHubConfig() (config *OpenIoTHubConfig, err error) {
url := fmt.Sprintf("https://%s/v2.0/open-iot-hub/access/config", c.apiURL) url := fmt.Sprintf("https://%s/v2.0/open-iot-hub/access/config", c.apiURL)
request := &OpenIoTHubConfigRequest{ request := &OpenIoTHubConfigRequest{
@@ -467,53 +467,53 @@ func(c *TuyaClient) LoadHubConfig() (config *OpenIoTHubConfig, err error) {
if !openIoTHubConfigResponse.Success { if !openIoTHubConfigResponse.Success {
return nil, fmt.Errorf("error: %s", openIoTHubConfigResponse.Msg) return nil, fmt.Errorf("error: %s", openIoTHubConfigResponse.Msg)
} }
return &openIoTHubConfigResponse.Result, nil return &openIoTHubConfigResponse.Result, nil
} }
// Search the streamType based on the selection "main" or "sub" // Search the streamType based on the selection "main" or "sub"
func (c *TuyaClient) getStreamType(streamChoice string) uint32 { func (c *TuyaClient) getStreamType(streamChoice string) uint32 {
// Default streamType if nothing is found // Default streamType if nothing is found
defaultStreamType := uint32(1) defaultStreamType := uint32(1)
if c.skill == nil || len(c.skill.Videos) == 0 {
return defaultStreamType
}
if c.skill == nil || len(c.skill.Videos) == 0 {
return defaultStreamType
}
// Find the highest and lowest resolution // Find the highest and lowest resolution
var highestResType uint32 = defaultStreamType var highestResType uint32 = defaultStreamType
var highestRes int = 0 var highestRes int = 0
var lowestResType uint32 = defaultStreamType var lowestResType uint32 = defaultStreamType
var lowestRes int = 0 var lowestRes int = 0
for _, video := range c.skill.Videos { for _, video := range c.skill.Videos {
res := video.Width * video.Height res := video.Width * video.Height
// Highest Resolution // Highest Resolution
if res > highestRes { if res > highestRes {
highestRes = res highestRes = res
highestResType = uint32(video.StreamType) highestResType = uint32(video.StreamType)
} }
// Lower Resolution (or first if not set yet) // Lower Resolution (or first if not set yet)
if lowestRes == 0 || res < lowestRes { if lowestRes == 0 || res < lowestRes {
lowestRes = res lowestRes = res
lowestResType = uint32(video.StreamType) lowestResType = uint32(video.StreamType)
} }
} }
// Return the streamType based on the selection // Return the streamType based on the selection
switch streamChoice { switch streamChoice {
case "main": case "main":
return highestResType return highestResType
case "sub": case "sub":
return lowestResType return lowestResType
default: default:
return defaultStreamType return defaultStreamType
} }
} }
func(c *TuyaClient) calBusinessSign(ts int64) string { func (c *TuyaClient) calBusinessSign(ts int64) string {
data := fmt.Sprintf("%s%s%s%d", c.clientId, c.accessToken, c.clientSecret, ts) data := fmt.Sprintf("%s%s%s%d", c.clientId, c.accessToken, c.clientSecret, ts)
val := md5.Sum([]byte(data)) val := md5.Sum([]byte(data))
res := fmt.Sprintf("%X", val) res := fmt.Sprintf("%X", val)
@@ -527,4 +527,4 @@ func contains(slice []int, val int) bool {
} }
} }
return false return false
} }
+13 -13
View File
@@ -14,18 +14,18 @@ import (
) )
type Client struct { type Client struct {
api *TuyaClient api *TuyaClient
prod core.Producer prod core.Producer
done chan struct{} done chan struct{}
} }
const ( const (
DefaultCnURL = "openapi.tuyacn.com" DefaultCnURL = "openapi.tuyacn.com"
DefaultWestUsURL = "openapi.tuyaus.com" DefaultWestUsURL = "openapi.tuyaus.com"
DefaultEastUsURL = "openapi-ueaz.tuyaus.com" DefaultEastUsURL = "openapi-ueaz.tuyaus.com"
DefaultCentralEuURL = "openapi.tuyaeu.com" DefaultCentralEuURL = "openapi.tuyaeu.com"
DefaultWestEuURL = "openapi-weaz.tuyaeu.com" DefaultWestEuURL = "openapi-weaz.tuyaeu.com"
DefaultInURL = "openapi.tuyain.com" DefaultInURL = "openapi.tuyain.com"
) )
func Dial(rawURL string) (core.Producer, error) { func Dial(rawURL string) (core.Producer, error) {
@@ -77,7 +77,7 @@ func Dial(rawURL string) (core.Producer, error) {
} }
client := &Client{ client := &Client{
api: tuyaAPI, api: tuyaAPI,
done: make(chan struct{}), done: make(chan struct{}),
} }
@@ -93,7 +93,7 @@ func Dial(rawURL string) (core.Producer, error) {
return streams.GetProducer(client.api.hlsURL) return streams.GetProducer(client.api.hlsURL)
} else { } else {
conf := pion.Configuration{ conf := pion.Configuration{
ICEServers: client.api.iceServers, ICEServers: client.api.iceServers,
ICETransportPolicy: pion.ICETransportPolicyAll, ICETransportPolicy: pion.ICETransportPolicyAll,
BundlePolicy: pion.BundlePolicyMaxBundle, BundlePolicy: pion.BundlePolicyMaxBundle,
} }
@@ -138,12 +138,12 @@ func Dial(rawURL string) (core.Producer, error) {
client.Stop() client.Stop()
return return
} }
if err = prod.SetAnswer(answer.Sdp); err != nil { if err = prod.SetAnswer(answer.Sdp); err != nil {
client.Stop() client.Stop()
return return
} }
prod.SDP = answer.Sdp prod.SDP = answer.Sdp
} }
@@ -159,7 +159,7 @@ func Dial(rawURL string) (core.Producer, error) {
client.api.mqtt.handleDisconnect = func() { client.api.mqtt.handleDisconnect = func() {
client.Stop() client.Stop()
} }
client.api.mqtt.handleError = func(err error) { client.api.mqtt.handleError = func(err error) {
// fmt.Printf("tuya: error: %s\n", err.Error()) // fmt.Printf("tuya: error: %s\n", err.Error())
client.Stop() client.Stop()
+49 -49
View File
@@ -11,67 +11,67 @@ import (
) )
type TuyaMQTT struct { type TuyaMQTT struct {
client mqtt.Client client mqtt.Client
waiter core.Waiter waiter core.Waiter
publishTopic string publishTopic string
subscribeTopic string subscribeTopic string
uid string uid string
closed bool closed bool
handleAnswer func(answer AnswerFrame) handleAnswer func(answer AnswerFrame)
handleCandidate func(candidate CandidateFrame) handleCandidate func(candidate CandidateFrame)
handleDisconnect func() handleDisconnect func()
handleError func(err error) handleError func(err error)
} }
type MqttFrameHeader struct { type MqttFrameHeader struct {
Type string `json:"type"` Type string `json:"type"`
From string `json:"from"` From string `json:"from"`
To string `json:"to"` To string `json:"to"`
SubDevID string `json:"sub_dev_id"` SubDevID string `json:"sub_dev_id"`
SessionID string `json:"sessionid"` SessionID string `json:"sessionid"`
MotoID string `json:"moto_id"` MotoID string `json:"moto_id"`
TransactionID string `json:"tid"` TransactionID string `json:"tid"`
} }
type MqttFrame struct { type MqttFrame struct {
Header MqttFrameHeader `json:"header"` Header MqttFrameHeader `json:"header"`
Message json.RawMessage `json:"msg"` Message json.RawMessage `json:"msg"`
} }
type OfferFrame struct { type OfferFrame struct {
Mode string `json:"mode"` Mode string `json:"mode"`
Sdp string `json:"sdp"` Sdp string `json:"sdp"`
StreamType uint32 `json:"stream_type"` StreamType uint32 `json:"stream_type"`
Auth string `json:"auth"` Auth string `json:"auth"`
} }
type AnswerFrame struct { type AnswerFrame struct {
Mode string `json:"mode"` Mode string `json:"mode"`
Sdp string `json:"sdp"` Sdp string `json:"sdp"`
} }
type CandidateFrame struct { type CandidateFrame struct {
Mode string `json:"mode"` Mode string `json:"mode"`
Candidate string `json:"candidate"` Candidate string `json:"candidate"`
} }
type ResolutionFrame struct { type ResolutionFrame struct {
Mode string `json:"mode"` Mode string `json:"mode"`
Value int `json:"value"` Value int `json:"value"`
} }
type DisconnectFrame struct { type DisconnectFrame struct {
Mode string `json:"mode"` Mode string `json:"mode"`
} }
type MqttMessage struct { type MqttMessage struct {
Protocol int `json:"protocol"` Protocol int `json:"protocol"`
Pv string `json:"pv"` Pv string `json:"pv"`
T int64 `json:"t"` T int64 `json:"t"`
Data MqttFrame `json:"data"` Data MqttFrame `json:"data"`
} }
func(c *TuyaClient) StartMQTT() error { func (c *TuyaClient) StartMQTT() error {
hubConfig, err := c.LoadHubConfig() hubConfig, err := c.LoadHubConfig()
if err != nil { if err != nil {
return err return err
@@ -106,14 +106,14 @@ func(c *TuyaClient) StartMQTT() error {
return nil return nil
} }
func(c *TuyaClient) StopMQTT() { func (c *TuyaClient) StopMQTT() {
if c.mqtt.client != nil { if c.mqtt.client != nil {
c.sendDisconnect() c.sendDisconnect()
c.mqtt.client.Disconnect(1000) c.mqtt.client.Disconnect(1000)
} }
} }
func(c *TuyaClient) onConnect(client mqtt.Client) { func (c *TuyaClient) onConnect(client mqtt.Client) {
if token := client.Subscribe(c.mqtt.subscribeTopic, 1, c.consume); token.Wait() && token.Error() != nil { if token := client.Subscribe(c.mqtt.subscribeTopic, 1, c.consume); token.Wait() && token.Error() != nil {
c.mqtt.waiter.Done(token.Error()) c.mqtt.waiter.Done(token.Error())
return return
@@ -122,7 +122,7 @@ func(c *TuyaClient) onConnect(client mqtt.Client) {
c.mqtt.waiter.Done(nil) c.mqtt.waiter.Done(nil)
} }
func(c *TuyaClient) consume(client mqtt.Client, msg mqtt.Message) { func (c *TuyaClient) consume(client mqtt.Client, msg mqtt.Message) {
var rmqtt MqttMessage var rmqtt MqttMessage
if err := json.Unmarshal(msg.Payload(), &rmqtt); err != nil { if err := json.Unmarshal(msg.Payload(), &rmqtt); err != nil {
c.mqtt.onError(fmt.Errorf("unmarshal mqtt message fail: %s, payload: %s", err.Error(), string(msg.Payload()))) c.mqtt.onError(fmt.Errorf("unmarshal mqtt message fail: %s, payload: %s", err.Error(), string(msg.Payload())))
@@ -143,7 +143,7 @@ func(c *TuyaClient) consume(client mqtt.Client, msg mqtt.Message) {
} }
} }
func(c *TuyaMQTT) onMqttAnswer(msg *MqttMessage) { func (c *TuyaMQTT) onMqttAnswer(msg *MqttMessage) {
var answerFrame AnswerFrame var answerFrame AnswerFrame
if err := json.Unmarshal(msg.Data.Message, &answerFrame); err != nil { if err := json.Unmarshal(msg.Data.Message, &answerFrame); err != nil {
c.onError(fmt.Errorf("unmarshal mqtt answer frame fail: %s, session: %s, frame: %s", c.onError(fmt.Errorf("unmarshal mqtt answer frame fail: %s, session: %s, frame: %s",
@@ -152,11 +152,11 @@ func(c *TuyaMQTT) onMqttAnswer(msg *MqttMessage) {
string(msg.Data.Message))) string(msg.Data.Message)))
return return
} }
c.onAnswer(answerFrame) c.onAnswer(answerFrame)
} }
func(c *TuyaMQTT) onMqttCandidate(msg *MqttMessage) { func (c *TuyaMQTT) onMqttCandidate(msg *MqttMessage) {
var candidateFrame CandidateFrame var candidateFrame CandidateFrame
if err := json.Unmarshal(msg.Data.Message, &candidateFrame); err != nil { if err := json.Unmarshal(msg.Data.Message, &candidateFrame); err != nil {
c.onError(fmt.Errorf("unmarshal mqtt candidate frame fail: %s, session: %s, frame: %s", c.onError(fmt.Errorf("unmarshal mqtt candidate frame fail: %s, session: %s, frame: %s",
@@ -173,30 +173,30 @@ func(c *TuyaMQTT) onMqttCandidate(msg *MqttMessage) {
c.onCandidate(candidateFrame) c.onCandidate(candidateFrame)
} }
func(c *TuyaMQTT) onMqttDisconnect() { func (c *TuyaMQTT) onMqttDisconnect() {
c.closed = true c.closed = true
c.onDisconnect() c.onDisconnect()
} }
func(c *TuyaMQTT) onAnswer(answer AnswerFrame) { func (c *TuyaMQTT) onAnswer(answer AnswerFrame) {
if c.handleAnswer != nil { if c.handleAnswer != nil {
c.handleAnswer(answer) c.handleAnswer(answer)
} }
} }
func(c *TuyaMQTT) onCandidate(candidate CandidateFrame) { func (c *TuyaMQTT) onCandidate(candidate CandidateFrame) {
if c.handleCandidate != nil { if c.handleCandidate != nil {
c.handleCandidate(candidate) c.handleCandidate(candidate)
} }
} }
func(c *TuyaMQTT) onDisconnect() { func (c *TuyaMQTT) onDisconnect() {
if c.handleDisconnect != nil { if c.handleDisconnect != nil {
c.handleDisconnect() c.handleDisconnect()
} }
} }
func(c *TuyaMQTT) onError(err error) { func (c *TuyaMQTT) onError(err error) {
if c.handleError != nil { if c.handleError != nil {
c.handleError(err) c.handleError(err)
} }
@@ -220,12 +220,12 @@ func (c *TuyaClient) sendCandidate(candidate string) {
func (c *TuyaClient) sendResolution(resolution int) { func (c *TuyaClient) sendResolution(resolution int) {
c.sendMqttMessage("resolution", 302, "", ResolutionFrame{ c.sendMqttMessage("resolution", 302, "", ResolutionFrame{
Mode: "webrtc", Mode: "webrtc",
Value: resolution, Value: resolution,
}) })
} }
func(c *TuyaClient) sendDisconnect() { func (c *TuyaClient) sendDisconnect() {
c.sendMqttMessage("disconnect", 302, "", DisconnectFrame{ c.sendMqttMessage("disconnect", 302, "", DisconnectFrame{
Mode: "webrtc", Mode: "webrtc",
}) })