format
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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")
|
||||||
|
|
||||||
|
|||||||
+227
-227
@@ -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 = ""
|
||||||
@@ -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)
|
c.medias = make([]*core.Media, 0)
|
||||||
|
|
||||||
if len(c.skill.Audios) > 0 {
|
if len(c.skill.Audios) > 0 {
|
||||||
// Use the first Audio-Codec
|
// Use the first Audio-Codec
|
||||||
audio := c.skill.Audios[0]
|
audio := c.skill.Audios[0]
|
||||||
|
|
||||||
c.medias = append(c.medias, &core.Media{
|
c.medias = append(c.medias, &core.Media{
|
||||||
Kind: core.KindAudio,
|
Kind: core.KindAudio,
|
||||||
Direction: audioDirection,
|
Direction: audioDirection,
|
||||||
Codecs: []*core.Codec{
|
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 {
|
|
||||||
// Use the first Video-Codec
|
|
||||||
video := c.skill.Videos[0]
|
|
||||||
|
|
||||||
c.medias = append(c.medias, &core.Media{
|
|
||||||
Kind: core.KindVideo,
|
|
||||||
Direction: core.DirectionRecvonly,
|
|
||||||
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{
|
||||||
@@ -474,46 +474,46 @@ func(c *TuyaClient) LoadHubConfig() (config *OpenIoTHubConfig, err error) {
|
|||||||
// 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 {
|
if c.skill == nil || len(c.skill.Videos) == 0 {
|
||||||
return defaultStreamType
|
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)
|
||||||
|
|||||||
+10
-10
@@ -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,
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-48
@@ -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",
|
||||||
@@ -156,7 +156,7 @@ func(c *TuyaMQTT) onMqttAnswer(msg *MqttMessage) {
|
|||||||
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",
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user