@@ -269,8 +269,11 @@ def online(self):
269269
270270 @property
271271 def structure (self ):
272- return Structure (self ._device ['structure_id' ],
273- self ._nest_api )
272+ if 'structure_id' in self ._device :
273+ return Structure (self ._device ['structure_id' ],
274+ self ._nest_api )
275+ else :
276+ return None
274277
275278 @property
276279 def where (self ):
@@ -299,7 +302,7 @@ def where(self, value):
299302
300303 @property
301304 def description (self ):
302- return self ._device [ 'name_long' ]
305+ return self ._device . get ( 'name_long' )
303306
304307 @property
305308 def is_thermostat (self ):
@@ -321,7 +324,7 @@ def is_thermostat(self):
321324
322325 @property
323326 def _device (self ):
324- return self ._devices [ THERMOSTATS ][ self ._serial ]
327+ return self ._devices . get ( THERMOSTATS , {}). get ( self ._serial , {})
325328
326329 @property
327330 def _shared (self ):
@@ -335,7 +338,7 @@ def _track(self):
335338
336339 @property
337340 def software_version (self ):
338- return self ._device [ 'software_version' ]
341+ return self ._device . get ( 'software_version' )
339342
340343 @property
341344 def fan (self ):
@@ -509,7 +512,7 @@ def _round_temp(self, temp):
509512
510513 @property
511514 def temperature_scale (self ):
512- return self ._device [ 'temperature_scale' ]
515+ return self ._device . get ( 'temperature_scale' )
513516
514517 @property
515518 def is_locked (self ):
@@ -552,11 +555,11 @@ def temperature(self, value):
552555 @property
553556 def target (self ):
554557 if self .mode == 'heat-cool' :
555- low = self ._device [ self ._temp_key ('target_temperature_low' )]
556- high = self ._device [ self ._temp_key ('target_temperature_high' )]
558+ low = self ._device . get ( self ._temp_key ('target_temperature_low' ))
559+ high = self ._device . get ( self ._temp_key ('target_temperature_high' ))
557560 return LowHighTuple (low , high )
558561
559- return self ._device [ self ._temp_key ('target_temperature' )]
562+ return self ._device . get ( self ._temp_key ('target_temperature' ))
560563
561564 @target .setter
562565 def target (self , value ):
@@ -677,7 +680,7 @@ def is_smoke_co_alarm(self):
677680
678681 @property
679682 def _device (self ):
680- return self ._devices [ SMOKE_CO_ALARMS ][ self ._serial ]
683+ return self ._devices . get ( SMOKE_CO_ALARMS , {}). get ( self ._serial , {})
681684
682685 @property
683686 def auto_away (self ):
@@ -1104,7 +1107,7 @@ def is_camera(self):
11041107
11051108 @property
11061109 def _device (self ):
1107- return self ._devices [ CAMERAS ][ self ._serial ]
1110+ return self ._devices . get ( CAMERAS , {}). get ( self ._serial , {})
11081111
11091112 @property
11101113 def ongoing_event (self ):
@@ -1348,7 +1351,8 @@ def web_url(http://github.com/self):
13481351class Structure (NestBase ):
13491352 @property
13501353 def _structure (self ):
1351- return self ._nest_api ._status [STRUCTURES ][self ._serial ]
1354+ return self ._nest_api ._status .get (
1355+ STRUCTURES , {}).get (self ._serial , {})
13521356
13531357 def __repr__ (self ):
13541358 return str (self ._structure )
@@ -1725,8 +1729,13 @@ def _open_data_stream(self, path="/"):
17251729
17261730 # Opens the data stream
17271731 headers = {'Accept' : 'text/event-stream' }
1732+ # Set Connection Timeout to 30 seconds
1733+ # Set Read Timeout to 5 mintues, Nest Stream API will send
1734+ # keep alive event every 30 seconds, 5 mintues is long enough
1735+ # for us to belive network issue occurred
17281736 response = self ._session .get (url , stream = True , headers = headers ,
1729- allow_redirects = False )
1737+ allow_redirects = False ,
1738+ timeout = (30 , 300 ))
17301739
17311740 _LOGGER .debug ("<< %s" , response .status_code )
17321741 if response .status_code == 401 :
@@ -1742,10 +1751,12 @@ def _open_data_stream(self, path="/"):
17421751 if response .status_code == 307 :
17431752 redirect_url = response .headers ['Location' ]
17441753 _LOGGER .debug (">> STREAM %s" , redirect_url )
1754+ # For stream API, we have to deal redirect manually
17451755 response = self ._session .get (redirect_url ,
17461756 allow_redirects = False ,
17471757 headers = headers ,
1748- stream = True )
1758+ stream = True ,
1759+ timeout = (30 , 300 ))
17491760 _LOGGER .debug ("<< %s" , response .status_code )
17501761 if response .status_code == 429 :
17511762 response = self ._handle_ratelimit (response , 'GET' , url , None ,
@@ -1785,6 +1796,8 @@ def _start_event_loop(self, response, queue, ready_event, update_event):
17851796
17861797 if not ready_event .is_set ():
17871798 ready_event .set ()
1799+ except requests .exceptions .ConnectionError :
1800+ _LOGGER .warning ("Haven't received data from Nest in 5 mintues" )
17881801 finally :
17891802 _LOGGER .debug ("Stopping event loop." )
17901803 queue .clear ()
@@ -1867,30 +1880,28 @@ def _status(self):
18671880 except AuthorizationError as authorization_error :
18681881 self ._queue_lock .release ()
18691882 raise authorization_error
1870- except Exception :
1871- # other error will fall back to pull mode
1872- pass
1883+ except Exception as error :
1884+ # other error still set update_event to trigger retry
1885+ _LOGGER .debug ("Exception occurred in processing stream:"
1886+ " %s" , error )
1887+ self ._queue .clear ()
1888+ self ._update_event .set ()
18731889 self ._queue_lock .release ()
18741890
1875- value = self ._queue [0 ]['data' ] if len (self ._queue ) > 0 else None
1876- if value is None :
1877- # fall back to pull mode
1878- _LOGGER .info ("Fall back to pull mode" )
1879- value = self ._get ("/" )
1880-
1891+ value = self ._queue [0 ]['data' ] if len (self ._queue ) > 0 else {}
18811892 return value
18821893
18831894 @property
18841895 def _metadata (self ):
1885- return self ._status [ METADATA ]
1896+ return self ._status . get ( METADATA , {})
18861897
18871898 @property
18881899 def client_version (self ):
1889- return self ._metadata [ 'client_version' ]
1900+ return self ._metadata . get ( 'client_version' )
18901901
18911902 @property
18921903 def _devices (self ):
1893- return self ._status [ DEVICES ]
1904+ return self ._status . get ( DEVICES , {})
18941905
18951906 @property
18961907 def devices (self ):
@@ -1922,7 +1933,7 @@ def cameras(self):
19221933 @property
19231934 def structures (self ):
19241935 return [Structure (stid , self )
1925- for stid in self ._status [ STRUCTURES ] ]
1936+ for stid in self ._status . get ( STRUCTURES , []) ]
19261937
19271938 @property
19281939 def urls (self ):
0 commit comments