diff --git a/.netatmo.credentials b/.netatmo.credentials index 2127562c..37b7e913 100644 --- a/.netatmo.credentials +++ b/.netatmo.credentials @@ -1,6 +1,10 @@ { + "USER_MAIL" : "", + "USER_PASSWORD" : "", "CLIENT_ID" : "", "CLIENT_SECRET" : "", - "REFRESH_TOKEN" : "" + "ACCESS_TOKEN" : "", + "REFRESH_TOKEN" : "", + "SCOPE" : "" } diff --git a/lnetatmo.py b/lnetatmo.py index 11bfb6d4..d4b8e2a8 100644 --- a/lnetatmo.py +++ b/lnetatmo.py @@ -3,7 +3,7 @@ # Multiple contributors : see https://github.com/philippelt/netatmo-api-python # License : GPL V3 """ -This API provides access to the Netatmo weather station or/and the Welcome camera +This API provides access to the Netatmo weather station or/and other installed devices This package can be used with Python2 or Python3 applications and do not require anything else than standard libraries @@ -121,8 +121,8 @@ 'NAModule2' : ["wind unit", 'Weather'], 'NAModule3' : ["rain unit", 'Weather'], 'NAModule4' : ["indoor unit", 'Weather'], - 'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat and a Relais module - # The relais module is also the bridge for thermostat and Valves + 'NAPlug' : ["thermostat relais station", 'Energy'], # A smart thermostat exist of a thermostat module and a Relay device + # The relay device is also the bridge for thermostat and Valves 'NATherm1' : ["thermostat", 'Energy'], 'NCO' : ["co2 sensor", 'Home + Security'], # The same API as smoke sensor 'NDB' : ["doorbell", 'Home + Security'], @@ -131,6 +131,7 @@ 'NSD' : ["smoke sensor", 'Home + Security'], 'NHC' : ["home coach", 'Aircare'], 'NIS' : ["indoor sirene", 'Home + Security'], + 'NDL' : ["Doorlock", 'Home + Security'], 'NLC' : ["Cable Outlet", 'Home+Control'], 'NLE' : ["Ecometer", 'Home+Control'], @@ -231,9 +232,9 @@ def __init__(self, clientId=None, self._clientId = clientId or cred["CLIENT_ID"] self._clientSecret = clientSecret or cred["CLIENT_SECRET"] + self._accessToken = None #accessToken or cred["ACCESS_TOKEN"] # Will be refreshed before any use self.refreshToken = refreshToken or cred["REFRESH_TOKEN"] self.expiration = 0 # Force refresh token - self._accessToken = None # Will be refreshed before any use @property def accessToken(self): @@ -341,8 +342,8 @@ def getModuleParam(self, module_id, param): class ThermostatData: """ - List the Thermostat and temperature modules - + List the Relay station and Thermostat modules + Valves are controlled by HomesData and HomeStatus in new API Args: authData (clientAuth): Authentication information with a working access Token home : Home name or id of the home who's thermostat belongs to @@ -375,36 +376,72 @@ def __init__(self, authData, home=None): # Standard the first Relaystation and Thermostat is returned # self.rawData is list all stations - # FIXME : This code is wrong as it will always return the first Relay - # I don't own a thermostat I can't fix this code, help welcome - def Relay_Plug(self, _id=None): +# if no ID is given the Relaystation at index 0 is returned + def Relay_Plug(self, Rid=""): for Relay in self.rawData: - if _id in Relay: - return Relay - else: + if Rid in Relay['_id']: + print ('Relay ', Rid, 'in rawData') + #print (Relay.keys()) #print (Relay['_id']) return Relay +#dict_keys(['_id', 'applications', 'cipher_id', 'command', 'config_version', 'd_amount', 'date_creation', 'dev_has_init', 'device_group', 'firmware', 'firmware_private', 'homekit_nb_pairing', 'last_bilan', 'last_day_extremum', 'last_fw_update', 'last_measure_stored', 'last_setup', 'last_status_store', 'last_sync_asked', 'last_time_boiler_on', 'mg_station_name', 'migration_date', 'module_history', 'netcom_transport', 'new_historic_data', 'place', 'plug_connected_boiler', 'recompute_outdoor_time', 'record_storage', 'rf_amb_status', 'setpoint_order_history', 'skip_module_history_creation', 'subtype', 'type', 'u_amount', 'update_device', 'upgrade_record_ts', 'wifi_status', 'room', 'modules', 'station_name', 'udp_conn', 'last_plug_seen']) - # FIXME : Probably wrong again, always returning "first" thermostat ? - def Thermostat_Data(self): - for thermostat in self.Relay_Plug()['modules']: - # - return thermostat +# if no ID is given the Thermostatmodule at index 0 is returned + def Thermostat_Data(self, tid=""): + for Relay in self.rawData: + for thermostat in Relay['modules']: + if tid in thermostat['_id']: + print ('Thermostat ',tid, 'in Relay', Relay['_id'], Relay['station_name']) + #print (thermostat['_id']) + #print (thermostat.keys()) + return thermostat +#dict_keys(['_id', 'module_name', 'type', 'firmware', 'last_message', 'rf_status', 'battery_vp', 'therm_orientation', 'therm_relay_cmd', 'anticipating', 'battery_percent', 'event_history', 'last_therm_seen', 'setpoint', 'therm_program_list', 'measured']) - def getThermostat(self, name=None, tid=None): - if self.rawData[0]['station_name'] != name: return None # OLD ['name'] - # FIXME: No thermostat property !! - return self.thermostat[self.defaultThermostatId] + + def getThermostat(self, name=None, id=""): + for Relay in self.rawData: + for module in Relay['modules']: + if id == Relay['_id']: + print ('Relay ', id, 'found') + return Relay + elif name == Relay['station_name']: + print ('Relay ', name, 'found') + return Relay + elif id == module['_id']: + print ('Thermostat ', id, 'found in Relay', Relay['_id'], Relay['station_name']) + return module + elif name == module['module_name']: + print ('Thermostat ', name, 'found in Relay', Relay['_id'], Relay['station_name']) + return module + else: + #print ('Device NOT Found') + pass def moduleNamesList(self, name=None, tid=None): - thermostat = self.getThermostat(name=name, tid=tid) - return [m['name'] for m in thermostat['modules']] if thermostat else None + l = [] + for Relay in self.rawData: + if id == Relay['_id'] or name == Relay['station_name']: + RL = [] + for module in Relay['modules']: + RL.append(module['module_name']) + return RL + else: + #print ("Cloud Data") + for module in Relay['modules']: + l.append(module['module_name']) + #This return a list off all connected Thermostat in the cloud. + return l - def getModuleByName(self, name, thermostatId=None): # ERROR 'NoneType' object is not subscriptable - thermostat = self.getThermostat(tid=thermostatId) - for m in thermostat['modules']: - if m['name'] == name: return m - return None + def getModuleByName(self, name, tid=""): + for Relay in self.rawData: + for module in Relay['modules']: + #print (module['module_name'], module['_id']) + if module['module_name'] == name: + return module + elif module['_id'] == tid: + return module + else: + pass class WeatherStationData: @@ -595,8 +632,11 @@ class HomeData: Args: authData (ClientAuth): Authentication information with a working access Token + home : Home name of the home where's devices are installed """ def __init__(self, authData, home=None): + warnings.warn("The 'HomeData' class is deprecated'", + DeprecationWarning ) self.getAuthToken = authData.accessToken postParams = { "access_token" : self.getAuthToken @@ -604,22 +644,36 @@ def __init__(self, authData, home=None): resp = postRequest("Home data", _GETHOMEDATA_REQ, postParams) self.rawData = resp['body'] # Collect homes - self.homes = { d['id'] : d for d in self.rawData['homes'] } - # FIXME : Doesn't use the home parameter to select the appropriate home ! - for k, v in self.homes.items(): - self.homeid = k - C = v.get('cameras') - P = v.get('persons') - S = v.get('smokedetectors') - E = v.get('events') - S or logger.warning('No smoke detector found') - C or logger.warning('No Cameras found') - P or logger.warning('No Persons found') - E or logger.warning('No events found') - if not (C or P or S or E): - raise NoDevice("No device found in home %s" % k) + self.homes = self.rawData['homes'][0] + for d in self.rawData['homes'] : + if home == d['name']: + self.homes = d + else: + pass + # + #print (self.homes.keys()) + #dict_keys(['id', 'name', 'persons', 'place', 'cameras', 'smokedetectors', 'events']) + self.homeid = self.homes['id'] + C = self.homes['cameras'] + P = self.homes['persons'] + S = self.homes['smokedetectors'] + E = None + # events not always in self.homes + if 'events' in self.homes.keys(): + E = self.homes['events'] + # + if not S: + logger.warning('No smoke detector found') + if not C: + logger.warning('No Cameras found') + if not P: + logger.warning('No Persons found') + if not E: + logger.warning('No events found') + # if not (C or P or S or E): + # raise NoDevice("No device found in home %s" % k) if S or C or P or E: - self.default_home = home or list(self.homes.values())[0]['name'] + self.default_home = home or self.homes['name'] # Split homes data by category self.persons = {} self.events = {} @@ -644,6 +698,7 @@ def __init__(self, authData, home=None): c["home_id"] = curHome['id'] for camera,e in self.events.items(): self.lastEvent[camera] = e[sorted(e)[-1]] + #self.default_home has no key homeId use homeName instead! if not self.cameras[self.default_home] : raise NoDevice("No camera available in default home") self.default_camera = list(self.cameras[self.default_home].values())[0] else: @@ -921,6 +976,9 @@ def __init__(self, authData, home=None): #print (h.keys()) if home in (h["name"], h["id"]): self.Homes_Data = h + else: + self.Homes_Data = self.rawData[0] + self.homeid = self.Homes_Data['id'] if not self.Homes_Data : raise NoDevice("No Devices available") @@ -932,10 +990,10 @@ class HomeCoach: authData (clientAuth): Authentication information with a working access Token home : Home name or id of the home who's HomeCoach belongs to """ - # FIXME: home parameter not used, unpredictible behavior - def __init__(self, authData, home=None): - # I don't own a HomeCoach thus I am not able to test the HomeCoach support + def __init__(self, authData): + # I don't own a HomeCoach thus I am not able to test the HomeCoach support + # Homecoach does not need or use HomeID parameter # warnings.warn("The HomeCoach code is not tested due to the lack of test environment.\n", RuntimeWarning ) # "As Netatmo is continuously breaking API compatibility, risk that current bindings are wrong is high.\n" \ # "Please report found issues (https://github.com/philippelt/netatmo-api-python/issues)" @@ -949,34 +1007,39 @@ def __init__(self, authData, home=None): # homecoach data if not self.rawData : raise NoDevice("No HomeCoach available") - for h in self.rawData: - # FIXME: This loop is nonsense (always end with the last value) - self.HomecoachDevice = h -# print ('Homecoach = ', self.HomecoachDevice) -# print (' ') -# print ('Homecoach_data = ', self.rawData[i]['dashboard_data']) -# print (' ') + def HomecoachDevice(self, hid=""): + for device in self.rawData: + if hid == device['_id']: + return device + return None - def Dashboard(self): - D = self.HomecoachDevice['dashboard_data'] - return D + def Dashboard(self, hid=""): + #D = self.HomecoachDevice['dashboard_data'] + for device in self.rawData: + if hid == device['_id']: + D = device['dashboard_data'] + return D - # FIXME: Exclusion of outdated info is not handled (exclude parameter unused) def lastData(self, hid=None, exclude=0): - if hid is not None: - s = self.HomecoachDevice['dashboard_data']['time_utc'] - _id = self.HomecoachDevice[hid] - return {'When':s}, {'_id':_id} - return {'When': 0 }, {'_id': hid} + for device in self.rawData: + if hid == device['_id']: + # LastData in HomeCoach + #s = self.HomecoachDevice['dashboard_data']['time_utc'] + # Define oldest acceptable sensor measure event + limit = (time.time() - exclude) if exclude else 0 + ds = device['dashboard_data']['time_utc'] + return { '_id': hid, 'When': ds if device.get('time_utc',limit+10) > limit else 0} + else: + pass - def checkNotUpdated(self, res, _id, delay=3600): + def checkNotUpdated(self, res, hid, delay=3600): ret = [] - if time.time()-res['When'] > delay : ret.append({_id: 'Device Not Updated'}) + if time.time()-res['When'] > delay : ret.append({hid: 'Device Not Updated'}) return ret if ret else None - def checkUpdated(self, res, _id, delay=3600): + def checkUpdated(self, res, hid, delay=3600): ret = [] - if time.time()-res['When'] < delay : ret.append({_id: 'Device up-to-date'}) + if time.time()-res['When'] < delay : ret.append({hid: 'Device up-to-date'}) return ret if ret else None @@ -1099,7 +1162,6 @@ def getStationMinMaxTH(station=None, module=None, home=None): try: homes = HomeData(authorization) - homeid = homes.homeid except NoDevice : logger.warning("No home available for testing") @@ -1107,15 +1169,31 @@ def getStationMinMaxTH(station=None, module=None, home=None): thermostat = ThermostatData(authorization) Default_relay = thermostat.Relay_Plug() Default_thermostat = thermostat.Thermostat_Data() + thermostat.getThermostat() + print (thermostat.moduleNamesList()) + #print (thermostat.getModuleByName(name)) except NoDevice: logger.warning("No thermostat avaible for testing") try: + print (' ') + logger.info("Homes Data") + #homesdata = HomesData(authorization, homeid) homesdata = HomesData(authorization) + homeid = homesdata.homeid except NoDevice: logger.warning("No HomesData avaible for testing") try: + print (' ') + logger.info("Home Status") + HomeStatus(authorization, homeid) + except NoDevice: + logger.warning("No Home available for testing") + + try: + print (' ') + logger.info("HomeCoach") Homecoach = HomeCoach(authorization) except NoDevice: logger.warning("No HomeCoach avaible for testing")