@@ -17,16 +17,28 @@ type API struct {
1717
1818 StreamProjectID string
1919 StreamDeviceID string
20- StreamSessionID string
2120 StreamExpiresAt time.Time
2221
22+ // WebRTC
23+ StreamSessionID string
24+
25+ // RTSP
26+ StreamToken string
27+ StreamExtensionToken string
28+
2329 extendTimer * time.Timer
2430}
2531
2632type Auth struct {
2733 AccessToken string
2834}
2935
36+ type DeviceInfo struct {
37+ Name string
38+ DeviceID string
39+ Protocols []string
40+ }
41+
3042var cache = map [string ]* API {}
3143var cacheMu sync.Mutex
3244
@@ -80,7 +92,7 @@ func NewAPI(clientID, clientSecret, refreshToken string) (*API, error) {
8092 return api , nil
8193}
8294
83- func (a * API ) GetDevices (projectID string ) (map [ string ] string , error ) {
95+ func (a * API ) GetDevices (projectID string ) ([] DeviceInfo , error ) {
8496 uri := "https://smartdevicemanagement.googleapis.com/v1/enterprises/" + projectID + "/devices"
8597 req , err := http .NewRequest ("GET" , uri , nil )
8698 if err != nil {
@@ -108,24 +120,30 @@ func (a *API) GetDevices(projectID string) (map[string]string, error) {
108120 return nil , err
109121 }
110122
111- devices := map [ string ] string {}
123+ devices := make ([] DeviceInfo , 0 , len ( resv . Devices ))
112124
113125 for _ , device := range resv .Devices {
126+ // only RTSP and WEB_RTC available (both supported)
114127 if len (device .Traits .SdmDevicesTraitsCameraLiveStream .SupportedProtocols ) == 0 {
115128 continue
116129 }
117130
118- if device .Traits .SdmDevicesTraitsCameraLiveStream .SupportedProtocols [0 ] != "WEB_RTC" {
119- continue
120- }
121-
122131 i := strings .LastIndexByte (device .Name , '/' )
123132 if i <= 0 {
124133 continue
125134 }
126135
127136 name := device .Traits .SdmDevicesTraitsInfo .CustomName
128- devices [name ] = device .Name [i + 1 :]
137+ // Devices configured through the Nest app use the container/room name as opposed to the customName trait
138+ if name == "" && len (device .ParentRelations ) > 0 {
139+ name = device .ParentRelations [0 ].DisplayName
140+ }
141+
142+ devices = append (devices , DeviceInfo {
143+ Name : name ,
144+ DeviceID : device .Name [i + 1 :],
145+ Protocols : device .Traits .SdmDevicesTraitsCameraLiveStream .SupportedProtocols ,
146+ })
129147 }
130148
131149 return devices , nil
@@ -190,11 +208,20 @@ func (a *API) ExtendStream() error {
190208 var reqv struct {
191209 Command string `json:"command"`
192210 Params struct {
193- MediaSessionID string `json:"mediaSessionId"`
211+ MediaSessionID string `json:"mediaSessionId,omitempty"`
212+ StreamExtensionToken string `json:"streamExtensionToken,omitempty"`
194213 } `json:"params"`
195214 }
196- reqv .Command = "sdm.devices.commands.CameraLiveStream.ExtendWebRtcStream"
197- reqv .Params .MediaSessionID = a .StreamSessionID
215+
216+ if a .StreamToken != "" {
217+ // RTSP
218+ reqv .Command = "sdm.devices.commands.CameraLiveStream.ExtendRtspStream"
219+ reqv .Params .StreamExtensionToken = a .StreamExtensionToken
220+ } else {
221+ // WebRTC
222+ reqv .Command = "sdm.devices.commands.CameraLiveStream.ExtendWebRtcStream"
223+ reqv .Params .MediaSessionID = a .StreamSessionID
224+ }
198225
199226 b , err := json .Marshal (reqv )
200227 if err != nil {
@@ -223,8 +250,10 @@ func (a *API) ExtendStream() error {
223250
224251 var resv struct {
225252 Results struct {
226- ExpiresAt time.Time `json:"expiresAt"`
227- MediaSessionID string `json:"mediaSessionId"`
253+ ExpiresAt time.Time `json:"expiresAt"`
254+ MediaSessionID string `json:"mediaSessionId"`
255+ StreamExtensionToken string `json:"streamExtensionToken"`
256+ StreamToken string `json:"streamToken"`
228257 } `json:"results"`
229258 }
230259
@@ -234,6 +263,111 @@ func (a *API) ExtendStream() error {
234263
235264 a .StreamSessionID = resv .Results .MediaSessionID
236265 a .StreamExpiresAt = resv .Results .ExpiresAt
266+ a .StreamExtensionToken = resv .Results .StreamExtensionToken
267+ a .StreamToken = resv .Results .StreamToken
268+
269+ return nil
270+ }
271+
272+ func (a * API ) GenerateRtspStream (projectID , deviceID string ) (string , error ) {
273+ var reqv struct {
274+ Command string `json:"command"`
275+ Params struct {} `json:"params"`
276+ }
277+ reqv .Command = "sdm.devices.commands.CameraLiveStream.GenerateRtspStream"
278+
279+ b , err := json .Marshal (reqv )
280+ if err != nil {
281+ return "" , err
282+ }
283+
284+ uri := "https://smartdevicemanagement.googleapis.com/v1/enterprises/" +
285+ projectID + "/devices/" + deviceID + ":executeCommand"
286+ req , err := http .NewRequest ("POST" , uri , bytes .NewReader (b ))
287+ if err != nil {
288+ return "" , err
289+ }
290+
291+ req .Header .Set ("Authorization" , "Bearer " + a .Token )
292+
293+ client := & http.Client {Timeout : time .Second * 5000 }
294+ res , err := client .Do (req )
295+ if err != nil {
296+ return "" , err
297+ }
298+
299+ if res .StatusCode != 200 {
300+ return "" , errors .New ("nest: wrong status: " + res .Status )
301+ }
302+
303+ var resv struct {
304+ Results struct {
305+ StreamURLs map [string ]string `json:"streamUrls"`
306+ StreamExtensionToken string `json:"streamExtensionToken"`
307+ StreamToken string `json:"streamToken"`
308+ ExpiresAt time.Time `json:"expiresAt"`
309+ } `json:"results"`
310+ }
311+
312+ if err = json .NewDecoder (res .Body ).Decode (& resv ); err != nil {
313+ return "" , err
314+ }
315+
316+ if _ , ok := resv .Results .StreamURLs ["rtspUrl" ]; ! ok {
317+ return "" , errors .New ("nest: failed to generate rtsp url" )
318+ }
319+
320+ a .StreamProjectID = projectID
321+ a .StreamDeviceID = deviceID
322+ a .StreamToken = resv .Results .StreamToken
323+ a .StreamExtensionToken = resv .Results .StreamExtensionToken
324+ a .StreamExpiresAt = resv .Results .ExpiresAt
325+
326+ return resv .Results .StreamURLs ["rtspUrl" ], nil
327+ }
328+
329+ func (a * API ) StopRTSPStream () error {
330+ if a .StreamProjectID == "" || a .StreamDeviceID == "" {
331+ return errors .New ("nest: tried to stop rtsp stream without a project or device ID" )
332+ }
333+
334+ var reqv struct {
335+ Command string `json:"command"`
336+ Params struct {
337+ StreamExtensionToken string `json:"streamExtensionToken"`
338+ } `json:"params"`
339+ }
340+ reqv .Command = "sdm.devices.commands.CameraLiveStream.StopRtspStream"
341+ reqv .Params .StreamExtensionToken = a .StreamExtensionToken
342+
343+ b , err := json .Marshal (reqv )
344+ if err != nil {
345+ return err
346+ }
347+
348+ uri := "https://smartdevicemanagement.googleapis.com/v1/enterprises/" +
349+ a .StreamProjectID + "/devices/" + a .StreamDeviceID + ":executeCommand"
350+ req , err := http .NewRequest ("POST" , uri , bytes .NewReader (b ))
351+ if err != nil {
352+ return err
353+ }
354+
355+ req .Header .Set ("Authorization" , "Bearer " + a .Token )
356+
357+ client := & http.Client {Timeout : time .Second * 5000 }
358+ res , err := client .Do (req )
359+ if err != nil {
360+ return err
361+ }
362+
363+ if res .StatusCode != 200 {
364+ return errors .New ("nest: wrong status: " + res .Status )
365+ }
366+
367+ a .StreamProjectID = ""
368+ a .StreamDeviceID = ""
369+ a .StreamExtensionToken = ""
370+ a .StreamToken = ""
237371
238372 return nil
239373}
@@ -266,10 +400,10 @@ type Device struct {
266400 //SdmDevicesTraitsCameraClipPreview struct {
267401 //} `json:"sdm.devices.traits.CameraClipPreview"`
268402 } `json:"traits"`
269- // ParentRelations []struct {
270- // Parent string `json:"parent"`
271- // DisplayName string `json:"displayName"`
272- // } `json:"parentRelations"`
403+ ParentRelations []struct {
404+ Parent string `json:"parent"`
405+ DisplayName string `json:"displayName"`
406+ } `json:"parentRelations"`
273407}
274408
275409func (a * API ) StartExtendStreamTimer () {
@@ -282,7 +416,6 @@ func (a *API) StartExtendStreamTimer() {
282416 duration = time .Until (a .StreamExpiresAt .Add (- 30 * time .Second ))
283417 a .extendTimer .Reset (duration )
284418 })
285-
286419}
287420
288421func (a * API ) StopExtendStreamTimer () {
0 commit comments