27
27
REDIRECT_URI = 'https://www.google.com'
28
28
SCOPE = ['https://www.googleapis.com/auth/sdm.service' ]
29
29
30
- STRUCTURES = 'structures'
31
- THERMOSTAT_TYPE = 'sdm.devices.types.THERMOSTAT'
32
- DOORBELL_TYPE = 'sdm.devices.types.DOORBELL'
33
-
34
30
_LOGGER = logging .getLogger (__name__ )
35
31
32
+
36
33
class APIError (Exception ):
37
34
def __init__ (self , response , msg = None ):
38
35
if response is None :
@@ -87,103 +84,73 @@ def __init__(self, response, msg=None):
87
84
88
85
self .response = response
89
86
90
- class NestBase (object ):
91
- def __init__ (self , name , nest_api ):
87
+
88
+ class Device ():
89
+
90
+ def __init__ (self , nest_api = None , name = None , device_data = None ):
92
91
self ._name = name
93
92
self ._nest_api = nest_api
93
+ self ._device_data = device_data
94
94
95
95
def __str__ (self ):
96
- return '<%s: %s>' % (self .__class__ .__name__ , self ._repr_name )
97
-
98
- def _set (self , data ):
99
- path = f'/{ self .name } :executeCommand'
100
-
101
- response = self ._nest_api ._put (path = path , data = data )
102
-
103
- return response
96
+ trait_str = ',' .join ([f'<{ k } : { v } >' for k , v in self .traits .items ()])
97
+ return f'name: { self .name } where:{ self .where } - { self .type } ({ trait_str } )'
104
98
105
99
@property
106
100
def name (self ):
107
- return self ._name
108
-
109
- @property
110
- def _repr_name (self ):
111
- return self .name
112
-
101
+ if self ._device_data is not None :
102
+ return self ._device_data ['name' ]
103
+ else :
104
+ return self ._name .split ('/' )[- 1 ]
113
105
114
- class Device (NestBase ):
115
106
@property
116
107
def _device (self ):
117
- return next (device for device in self ._devices if device ['name' ] == self .name )
108
+ if self ._device_data is not None :
109
+ return self ._device_data
110
+ else :
111
+ return next (device for device in self ._devices if self .name in device ['name' ])
118
112
119
113
@property
120
114
def _devices (self ):
115
+ if self ._device_data is not None :
116
+ raise RuntimeError ("Invalid use of singular device" )
121
117
return self ._nest_api ._devices
122
118
123
- @property
124
- def _repr_name (self ):
125
- if self .name :
126
- return self .name
127
-
128
- return self .where
129
-
130
- def __repr__ (self ):
131
- return str (self ._device )
132
-
133
119
@property
134
120
def where (self ):
135
121
return self ._device ['parentRelations' ][0 ]['displayName' ]
136
122
137
-
138
- class Thermostat (Device ):
139
- #TODO: Fill in rest from https://developers.google.com/nest/device-access/traits/device/thermostat-eco
140
-
141
- @property
142
- def humidity (self ):
143
- return self ._device ['traits' ]['sdm.devices.traits.Humidity' ]['ambientHumidityPercent' ]
144
-
145
- @property
146
- def mode (self ):
147
- return self ._device ['traits' ]['sdm.devices.traits.ThermostatMode' ]['mode' ]
148
-
149
123
@property
150
- def temperature_scale (self ):
151
- return self ._device ['traits' ][ 'sdm.devices.traits.Settings' ][ 'temperatureScale' ]
124
+ def type (self ):
125
+ return self ._device ['type' ]. split ( '.' )[ - 1 ]
152
126
153
127
@property
154
- def temperature (self ):
155
- return self . _device [ 'traits' ][ 'sdm.devices. traits.Temperature' ][ 'ambientTemperatureCelsius' ]
128
+ def traits (self ):
129
+ return { k . split ( '.' )[ - 1 ]: v for k , v in self . _device [ ' traits' ]. items ()}
156
130
157
131
@property
158
- def hvac_state (self ):
159
- return self ._device ['traits' ]['sdm.devices.traits.ThermostatHvac' ]['status' ]
160
-
161
- @property
162
- def heat_setpoint (self ):
163
- return self ._device ['traits' ]['sdm.devices.traits.ThermostatTemperatureSetpoint' ]['heatCelsius' ]
164
-
165
- @heat_setpoint .setter
166
- def heat_setpoint (self , value ):
167
- self ._set ({
168
- "command" : "sdm.devices.commands.ThermostatMode.SetHeat" ,
169
- "params" : {
170
- "heatCelsius" : value
171
- }
172
- })
173
-
174
- def __str__ (self ):
175
- fields = ['name' , 'where' , 'temperature' , 'humidity' , 'heat_setpoint' , 'hvac_state' ]
176
- properties = ' ' .join ([f'{ field } ={ getattr (self , field )} ' for field in fields ])
177
- return f'Thermostat({ properties } )'
132
+ def traits (self ):
133
+ return {k .split ('.' )[- 1 ]: v for k , v in self ._device ['traits' ].items ()}
178
134
135
+ def send_cmd (self , cmd , params ):
136
+ cmd = '.' .join (cmd .split ('.' )[- 2 :])
137
+ path = f'/{ self .name } :executeCommand'
138
+ data = {
139
+ "command" : "sdm.devices.commands." + cmd ,
140
+ 'params' : params
141
+ }
142
+ response = self ._nest_api ._put (path = path , data = data )
143
+ return response
179
144
180
- class Doorbell (Device ):
181
- #TODO: Fill in rest from https://developers.google.com/nest/device-access/traits/device/camera-event-image
145
+ @staticmethod
146
+ def filter_for_trait (devices , trait ):
147
+ trait = trait .split ('.' )[- 1 ]
148
+ return [device for device in devices if trait in device .traits ]
182
149
183
- def __str__ ( self ):
184
- fields = [ 'name' , 'where' ]
185
- properties = ' ' . join ([ f' { field } = { getattr ( self , field ) } ' for field in fields ])
186
- return f'Doorbell( { properties } )'
150
+ @ staticmethod
151
+ def filter_for_cmd ( devices , cmd ):
152
+ trait = cmd . split ( '.' )[ - 2 ]
153
+ return Device . filter_for_trait ( devices , trait )
187
154
188
155
189
156
class Nest (object ):
@@ -204,27 +171,28 @@ def __init__(self,
204
171
self ._devices_value = {}
205
172
206
173
if not access_token :
207
- try :
208
- with open (self ._access_token_cache_file , 'r' ) as fd :
209
- access_token = json .load (fd )
210
- _LOGGER .debug ("Loaded access token from %s" ,
211
- self ._access_token_cache_file )
212
- except :
213
- _LOGGER .warn ("Token load failed from %s" ,
214
- self ._access_token_cache_file )
174
+ try :
175
+ with open (self ._access_token_cache_file , 'r' ) as fd :
176
+ access_token = json .load (fd )
177
+ _LOGGER .debug ("Loaded access token from %s" ,
178
+ self ._access_token_cache_file )
179
+ except :
180
+ _LOGGER .warn ("Token load failed from %s" ,
181
+ self ._access_token_cache_file )
215
182
if access_token :
216
183
self ._client = OAuth2Session (self ._client_id , token = access_token )
217
184
218
185
def __save_token (self , token ):
219
186
with open (self ._access_token_cache_file , 'w' ) as fd :
220
187
json .dump (token , fd )
221
188
_LOGGER .debug ("Save access token to %s" ,
222
- self ._access_token_cache_file )
189
+ self ._access_token_cache_file )
223
190
224
191
def __reauthorize (self ):
225
192
if self ._reautherize_callback is None :
226
193
raise AuthorizationError (None , 'No callback to handle OAuth URL' )
227
- self ._client = OAuth2Session (self ._client_id , redirect_uri = REDIRECT_URI , scope = SCOPE )
194
+ self ._client = OAuth2Session (
195
+ self ._client_id , redirect_uri = REDIRECT_URI , scope = SCOPE )
228
196
229
197
authorization_url , state = self ._client .authorization_url (
230
198
AUTHORIZE_URL .format (project_id = self ._project_id ),
@@ -252,8 +220,8 @@ def _request(self, verb, path, data=None):
252
220
try :
253
221
_LOGGER .debug (">> %s %s" , verb , url )
254
222
r = self ._client .request (verb , url ,
255
- allow_redirects = False ,
256
- data = data )
223
+ allow_redirects = False ,
224
+ data = data )
257
225
_LOGGER .debug (f"<< { r .status_code } " )
258
226
if r .status_code == 200 :
259
227
return r .json ()
@@ -267,13 +235,15 @@ def _request(self, verb, path, data=None):
267
235
'client_secret' : self ._client_secret ,
268
236
}
269
237
_LOGGER .debug (">> refreshing token" )
270
- token = self ._client .refresh_token (ACCESS_TOKEN_URL , ** extra )
238
+ token = self ._client .refresh_token (
239
+ ACCESS_TOKEN_URL , ** extra )
271
240
self .__save_token (token )
272
241
if attempt > 1 :
273
- raise AuthorizationError (None , 'Repeated TokenExpiredError' )
242
+ raise AuthorizationError (
243
+ None , 'Repeated TokenExpiredError' )
274
244
continue
275
245
self .__reauthorize ()
276
-
246
+
277
247
def _put (self , path , data = None ):
278
248
pieces = path .split ('/' )
279
249
path = '/' + pieces [- 1 ]
@@ -295,26 +265,15 @@ def _devices(self):
295
265
" %s" , error )
296
266
return self ._devices_value
297
267
298
- def thermostats (self , where = None ):
299
- names = [ device ['name' ] for device in
300
- self ._devices if device ['type' ] == THERMOSTAT_TYPE and (where is None or device ['parentRelations' ][0 ]['displayName' ] in where )
301
- ]
302
- return [Thermostat (name , self ) for name in names ]
303
-
304
- def doorbells (self , where = None ):
305
- names = [ device ['name' ] for device in
306
- self ._devices if device ['type' ] == DOORBELL_TYPE and (where is None or device ['parentRelations' ][0 ]['displayName' ] in where )
307
- ]
308
- return [Doorbell (name , self ) for name in names ]
309
-
310
- def get_devices (self , names = None , where = None ):
268
+ def get_devices (self , names = None , wheres = None , types = None ):
311
269
ret = []
312
270
for device in self ._devices :
313
- if (names is None or device ['name' ] in names ) and (where is None or device ['parentRelations' ][0 ]['displayName' ] in where ):
314
- ret .append ({
315
- THERMOSTAT_TYPE : Thermostat ,
316
- DOORBELL_TYPE : Doorbell
317
- }[device ['type' ]](device ['name' ], self ))
271
+ obj = Device (device_data = device )
272
+ name_match = (names is None or obj .name in names )
273
+ where_match = (wheres is None or obj .where in wheres )
274
+ type_match = (types is None or obj .type in types )
275
+ if name_match and where_match and type_match :
276
+ ret .append (Device (nest_api = self , name = obj .name ))
318
277
return ret
319
278
320
279
def __enter__ (self ):
0 commit comments