def get_weight(creds): client = NokiaApi(creds) # Refresh credentials if necessary # creds = client.get_credentials() # http://developer.withings.com/oauth2/#tag/measure%2Fpaths%2Fhttps%3A~1~1wbsapi.withings.net~1measure%3Faction%3Dgetmeas%2Fget # lastupdate for syncing... # 'timezone': 'America/Los_Angeles', # 'updatetime': <Arrow [2018-10-12T08:14:44+00:00]>} measures = client.get_measures(meastype=1, category=1, lastupdate=1534748400) # limit=1) for measure in measures: ''' pprint(vars(measure)) pprint('---') pprint(vars(measures)) sys.exit() ''' pprint(measure.date) pprint(measure.weight) print()
def pull_withings_data(): # UTC dates will get sampled into daily if withings_connected(): client = NokiaApi(nokia_creds(current_token_dict()), refresh_cb=save_withings_token) df = pd.DataFrame( [measure.__dict__ for measure in client.get_measures()]) df['date_utc'] = df['date'].apply(lambda x: datetime.strptime( str(x.format('YYYY-MM-DD HH:mm:ss')), '%Y-%m-%d %H:%M:%S')) df = df.drop(columns=['date']) df = df.set_index(df['date_utc']) df = df[['weight', 'fat_ratio', 'hydration']] # Convert to lbs df['weight'] *= 2.20462 # Filter to days later than what is already in db session, engine = db_connect() withings_max_date = session.query(func.max( withings.date_utc)).first()[0] withings_max_date = datetime.strptime( '1991-08-30 00:00:00', '%Y-%m-%d %H:%M:%S' ) if not withings_max_date else withings_max_date engine.dispose() session.close() df = df[(df.index > withings_max_date) & (~np.isnan(df['weight'])) & (~np.isnan(df['fat_ratio']))] if len(df) > 0: dash_app.server.logger.info('New withings measurements found!') df.to_sql('withings', engine, if_exists='append', index=True)
def test_set_token(self): """ Make sure NokiaApi.set_token makes the expected changes """ timestamp = int(( datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1) ).total_seconds()) creds = NokiaCredentials(token_expiry=timestamp) api = NokiaApi(creds) token = { 'access_token': 'fakeat', 'refresh_token': 'fakert', 'expires_in': 100, } api.set_token(token) self.assertEqual(api.token, token) self.assertEqual(api.get_credentials().access_token, 'fakeat') self.assertEqual(api.get_credentials().refresh_token, 'fakert') # Need to check 100 or 101 in case a second ticked over during testing self.assertTrue( int(api.credentials.token_expiry) == (timestamp + 100) or int(api.credentials.token_expiry) == (timestamp + 101) )
def __init__(self, config): """Initialize the data object.""" from nokia import NokiaApi, NokiaCredentials credentials = NokiaCredentials(config.get("access_token"), config.get("access_token_secret"), config.get("consumer_key"), config.get("consumer_secret"), config.get("user_id")) self._api = NokiaApi(credentials) self.data = None self._update()
def withings_connected(): token_dict = current_token_dict() try: if token_dict: creds = nokia_creds(token_dict) client = NokiaApi(credentials=creds, refresh_cb=save_withings_token) measures = client.get_measures(limit=1) dash_app.server.logger.debug('Withings Connected') return True except BaseException as e: dash_app.server.logger.error('Withings not connected') dash_app.server.logger.error(e) return False
def setUp(self): self.mock_api = True if self.mock_api: self.creds = NokiaCredentials() else: config = configparser.ConfigParser() config.read('nokia.conf') self.creds = NokiaCredentials( consumer_key=config.get('nokia', 'consumer_key'), consumer_secret=config.get('nokia', 'consumer_secret'), access_token=config.get('nokia', 'access_token'), access_token_secret=config.get('nokia', 'access_token_secret'), user_id=config.get('nokia', 'user_id')) self.api = NokiaApi(self.creds)
def test_attributes(self): """ Make sure the NokiaApi objects have the right attributes """ assert hasattr(NokiaApi, 'URL') creds = NokiaCredentials(user_id='FAKEID') api = NokiaApi(creds) assert hasattr(api, 'credentials') assert hasattr(api, 'oauth') assert hasattr(api, 'client')
def test_attributes(self): """ Make sure the NokiaApi objects have the right attributes """ assert hasattr(NokiaApi, 'URL') creds = NokiaCredentials(user_id='FAKEID', token_expiry='123412341234') api = NokiaApi(creds) assert hasattr(api, 'credentials') assert hasattr(api, 'token') assert hasattr(api, 'client') assert hasattr(api, 'refresh_cb')
def __init__(self, smarthome, consumer_key, consumer_secret, access_token, access_token_secret, user_id, cycle=300): self.logger = logging.getLogger(__name__) self._sh = smarthome self._consumer_key = consumer_key self._consumer_secret = consumer_secret self._access_token = access_token self._access_token_secret = access_token_secret self._auth = NokiaAuth(self._consumer_key, self._consumer_secret) self._user_id = user_id self._creds = NokiaCredentials(self._access_token, self._access_token_secret, self._consumer_key, self._consumer_secret, self._user_id) self._client = NokiaApi(self._creds) self._cycle = cycle self._items = {} if not self.init_webinterface(): self._init_complete = False
def test_attribute_defaults(self): """ Make sure NokiaApi object attributes have the correct defaults """ self.assertEqual(NokiaApi.URL, 'https://api.health.nokia.com') creds = NokiaCredentials(user_id='FAKEID') api = NokiaApi(creds) self.assertEqual(api.credentials, creds) self.assertEqual(api.client.auth, api.oauth) self.assertEqual(api.client.params, {'userid': creds.user_id})
def test_set_token_refresh_cb(self): """ Make sure set_token calls refresh_cb when specified """ timestamp = int((datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)).total_seconds()) creds = NokiaCredentials(token_expiry=timestamp) refresh_cb = MagicMock() api = NokiaApi(creds, refresh_cb=refresh_cb) token = { 'access_token': 'fakeat', 'refresh_token': 'fakert', 'expires_in': 100, } api.set_token(token) self.assertEqual(api.token, token) refresh_cb.assert_called_once_with(token)
def refresh_weight(cfg_file, engine, db_df): print("REFRESHING WEIGHT...") parser = configparser.ConfigParser() parser.read(cfg_file) client_id = parser.get('nokia', 'client_id') client_secret = parser.get('nokia', 'client_secret') access_token = parser.get('nokia', 'access_token') token_expiry = parser.get('nokia', 'token_expiry') token_type = parser.get('nokia', 'token_type') refresh_token = parser.get('nokia', 'refresh_token') user_id = parser.get('nokia', 'user_id') creds = NokiaCredentials(access_token=access_token, token_expiry=token_expiry, token_type=token_type, refresh_token=refresh_token, user_id=user_id, client_id=client_id, consumer_secret=client_secret) client = NokiaApi( creds, refresh_cb=(lambda x: persist_nokia_refresh_token(x, cfg_file))) [date_start, date_end] = get_target_date_endpoints('weight', db_df) date_query = date_start date_diff = date_end - date_query days = date_diff.days + 2 measures = client.get_measures(meastype=1, limit=days) weight_json = [{ 'weight': (float("{:.1f}".format(x.weight * 2.20462))), 'date': x.date.strftime('%Y-%m-%d') } for x in measures] date_values = [[pd.to_datetime(x['date']), x['weight']] for x in weight_json] date_values_imp = [[pd.to_datetime(x['date']), np.nan] for x in weight_json] updated_df = insert_values(date_values, 'weight', db_df) updated_df = insert_values(date_values_imp, 'weight_imputed', updated_df) with engine.connect() as conn, conn.begin(): updated_df.to_sql('fitness', conn, if_exists='replace') return updated_df
def test_set_token_refresh_cb(self): """ Make sure set_token calls refresh_cb when specified """ timestamp = int(( datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1) ).total_seconds()) creds = NokiaCredentials(token_expiry=timestamp) refresh_cb = MagicMock() api = NokiaApi(creds, refresh_cb=refresh_cb) token = { 'access_token': 'fakeat', 'refresh_token': 'fakert', 'expires_in': 100, } api.set_token(token) self.assertEqual(api.token, token) refresh_cb.assert_called_once_with(token)
def test_attribute_defaults(self): """ Make sure NokiaApi object attributes have the correct defaults """ self.assertEqual(NokiaApi.URL, 'https://api.health.nokia.com') creds = NokiaCredentials(user_id='FAKEID', token_expiry='123412341234') api = NokiaApi(creds) self.assertEqual(api.credentials, creds) self.assertEqual(api.client.params, {}) self.assertEqual(api.client.token, api.token) self.assertEqual(api.refresh_cb, None)
def test_set_token(self): """ Make sure NokiaApi.set_token makes the expected changes """ timestamp = int((datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)).total_seconds()) creds = NokiaCredentials(token_expiry=timestamp) api = NokiaApi(creds) token = { 'access_token': 'fakeat', 'refresh_token': 'fakert', 'expires_in': 100, } api.set_token(token) self.assertEqual(api.token, token) self.assertEqual(api.get_credentials().access_token, 'fakeat') self.assertEqual(api.get_credentials().refresh_token, 'fakert') # Need to check 100 or 101 in case a second ticked over during testing self.assertTrue( int(api.credentials.token_expiry) == (timestamp + 100) or int(api.credentials.token_expiry) == (timestamp + 101))
def setUp(self): self.mock_api = True creds_attrs = [ 'access_token', 'token_expiry', 'token_type', 'refresh_token', 'user_id', 'client_id', 'consumer_secret', ] if self.mock_api: creds_args = {a: 'fake' + a for a in creds_attrs} creds_args.update({ 'token_expiry': '123412341234', 'token_type': 'Bearer', }) self.creds = NokiaCredentials(**creds_args) else: config = configparser.ConfigParser() config.read('nokia.conf') creds_args = {a: config.get('nokia', a) for a in creds_attrs} self.creds = NokiaCredentials(**creds_args) self.api = NokiaApi(self.creds)
def __init__(self): try: with open('personal_dashboard/nokia_data.pkl', 'rb') as pickle_file: nokia = pickle.load(pickle_file) self.measures = nokia.get_measures() measures = nokia.get_measures(limit=1) self.weight = round(float(measures[0].weight) * 2.20462, 2) pickle_file.close() except: auth = NokiaAuth(WITHINGS_KEYS['API_KEY'], WITHINGS_KEYS['API_SECRET']) authorize_url = auth.get_authorize_url() print("Go to %s allow the app and copy your oauth_verifier" % authorize_url) oauth_verifier = input('Please enter your oauth_verifier: ') creds = auth.get_credentials(oauth_verifier) client = NokiaApi(creds) with open('personal_dashboard/nokia_data.pkl', 'wb') as output: pickle.dump(client, output, pickle.HIGHEST_PROTOCOL) self.measures = client.get_measures() measures = client.get_measures(limit=1) #Convert Kg to Lbs self.weight = round(float(measures[0].weight) * 2.20462, 2)
class NokiaHealthDataManager(object): """Manage data from Nokia Health. Bundles downloading all attributes for a measurement.""" def __init__(self, config): """Initialize the data object.""" from nokia import NokiaApi, NokiaCredentials credentials = NokiaCredentials(config.get("access_token"), config.get("access_token_secret"), config.get("consumer_key"), config.get("consumer_secret"), config.get("user_id")) self._api = NokiaApi(credentials) self.data = None self._update() @Throttle(SCAN_INTERVAL) def _update(self): """Get the latest data from Nokia Health.""" self.data = self._api.get_measures(limit=1)
def _add_device(creds): client = NokiaApi(creds) nokia = NokiaSensor(hass, client) yield from nokia.async_update() return async_add_devices([nokia])
def test_get_credentials(self): """ Make sure NokiaApi returns the credentials as expected """ creds = NokiaCredentials(token_expiry=0) api = NokiaApi(creds)
class TestNokiaApi(unittest.TestCase): def setUp(self): self.mock_api = True if self.mock_api: self.creds = NokiaCredentials() else: config = configparser.ConfigParser() config.read('nokia.conf') self.creds = NokiaCredentials( consumer_key=config.get('nokia', 'consumer_key'), consumer_secret=config.get('nokia', 'consumer_secret'), access_token=config.get('nokia', 'access_token'), access_token_secret=config.get('nokia', 'access_token_secret'), user_id=config.get('nokia', 'user_id')) self.api = NokiaApi(self.creds) def test_attributes(self): """ Make sure the NokiaApi objects have the right attributes """ assert hasattr(NokiaApi, 'URL') creds = NokiaCredentials(user_id='FAKEID') api = NokiaApi(creds) assert hasattr(api, 'credentials') assert hasattr(api, 'oauth') assert hasattr(api, 'client') def test_attribute_defaults(self): """ Make sure NokiaApi object attributes have the correct defaults """ self.assertEqual(NokiaApi.URL, 'https://api.health.nokia.com') creds = NokiaCredentials(user_id='FAKEID') api = NokiaApi(creds) self.assertEqual(api.credentials, creds) self.assertEqual(api.client.auth, api.oauth) self.assertEqual(api.client.params, {'userid': creds.user_id}) def test_request(self): """ Make sure the request method builds the proper URI and returns the request body as a python dict. """ self.mock_request({}) resp = self.api.request('fake_service', 'fake_action') Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/fake_service', params={'action': 'fake_action'}) self.assertEqual(resp, {}) def test_request_params(self): """ Check that the request method passes along extra params and works with different HTTP methods """ self.mock_request({}) resp = self.api.request('user', 'getbyuserid', params={'p2': 'p2'}, method='POST') Session.request.assert_called_once_with( 'POST', 'https://api.health.nokia.com/user', params={ 'p2': 'p2', 'action': 'getbyuserid' }) self.assertEqual(resp, {}) def test_request_error(self): """ Check that requests raises an exception when there is an error """ self.mock_request('', status=1) self.assertRaises(Exception, self.api.request, ('user', 'getbyuserid')) def test_get_user(self): """ Check that the get_user method fetches the right URL """ self.mock_request({ 'users': [{ 'id': 1111111, 'birthdate': 364305600, 'lastname': 'Baggins', 'ispublic': 255, 'firstname': 'Frodo', 'fatmethod': 131, 'gender': 0, 'shortname': 'FRO' }] }) resp = self.api.get_user() Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/user', params={'action': 'getbyuserid'}) self.assertEqual(type(resp), dict) assert 'users' in resp self.assertEqual(type(resp['users']), list) self.assertEqual(len(resp['users']), 1) self.assertEqual(resp['users'][0]['firstname'], 'Frodo') self.assertEqual(resp['users'][0]['lastname'], 'Baggins') def test_get_sleep(self): """ Check that get_sleep fetches the appropriate URL, the response looks correct, and the return value is a NokiaSleep object with the correct attributes """ body = { "series": [{ "startdate": 1387235398, "state": 0, "enddate": 1387235758 }, { "startdate": 1387243618, "state": 1, "enddate": 1387244518 }], "model": 16 } self.mock_request(body) resp = self.api.get_sleep() Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/v2/sleep', params={'action': 'get'}) self.assertEqual(type(resp), NokiaSleep) self.assertEqual(resp.model, body['model']) self.assertEqual(type(resp.series), list) self.assertEqual(len(resp.series), 2) self.assertEqual(type(resp.series[0]), NokiaSleepSeries) self.assertEqual(resp.series[0].startdate.timestamp, body['series'][0]['startdate']) self.assertEqual(resp.series[0].enddate.timestamp, body['series'][0]['enddate']) self.assertEqual(resp.series[1].state, 1) def test_get_activities(self): """ Check that get_activities fetches the appropriate URL, the response looks correct, and the return value is a list of NokiaActivity objects """ body = { "date": "2013-04-10", "steps": 6523, "distance": 4600, "calories": 408.52, "elevation": 18.2, "soft": 5880, "moderate": 1080, "intense": 540, "timezone": "Europe/Berlin" } self.mock_request(body) resp = self.api.get_activities() Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/v2/measure', params={'action': 'getactivity'}) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 1) self.assertEqual(type(resp[0]), NokiaActivity) # No need to assert all attributes, that happens elsewhere self.assertEqual(resp[0].data, body) # Test multiple activities new_body = { 'activities': [ body, { "date": "2013-04-11", "steps": 223, "distance": 400, "calories": 108.52, "elevation": 1.2, "soft": 160, "moderate": 42, "intense": 21, "timezone": "Europe/Berlin" } ] } self.mock_request(new_body) resp = self.api.get_activities() Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/v2/measure', params={'action': 'getactivity'}) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 2) self.assertEqual(type(resp[0]), NokiaActivity) self.assertEqual(type(resp[1]), NokiaActivity) self.assertEqual(resp[0].data, new_body['activities'][0]) self.assertEqual(resp[1].data, new_body['activities'][1]) def test_get_measures(self): """ Check that get_measures fetches the appriate URL, the response looks correct, and the return value is a NokiaMeasures object """ body = { 'updatetime': 1409596058, 'measuregrps': [{ 'attrib': 2, 'measures': [{ 'unit': -1, 'type': 1, 'value': 860 }], 'date': 1409361740, 'category': 1, 'grpid': 111111111 }, { 'attrib': 2, 'measures': [{ 'unit': -2, 'type': 4, 'value': 185 }], 'date': 1409361740, 'category': 1, 'grpid': 111111112 }] } self.mock_request(body) resp = self.api.get_measures() Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/measure', params={'action': 'getmeas'}) self.assertEqual(type(resp), NokiaMeasures) self.assertEqual(len(resp), 2) self.assertEqual(type(resp[0]), NokiaMeasureGroup) self.assertEqual(resp[0].weight, 86.0) self.assertEqual(resp[1].height, 1.85) # Test limit=1 body['measuregrps'].pop() self.mock_request(body) resp = self.api.get_measures(limit=1) Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/measure', params={ 'action': 'getmeas', 'limit': 1 }) self.assertEqual(len(resp), 1) self.assertEqual(resp[0].weight, 86.0) def test_get_measures_lastupdate_date(self): """Check that dates get converted to timestampse for API calls""" self.mock_request({'updatetime': 1409596058, 'measuregrps': []}) self.api.get_measures(lastupdate=datetime.date(2014, 9, 1)) Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/measure', params={ 'action': 'getmeas', 'lastupdate': 1409529600 }) def test_get_measures_lastupdate_datetime(self): """Check that datetimes get converted to timestampse for API calls""" self.mock_request({'updatetime': 1409596058, 'measuregrps': []}) self.api.get_measures(lastupdate=datetime.datetime(2014, 9, 1)) Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/measure', params={ 'action': 'getmeas', 'lastupdate': 1409529600 }) def test_get_measures_lastupdate_arrow(self): """Check that arrow dates get converted to timestampse for API calls""" self.mock_request({'updatetime': 1409596058, 'measuregrps': []}) self.api.get_measures(lastupdate=arrow.get('2014-09-01')) Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/measure', params={ 'action': 'getmeas', 'lastupdate': 1409529600 }) def test_subscribe(self): """ Check that subscribe fetches the right URL and returns the expected results """ # Unspecified appli self.mock_request(None) resp = self.api.subscribe('http://www.example.com/', 'fake_comment') Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/notify', params={ 'action': 'subscribe', 'comment': 'fake_comment', 'callbackurl': 'http://www.example.com/' }) self.assertEqual(resp, None) # appli=1 self.mock_request(None) resp = self.api.subscribe('http://www.example.com/', 'fake_comment', appli=1) Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/notify', params={ 'action': 'subscribe', 'appli': 1, 'comment': 'fake_comment', 'callbackurl': 'http://www.example.com/' }) self.assertEqual(resp, None) def test_unsubscribe(self): """ Check that unsubscribe fetches the right URL and returns the expected results """ # Unspecified appli self.mock_request(None) resp = self.api.unsubscribe('http://www.example.com/') Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/notify', params={ 'action': 'revoke', 'callbackurl': 'http://www.example.com/' }) self.assertEqual(resp, None) # appli=1 self.mock_request(None) resp = self.api.unsubscribe('http://www.example.com/', appli=1) Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/notify', params={ 'action': 'revoke', 'appli': 1, 'callbackurl': 'http://www.example.com/' }) self.assertEqual(resp, None) def test_is_subscribed(self): """ Check that is_subscribed fetches the right URL and returns the expected results """ url = 'https://api.health.nokia.com/notify' params = { 'callbackurl': 'http://www.example.com/', 'action': 'get', 'appli': 1 } self.mock_request({'expires': 2147483647, 'comment': 'fake_comment'}) resp = self.api.is_subscribed('http://www.example.com/') Session.request.assert_called_once_with('GET', url, params=params) self.assertEquals(resp, True) # Not subscribed self.mock_request(None, status=343) resp = self.api.is_subscribed('http://www.example.com/') Session.request.assert_called_once_with('GET', url, params=params) self.assertEquals(resp, False) def test_list_subscriptions(self): """ Check that list_subscriptions fetches the right URL and returns the expected results """ self.mock_request( {'profiles': [{ 'comment': 'fake_comment', 'expires': 2147483647 }]}) resp = self.api.list_subscriptions() Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/notify', params={ 'action': 'list', 'appli': 1 }) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 1) self.assertEqual(resp[0]['comment'], 'fake_comment') self.assertEqual(resp[0]['expires'], 2147483647) # No subscriptions self.mock_request({'profiles': []}) resp = self.api.list_subscriptions() Session.request.assert_called_once_with( 'GET', 'https://api.health.nokia.com/notify', params={ 'action': 'list', 'appli': 1 }) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 0) def mock_request(self, body, status=0): if self.mock_api: json_content = {'status': status} if body is not None: json_content['body'] = body response = MagicMock() response.content = json.dumps(json_content).encode('utf8') Session.request = MagicMock(return_value=response)
def _add_device(creds): client = NokiaApi(creds) withings = WithingsSensor(hass, client) yield from withings.async_update() return async_add_devices([withings])
class NokiaHealth(SmartPlugin): ALLOW_MULTIINSTANCE = True PLUGIN_VERSION = "1.5.2" BASE_URL = "https://api.health.nokia.com/" ALLOWED_MEASURE_TYPES = [1, 4, 5, 6, 8, 11] # see https://developer.health.nokia.com/api/doc def __init__(self, smarthome, consumer_key, consumer_secret, access_token, access_token_secret, user_id, cycle=300): self.logger = logging.getLogger(__name__) self._sh = smarthome self._consumer_key = consumer_key self._consumer_secret = consumer_secret self._access_token = access_token self._access_token_secret = access_token_secret self._auth = NokiaAuth(self._consumer_key, self._consumer_secret) self._user_id = user_id self._creds = NokiaCredentials(self._access_token, self._access_token_secret, self._consumer_key, self._consumer_secret, self._user_id) self._client = NokiaApi(self._creds) self._cycle = cycle self._items = {} if not self.init_webinterface(): self._init_complete = False def run(self): self.alive = True self.scheduler_add(__name__, self._update_loop, cycle=self._cycle) def stop(self): self.alive = False def _update_loop(self): """ Starts the update loop for all known items. """ self.logger.debug('Starting update loop for instance %s' % self.get_instance_name()) if not self.alive: return self._update() def _update(self): """ Updates information on diverse items Mappings: ('weight', 1), ('height', 4), ('fat_free_mass', 5), ('fat_ratio', 6), ('fat_mass_weight', 8), ('diastolic_blood_pressure', 9), ('systolic_blood_pressure', 10), ('heart_pulse', 11), ('temperature', 12), ('spo2', 54), ('body_temperature', 71), ('skin_temperature', 72), ('muscle_mass', 76), ('hydration', 77), ('bone_mass', 88), ('pulse_wave_velocity', 91) """ measures = self._client.get_measures() last_measure = measures[0] if last_measure.get_measure(11) is not None and 'heart_pulse' in self._items: self._items['heart_pulse'](last_measure.get_measure(11)) self.logger.debug(last_measure.get_measure(11)) # Bugfix for strange behavior of returning heart_pulse as seperate dataset.. if last_measure.get_measure(1) is None: last_measure = measures[1] if last_measure.get_measure(1) is not None and 'weight' in self._items: self._items['weight'](last_measure.get_measure(1)) self.logger.debug(last_measure.get_measure(1)) if last_measure.get_measure(4) is not None and 'height' in self._items: self._items['height'](last_measure.get_measure(4)) self.logger.debug(last_measure.get_measure(4)) if last_measure.get_measure(5) is not None and 'fat_free_mass' in self._items: self._items['fat_free_mass'](last_measure.get_measure(5)) self.logger.debug(last_measure.get_measure(5)) if last_measure.get_measure(6) is not None and 'fat_ratio' in self._items: self._items['fat_ratio'](last_measure.get_measure(6)) self.logger.debug(last_measure.get_measure(6)) if last_measure.get_measure(8) is not None and 'fat_mass_weight' in self._items: self._items['fat_mass_weight'](last_measure.get_measure(8)) self.logger.debug(last_measure.get_measure(8)) if last_measure.get_measure(9) is not None and 'diastolic_blood_pressure' in self._items: self._items['diastolic_blood_pressure'](last_measure.get_measure(9)) self.logger.debug(last_measure.get_measure(9)) if last_measure.get_measure(10) is not None and 'systolic_blood_pressure' in self._items: self._items['systolic_blood_pressure'](last_measure.get_measure(10)) self.logger.debug(last_measure.get_measure(10)) if last_measure.get_measure(11) is not None and 'heart_pulse' in self._items: self._items['heart_pulse'](last_measure.get_measure(11)) self.logger.debug(last_measure.get_measure(11)) if last_measure.get_measure(12) is not None and 'temperature' in self._items: self._items['temperature'](last_measure.get_measure(12)) self.logger.debug(last_measure.get_measure(12)) if last_measure.get_measure(54) is not None and 'spo2' in self._items: self._items['spo2'](last_measure.get_measure(54)) self.logger.debug(last_measure.get_measure(54)) if last_measure.get_measure(71) is not None and 'body_temperature' in self._items: self._items['body_temperature'](last_measure.get_measure(71)) self.logger.debug(last_measure.get_measure(71)) if last_measure.get_measure(72) is not None and 'skin_temperature' in self._items: self._items['skin_temperature'](last_measure.get_measure(72)) self.logger.debug(last_measure.get_measure(72)) if last_measure.get_measure(76) is not None and 'muscle_mass' in self._items: self._items['muscle_mass'](last_measure.get_measure(76)) self.logger.debug(last_measure.get_measure(76)) if last_measure.get_measure(77) is not None and 'hydration' in self._items: self._items['hydration'](last_measure.get_measure(77)) self.logger.debug(last_measure.get_measure(77)) if last_measure.get_measure(88) is not None and 'bone_mass' in self._items: self._items['bone_mass'](last_measure.get_measure(88)) self.logger.debug(last_measure.get_measure(88)) if last_measure.get_measure(91) is not None and 'pulse_wave_velocity' in self._items: self._items['pulse_wave_velocity'](last_measure.get_measure(91)) self.logger.debug(last_measure.get_measure(91)) if 'height' in self._items and ('bmi' in self._items or 'bmi_text' in self._items) and last_measure.get_measure( 1) is not None: if self._items['height']() > 0: bmi = round( last_measure.get_measure(1) / ((self._items['height']()) * (self._items['height']())), 2) if 'bmi' in self._items: self._items['bmi'](bmi) if 'bmi_text' in self._items: if bmi < 16: self._items['bmi_text']('starkes Untergewicht') elif 16 <= bmi < 17: self._items['bmi_text']('mäßiges Untergewicht ') elif 17 <= bmi < 18.5: self._items['bmi_text']('leichtes Untergewicht ') elif 18.5 <= bmi < 25: self._items['bmi_text']('Normalgewicht') elif 25 <= bmi < 30: self._items['bmi_text']('Präadipositas (Übergewicht)') elif 30 <= bmi < 35: self._items['bmi_text']('Adipositas Grad I') elif 35 <= bmi < 40: self._items['bmi_text']('Adipositas Grad II') elif 40 <= bmi: self._items['bmi_text']('Adipositas Grad III') else: self.logger.error( "Cannot calculate BMI: height is 0, please set height (in m) for height item manually.") else: self.logger.error("Cannot calculate BMI: height and / or bmi item missing.") def parse_item(self, item): """ Default plugin parse_item method. Is called when the plugin is initialized. Selects each item corresponding to the Nokia Health identifier and adds it to an internal array :param item: The item to process. """ # items specific to call monitor if self.get_iattr_value(item.conf, 'nh_type') in ['weight', 'height', 'fat_free_mass', 'fat_mass_weight', 'fat_ratio', 'fat_mass_weight', 'diastolic_blood_pressure', 'systolic_blood_pressure', 'heart_pulse', 'temperature', 'spo2', 'body_temperature', 'skin_temperature', 'muscle_mass', 'hydration', 'bone_mass', 'pulse_wave_velocity', 'bmi', 'bmi_text']: self._items[self.get_iattr_value(item.conf, 'nh_type')] = item def get_items(self): return self._items def init_webinterface(self): """" Initialize the web interface for this plugin This method is only needed if the plugin is implementing a web interface """ try: self.mod_http = Modules.get_instance().get_module( 'http') # try/except to handle running in a core version that does not support modules except: self.mod_http = None if self.mod_http == None: self.logger.error("Plugin '{}': Not initializing the web interface".format(self.get_shortname())) return False # set application configuration for cherrypy webif_dir = self.path_join(self.get_plugin_dir(), 'webif') config = { '/': { 'tools.staticdir.root': webif_dir, }, '/static': { 'tools.staticdir.on': True, 'tools.staticdir.dir': 'static' } } # Register the web interface as a cherrypy app self.mod_http.register_webif(WebInterface(webif_dir, self), self.get_shortname(), config, self.get_classname(), self.get_instance_name(), description='') return True
class TestNokiaApi(unittest.TestCase): def setUp(self): self.mock_api = True creds_attrs = [ 'access_token', 'token_expiry', 'token_type', 'refresh_token', 'user_id', 'client_id', 'consumer_secret', ] if self.mock_api: creds_args = {a: 'fake' + a for a in creds_attrs} creds_args.update({ 'token_expiry': '123412341234', 'token_type': 'Bearer', }) self.creds = NokiaCredentials(**creds_args) else: config = configparser.ConfigParser() config.read('nokia.conf') creds_args = {a: config.get('nokia', a) for a in creds_attrs} self.creds = NokiaCredentials(**creds_args) self.api = NokiaApi(self.creds) def _req_url(self, url): return url + '?access_token=fakeaccess_token' def _req_kwargs(self, extra_params): params = { 'userid': 'fakeuser_id', } params.update(extra_params) return { 'data': None, 'headers': None, 'params': params, } def test_attributes(self): """ Make sure the NokiaApi objects have the right attributes """ assert hasattr(NokiaApi, 'URL') creds = NokiaCredentials(user_id='FAKEID', token_expiry='123412341234') api = NokiaApi(creds) assert hasattr(api, 'credentials') assert hasattr(api, 'token') assert hasattr(api, 'client') assert hasattr(api, 'refresh_cb') def test_attribute_defaults(self): """ Make sure NokiaApi object attributes have the correct defaults """ self.assertEqual(NokiaApi.URL, 'https://wbsapi.withings.net') creds = NokiaCredentials(user_id='FAKEID', token_expiry='123412341234') api = NokiaApi(creds) self.assertEqual(api.credentials, creds) self.assertEqual(api.client.params, {}) self.assertEqual(api.client.token, api.token) self.assertEqual(api.refresh_cb, None) def test_get_credentials(self): """ Make sure NokiaApi returns the credentials as expected """ creds = NokiaCredentials(token_expiry=0) api = NokiaApi(creds) def test_set_token(self): """ Make sure NokiaApi.set_token makes the expected changes """ timestamp = int(( datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1) ).total_seconds()) creds = NokiaCredentials(token_expiry=timestamp) api = NokiaApi(creds) token = { 'access_token': 'fakeat', 'refresh_token': 'fakert', 'expires_in': 100, } api.set_token(token) self.assertEqual(api.token, token) self.assertEqual(api.get_credentials().access_token, 'fakeat') self.assertEqual(api.get_credentials().refresh_token, 'fakert') # Need to check 100 or 101 in case a second ticked over during testing self.assertTrue( int(api.credentials.token_expiry) == (timestamp + 100) or int(api.credentials.token_expiry) == (timestamp + 101) ) def test_set_token_refresh_cb(self): """ Make sure set_token calls refresh_cb when specified """ timestamp = int(( datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1) ).total_seconds()) creds = NokiaCredentials(token_expiry=timestamp) refresh_cb = MagicMock() api = NokiaApi(creds, refresh_cb=refresh_cb) token = { 'access_token': 'fakeat', 'refresh_token': 'fakert', 'expires_in': 100, } api.set_token(token) self.assertEqual(api.token, token) refresh_cb.assert_called_once_with(token) def test_request(self): """ Make sure the request method builds the proper URI and returns the request body as a python dict. """ self.mock_request({}) resp = self.api.request('fake_service', 'fake_action') Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/fake_service'), **self._req_kwargs({'action': 'fake_action'}) ) self.assertEqual(resp, {}) def test_request_params(self): """ Check that the request method passes along extra params and works with different HTTP methods """ self.mock_request({}) resp = self.api.request('user', 'getbyuserid', params={'p2': 'p2'}, method='POST') Session.request.assert_called_once_with( 'POST', self._req_url('https://wbsapi.withings.net/user'), **self._req_kwargs({'p2': 'p2', 'action': 'getbyuserid'}) ) self.assertEqual(resp, {}) def test_request_error(self): """ Check that requests raises an exception when there is an error """ self.mock_request('', status=1) self.assertRaises(Exception, self.api.request, ('user', 'getbyuserid')) def test_get_user(self): """ Check that the get_user method fetches the right URL """ self.mock_request({ 'users': [ {'id': 1111111, 'birthdate': 364305600, 'lastname': 'Baggins', 'ispublic': 255, 'firstname': 'Frodo', 'fatmethod': 131, 'gender': 0, 'shortname': 'FRO'} ] }) resp = self.api.get_user() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/user'), **self._req_kwargs({'action': 'getbyuserid'}) ) self.assertEqual(type(resp), dict) assert 'users' in resp self.assertEqual(type(resp['users']), list) self.assertEqual(len(resp['users']), 1) self.assertEqual(resp['users'][0]['firstname'], 'Frodo') self.assertEqual(resp['users'][0]['lastname'], 'Baggins') def test_get_sleep(self): """ Check that get_sleep fetches the appropriate URL, the response looks correct, and the return value is a NokiaSleep object with the correct attributes """ body = { "series": [{ "startdate": 1387235398, "state": 0, "enddate": 1387235758 }, { "startdate": 1387243618, "state": 1, "enddate": 1387244518 }], "model": 16 } self.mock_request(body) resp = self.api.get_sleep() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/v2/sleep'), **self._req_kwargs({'action': 'get'}) ) self.assertEqual(type(resp), NokiaSleep) self.assertEqual(resp.model, body['model']) self.assertEqual(type(resp.series), list) self.assertEqual(len(resp.series), 2) self.assertEqual(type(resp.series[0]), NokiaSleepSeries) self.assertEqual(resp.series[0].startdate.timestamp, body['series'][0]['startdate']) self.assertEqual(resp.series[0].enddate.timestamp, body['series'][0]['enddate']) self.assertEqual(resp.series[1].state, 1) def test_get_sleep_summary(self): """ Check that get_sleep_summary fetches the appropriate URL, the response looks correct, and the return value is a NokiaSleepSummary object with the correct attributes """ body = { 'more': False, 'series': [ { 'data': { 'deepsleepduration': 18660, 'durationtosleep': 0, 'durationtowakeup': 240, 'lightsleepduration': 20220, 'wakeupcount': 1, 'wakeupduration': 720 }, 'date': '2018-10-30', 'enddate': 1540897020, 'id': 900363515, 'model': 16, 'modified': 1540897246, 'startdate': 1540857420, 'timezone': 'Europe/London' }, { 'data': { 'deepsleepduration': 17040, 'durationtosleep': 360, 'durationtowakeup': 0, 'lightsleepduration': 10860, 'wakeupcount': 1, 'wakeupduration': 540 }, 'date': '2018-10-31', 'enddate': 1540973400, 'id': 901269807, 'model': 16, 'modified': 1541020749, 'startdate': 1540944960, 'timezone': 'Europe/London' } ] } self.mock_request(body) resp = self.api.get_sleep_summary() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/v2/sleep'), **self._req_kwargs({'action': 'getsummary'}) ) self.assertEqual(type(resp), NokiaSleepSummary) self.assertEqual(type(resp.series), list) self.assertEqual(len(resp.series), 2) self.assertEqual(type(resp.series[0]), NokiaSleepSummarySeries) self.assertEqual(resp.series[0].model, body['series'][0]['model']) self.assertEqual(resp.series[0].startdate.timestamp, body['series'][0]['startdate']) self.assertEqual(resp.series[0].enddate.timestamp, body['series'][0]['enddate']) self.assertEqual(resp.series[0].deepsleepduration, body['series'][0]['data']['deepsleepduration']) def test_get_activities(self): """ Check that get_activities fetches the appropriate URL, the response looks correct, and the return value is a list of NokiaActivity objects """ body = { "date": "2013-04-10", "steps": 6523, "distance": 4600, "calories": 408.52, "elevation": 18.2, "soft": 5880, "moderate": 1080, "intense": 540, "timezone": "Europe/Berlin" } self.mock_request(body) resp = self.api.get_activities() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/v2/measure'), **self._req_kwargs({'action': 'getactivity'}) ) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 1) self.assertEqual(type(resp[0]), NokiaActivity) # No need to assert all attributes, that happens elsewhere self.assertEqual(resp[0].data, body) # Test multiple activities new_body = { 'activities': [ body, { "date": "2013-04-11", "steps": 223, "distance": 400, "calories": 108.52, "elevation": 1.2, "soft": 160, "moderate": 42, "intense": 21, "timezone": "Europe/Berlin" } ] } self.mock_request(new_body) resp = self.api.get_activities() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/v2/measure'), **self._req_kwargs({'action': 'getactivity'}) ) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 2) self.assertEqual(type(resp[0]), NokiaActivity) self.assertEqual(type(resp[1]), NokiaActivity) self.assertEqual(resp[0].data, new_body['activities'][0]) self.assertEqual(resp[1].data, new_body['activities'][1]) def test_get_measures(self): """ Check that get_measures fetches the appriate URL, the response looks correct, and the return value is a NokiaMeasures object """ body = { 'updatetime': 1409596058, 'measuregrps': [ {'attrib': 2, 'measures': [ {'unit': -1, 'type': 1, 'value': 860} ], 'date': 1409361740, 'category': 1, 'grpid': 111111111}, {'attrib': 2, 'measures': [ {'unit': -2, 'type': 4, 'value': 185} ], 'date': 1409361740, 'category': 1, 'grpid': 111111112} ] } self.mock_request(body) resp = self.api.get_measures() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/measure'), **self._req_kwargs({'action': 'getmeas'}) ) self.assertEqual(type(resp), NokiaMeasures) self.assertEqual(len(resp), 2) self.assertEqual(type(resp[0]), NokiaMeasureGroup) self.assertEqual(resp[0].weight, 86.0) self.assertEqual(resp[1].height, 1.85) # Test limit=1 body['measuregrps'].pop() self.mock_request(body) resp = self.api.get_measures(limit=1) Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/measure'), **self._req_kwargs({'action': 'getmeas', 'limit': 1}) ) self.assertEqual(len(resp), 1) self.assertEqual(resp[0].weight, 86.0) def test_get_measures_lastupdate_date(self): """Check that dates get converted to timestampse for API calls""" self.mock_request({'updatetime': 1409596058, 'measuregrps': []}) self.api.get_measures(lastupdate=datetime.date(2014, 9, 1)) Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/measure'), **self._req_kwargs({'action': 'getmeas', 'lastupdate': 1409529600}) ) def test_get_measures_lastupdate_datetime(self): """Check that datetimes get converted to timestampse for API calls""" self.mock_request({'updatetime': 1409596058, 'measuregrps': []}) self.api.get_measures(lastupdate=datetime.datetime(2014, 9, 1)) Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/measure'), **self._req_kwargs({'action': 'getmeas', 'lastupdate': 1409529600}) ) def test_get_measures_lastupdate_arrow(self): """Check that arrow dates get converted to timestampse for API calls""" self.mock_request({'updatetime': 1409596058, 'measuregrps': []}) self.api.get_measures(lastupdate=arrow.get('2014-09-01')) Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/measure'), **self._req_kwargs({'action': 'getmeas', 'lastupdate': 1409529600}) ) def test_subscribe(self): """ Check that subscribe fetches the right URL and returns the expected results """ # Unspecified appli self.mock_request(None) resp = self.api.subscribe('http://www.example.com/', 'fake_comment') Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/notify'), **self._req_kwargs({ 'action': 'subscribe', 'comment': 'fake_comment', 'callbackurl': 'http://www.example.com/', }) ) self.assertEqual(resp, None) # appli=1 self.mock_request(None) resp = self.api.subscribe('http://www.example.com/', 'fake_comment', appli=1) Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/notify'), **self._req_kwargs({ 'action': 'subscribe', 'appli': 1, 'comment': 'fake_comment', 'callbackurl': 'http://www.example.com/', }) ) self.assertEqual(resp, None) def test_unsubscribe(self): """ Check that unsubscribe fetches the right URL and returns the expected results """ # Unspecified appli self.mock_request(None) resp = self.api.unsubscribe('http://www.example.com/') Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/notify'), **self._req_kwargs({ 'action': 'revoke', 'callbackurl': 'http://www.example.com/', }) ) self.assertEqual(resp, None) # appli=1 self.mock_request(None) resp = self.api.unsubscribe('http://www.example.com/', appli=1) Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/notify'), **self._req_kwargs({ 'action': 'revoke', 'appli': 1, 'callbackurl': 'http://www.example.com/', }) ) self.assertEqual(resp, None) def test_is_subscribed(self): """ Check that is_subscribed fetches the right URL and returns the expected results """ url = self._req_url('https://wbsapi.withings.net/notify') params = { 'callbackurl': 'http://www.example.com/', 'action': 'get', 'appli': 1 } self.mock_request({'expires': 2147483647, 'comment': 'fake_comment'}) resp = self.api.is_subscribed('http://www.example.com/') Session.request.assert_called_once_with( 'GET', url, **self._req_kwargs(params)) self.assertEquals(resp, True) # Not subscribed self.mock_request(None, status=343) resp = self.api.is_subscribed('http://www.example.com/') Session.request.assert_called_once_with( 'GET', url, **self._req_kwargs(params)) self.assertEquals(resp, False) def test_list_subscriptions(self): """ Check that list_subscriptions fetches the right URL and returns the expected results """ self.mock_request({'profiles': [ {'comment': 'fake_comment', 'expires': 2147483647} ]}) resp = self.api.list_subscriptions() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/notify'), **self._req_kwargs({'action': 'list', 'appli': 1}) ) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 1) self.assertEqual(resp[0]['comment'], 'fake_comment') self.assertEqual(resp[0]['expires'], 2147483647) # No subscriptions self.mock_request({'profiles': []}) resp = self.api.list_subscriptions() Session.request.assert_called_once_with( 'GET', self._req_url('https://wbsapi.withings.net/notify'), **self._req_kwargs({'action': 'list', 'appli': 1}) ) self.assertEqual(type(resp), list) self.assertEqual(len(resp), 0) def mock_request(self, body, status=0): if self.mock_api: json_content = {'status': status} if body is not None: json_content['body'] = body response = MagicMock() response.content = json.dumps(json_content).encode('utf8') Session.request = MagicMock(return_value=response)
class WithingsHealth(SmartPlugin): ALLOW_MULTIINSTANCE = True PLUGIN_VERSION = "1.5.3" ALLOWED_MEASURE_TYPES = [1, 4, 5, 6, 8, 11] def __init__(self, sh, *args, **kwargs): self.logger = logging.getLogger(__name__) self.shtime = Shtime.get_instance() self._user_id = self.get_parameter_value('user_id') self._client_id = self.get_parameter_value('client_id') self._consumer_secret = self.get_parameter_value('consumer_secret') self._cycle = self.get_parameter_value('cycle') self._creds = None self._client = None self._items = {} if not self.init_webinterface(): self._init_complete = False def run(self): self.alive = True self.scheduler_add('poll_data', self._update_loop, cycle=self._cycle) def stop(self): self.alive = False def _store_tokens(self, token): self.logger.debug( "Plugin '{}': Updating tokens to items: access_token - {} token_expiry - {} token_type - {} refresh_token - {}" .format(self.get_fullname(), token['access_token'], token['expires_in'], token['token_type'], token['refresh_token'])) self.get_item('access_token')(token['access_token']) self.get_item('token_expiry')(int( (datetime.datetime.utcnow() - datetime.datetime(1970, 1, 1)).total_seconds()) + int(token['expires_in'])) self.get_item('token_type')(token['token_type']) self.get_item('refresh_token')(token['refresh_token']) def _update_loop(self): """ Starts the update loop for all known items. """ self.logger.debug("Plugin '{}': Starting update loop".format( self.get_fullname())) if not self.alive: return self._update() def _update(self): """ Updates information on diverse items Mappings: ('weight', 1), ('height', 4), ('fat_free_mass', 5), ('fat_ratio', 6), ('fat_mass_weight', 8), ('diastolic_blood_pressure', 9), ('systolic_blood_pressure', 10), ('heart_pulse', 11), ('temperature', 12), ('spo2', 54), ('body_temperature', 71), ('skin_temperature', 72), ('muscle_mass', 76), ('hydration', 77), ('bone_mass', 88), ('pulse_wave_velocity', 91) """ if 'access_token' not in self.get_items( ) or 'token_expiry' not in self.get_items( ) or 'token_type' not in self.get_items( ) or 'refresh_token' not in self.get_items(): self.logger.error( "Plugin '{}': Mandatory Items for OAuth2 Data do not exist. Verify that you have items with withings_type: token_expiry, token_type, refresh_token and access_token in your item tree." .format(self.get_fullname())) return if self._client is None: if self.get_item('access_token')( ) and self.get_item('token_expiry')() > 0 and self.get_item( 'token_type')() and self.get_item('refresh_token')(): if (self.shtime.now() < datetime.datetime.fromtimestamp( self.get_item('token_expiry')(), tz=self.shtime.tzinfo())): self.logger.debug( "Plugin '{}': Token is valid, will expire on {}.". format( self.get_fullname(), datetime.datetime.fromtimestamp( self.get_item('token_expiry')(), tz=self.shtime.tzinfo()).strftime( '%d.%m.%Y %H:%M:%S'))) self.logger.debug( "Plugin '{}': Initializing NokiaCredentials: access_token - {} token_expiry - {} token_type - {} refresh_token - {} user_id - {} client_id - {} consumer_secret - {}" .format(self.get_fullname(), self.get_item('access_token')(), self.get_item('token_expiry')(), self.get_item('token_type')(), self.get_item('refresh_token')(), self._user_id, self._client_id, self._consumer_secret)) self._creds = NokiaCredentials( self.get_item('access_token')(), self.get_item('token_expiry')(), self.get_item('token_type')(), self.get_item('refresh_token')(), self._user_id, self._client_id, self._consumer_secret) self._client = NokiaApi(self._creds, refresh_cb=self._store_tokens) else: self.logger.error( "Plugin '{}': Token is expired, run OAuth2 again from Web Interface (Expiry Date: {})." .format( self.get_fullname(), datetime.datetime.fromtimestamp( self.get_item('token_expiry')(), tz=self.shtime.tzinfo()).strftime( '%d.%m.%Y %H:%M:%S'))) return else: self.logger.error( "Plugin '{}': Items for OAuth2 Data are not set with required values. Please run process via WebGUI of the plugin." .format(self.get_fullname())) return measures = self._client.get_measures() last_measure = measures[0] if last_measure.get_measure( 11) is not None and 'heart_pulse' in self._items: self._items['heart_pulse'](last_measure.get_measure(11)) self.logger.debug("Plugin '{}': heart_pulse - {}".format( self.get_fullname(), last_measure.get_measure(11))) # Bugfix for strange behavior of returning heart_pulse as seperate dataset.. if last_measure.get_measure(1) is None: last_measure = measures[1] if last_measure.get_measure(1) is not None and 'weight' in self._items: self._items['weight'](last_measure.get_measure(1)) self.logger.debug("Plugin '{}': weight - {}".format( self.get_fullname(), last_measure.get_measure(1))) if last_measure.get_measure(4) is not None and 'height' in self._items: self._items['height'](last_measure.get_measure(4)) self.logger.debug("Plugin '{}': height - {}".format( self.get_fullname(), last_measure.get_measure(4))) if last_measure.get_measure( 5) is not None and 'fat_free_mass' in self._items: self._items['fat_free_mass'](last_measure.get_measure(5)) self.logger.debug("Plugin '{}': fat_free_mass - {}".format( self.get_fullname(), last_measure.get_measure(5))) if last_measure.get_measure( 6) is not None and 'fat_ratio' in self._items: self._items['fat_ratio'](last_measure.get_measure(6)) self.logger.debug("Plugin '{}': fat_ratio - {}".format( self.get_fullname(), last_measure.get_measure(6))) if last_measure.get_measure( 8) is not None and 'fat_mass_weight' in self._items: self._items['fat_mass_weight'](last_measure.get_measure(8)) self.logger.debug("Plugin '{}': fat_mass_weight - {}".format( self.get_fullname(), last_measure.get_measure(8))) if last_measure.get_measure( 9) is not None and 'diastolic_blood_pressure' in self._items: self._items['diastolic_blood_pressure']( last_measure.get_measure(9)) self.logger.debug( "Plugin '{}': diastolic_blood_pressure - {}".format( self.get_fullname(), last_measure.get_measure(9))) if last_measure.get_measure( 10) is not None and 'systolic_blood_pressure' in self._items: self._items['systolic_blood_pressure']( last_measure.get_measure(10)) self.logger.debug( "Plugin '{}': systolic_blood_pressure - {}".format( self.get_fullname(), last_measure.get_measure(10))) if last_measure.get_measure( 11) is not None and 'heart_pulse' in self._items: self._items['heart_pulse'](last_measure.get_measure(11)) self.logger.debug("Plugin '{}': heart_pulse - {}".format( self.get_fullname(), last_measure.get_measure(11))) if last_measure.get_measure( 12) is not None and 'temperature' in self._items: self._items['temperature'](last_measure.get_measure(12)) self.logger.debug("Plugin '{}': temperature - {}".format( self.get_fullname(), last_measure.get_measure(12))) if last_measure.get_measure(54) is not None and 'spo2' in self._items: self._items['spo2'](last_measure.get_measure(54)) self.logger.debug("Plugin '{}': spo2 - {}".format( self.get_fullname(), last_measure.get_measure(54))) if last_measure.get_measure( 71) is not None and 'body_temperature' in self._items: self._items['body_temperature'](last_measure.get_measure(71)) self.logger.debug("Plugin '{}': body_temperature - {}".format( self.get_fullname(), last_measure.get_measure(71))) if last_measure.get_measure( 72) is not None and 'skin_temperature' in self._items: self._items['skin_temperature'](last_measure.get_measure(72)) self.logger.debug("Plugin '{}': skin_temperature - {}".format( self.get_fullname(), last_measure.get_measure(72))) if last_measure.get_measure( 76) is not None and 'muscle_mass' in self._items: self._items['muscle_mass'](last_measure.get_measure(76)) self.logger.debug("Plugin '{}': muscle_mass - {}".format( self.get_fullname(), last_measure.get_measure(76))) if last_measure.get_measure( 77) is not None and 'hydration' in self._items: self._items['hydration'](last_measure.get_measure(77)) self.logger.debug("Plugin '{}': hydration - {}".format( self.get_fullname(), last_measure.get_measure(77))) if last_measure.get_measure( 88) is not None and 'bone_mass' in self._items: self._items['bone_mass'](last_measure.get_measure(88)) self.logger.debug("Plugin '{}': bone_mass - {}".format( self.get_fullname(), last_measure.get_measure(88))) if last_measure.get_measure( 91) is not None and 'pulse_wave_velocity' in self._items: self._items['pulse_wave_velocity'](last_measure.get_measure(91)) self.logger.debug("Plugin '{}': pulse_wave_velocity - {}".format( self.get_fullname(), last_measure.get_measure(91))) if 'height' in self._items and ( 'bmi' in self._items or 'bmi_text' in self._items) and last_measure.get_measure(1) is not None: if self._items['height']() > 0: bmi = round( last_measure.get_measure(1) / ((self._items['height']()) * (self._items['height']())), 2) if 'bmi' in self._items: self._items['bmi'](bmi) if 'bmi_text' in self._items: if bmi < 16: self._items['bmi_text']('starkes Untergewicht') elif 16 <= bmi < 17: self._items['bmi_text']('mäßiges Untergewicht ') elif 17 <= bmi < 18.5: self._items['bmi_text']('leichtes Untergewicht ') elif 18.5 <= bmi < 25: self._items['bmi_text']('Normalgewicht') elif 25 <= bmi < 30: self._items['bmi_text']('Präadipositas (Übergewicht)') elif 30 <= bmi < 35: self._items['bmi_text']('Adipositas Grad I') elif 35 <= bmi < 40: self._items['bmi_text']('Adipositas Grad II') elif 40 <= bmi: self._items['bmi_text']('Adipositas Grad III') else: self.logger.error( "Plugin '{}': Cannot calculate BMI: height is 0, please set height (in m) for height item manually." .format(self.get_fullname())) else: self.logger.error( "Plugin '{}': Cannot calculate BMI: height and / or bmi item missing." .format(self.get_fullname())) def parse_item(self, item): """ Default plugin parse_item method. Is called when the plugin is initialized. Selects each item corresponding to the Nokia Health identifier and adds it to an internal array :param item: The item to process. """ # items specific to call monitor if self.get_iattr_value(item.conf, 'withings_type') in [ 'weight', 'height', 'fat_free_mass', 'fat_mass_weight', 'fat_ratio', 'fat_mass_weight', 'diastolic_blood_pressure', 'systolic_blood_pressure', 'heart_pulse', 'temperature', 'spo2', 'body_temperature', 'skin_temperature', 'muscle_mass', 'hydration', 'bone_mass', 'pulse_wave_velocity', 'bmi', 'bmi_text', 'access_token', 'token_expiry', 'token_type', 'refresh_token' ]: self._items[self.get_iattr_value(item.conf, 'withings_type')] = item def get_items(self): return self._items def get_item(self, key): return self._items[key] def init_webinterface(self): """" Initialize the web interface for this plugin This method is only needed if the plugin is implementing a web interface """ try: self.mod_http = Modules.get_instance().get_module( 'http' ) # try/except to handle running in a core version that does not support modules except: self.mod_http = None if self.mod_http == None: self.logger.error( "Plugin '{}': Not initializing the web interface".format( self.get_fullname())) return False # set application configuration for cherrypy webif_dir = self.path_join(self.get_plugin_dir(), 'webif') config = { '/': { 'tools.staticdir.root': webif_dir, }, '/static': { 'tools.staticdir.on': True, 'tools.staticdir.dir': 'static' } } # Register the web interface as a cherrypy app self.mod_http.register_webif(WebInterface(webif_dir, self), self.get_shortname(), config, self.get_classname(), self.get_instance_name(), description='') return True
def _update(self): """ Updates information on diverse items Mappings: ('weight', 1), ('height', 4), ('fat_free_mass', 5), ('fat_ratio', 6), ('fat_mass_weight', 8), ('diastolic_blood_pressure', 9), ('systolic_blood_pressure', 10), ('heart_pulse', 11), ('temperature', 12), ('spo2', 54), ('body_temperature', 71), ('skin_temperature', 72), ('muscle_mass', 76), ('hydration', 77), ('bone_mass', 88), ('pulse_wave_velocity', 91) """ if 'access_token' not in self.get_items( ) or 'token_expiry' not in self.get_items( ) or 'token_type' not in self.get_items( ) or 'refresh_token' not in self.get_items(): self.logger.error( "Plugin '{}': Mandatory Items for OAuth2 Data do not exist. Verify that you have items with withings_type: token_expiry, token_type, refresh_token and access_token in your item tree." .format(self.get_fullname())) return if self._client is None: if self.get_item('access_token')( ) and self.get_item('token_expiry')() > 0 and self.get_item( 'token_type')() and self.get_item('refresh_token')(): if (self.shtime.now() < datetime.datetime.fromtimestamp( self.get_item('token_expiry')(), tz=self.shtime.tzinfo())): self.logger.debug( "Plugin '{}': Token is valid, will expire on {}.". format( self.get_fullname(), datetime.datetime.fromtimestamp( self.get_item('token_expiry')(), tz=self.shtime.tzinfo()).strftime( '%d.%m.%Y %H:%M:%S'))) self.logger.debug( "Plugin '{}': Initializing NokiaCredentials: access_token - {} token_expiry - {} token_type - {} refresh_token - {} user_id - {} client_id - {} consumer_secret - {}" .format(self.get_fullname(), self.get_item('access_token')(), self.get_item('token_expiry')(), self.get_item('token_type')(), self.get_item('refresh_token')(), self._user_id, self._client_id, self._consumer_secret)) self._creds = NokiaCredentials( self.get_item('access_token')(), self.get_item('token_expiry')(), self.get_item('token_type')(), self.get_item('refresh_token')(), self._user_id, self._client_id, self._consumer_secret) self._client = NokiaApi(self._creds, refresh_cb=self._store_tokens) else: self.logger.error( "Plugin '{}': Token is expired, run OAuth2 again from Web Interface (Expiry Date: {})." .format( self.get_fullname(), datetime.datetime.fromtimestamp( self.get_item('token_expiry')(), tz=self.shtime.tzinfo()).strftime( '%d.%m.%Y %H:%M:%S'))) return else: self.logger.error( "Plugin '{}': Items for OAuth2 Data are not set with required values. Please run process via WebGUI of the plugin." .format(self.get_fullname())) return measures = self._client.get_measures() last_measure = measures[0] if last_measure.get_measure( 11) is not None and 'heart_pulse' in self._items: self._items['heart_pulse'](last_measure.get_measure(11)) self.logger.debug("Plugin '{}': heart_pulse - {}".format( self.get_fullname(), last_measure.get_measure(11))) # Bugfix for strange behavior of returning heart_pulse as seperate dataset.. if last_measure.get_measure(1) is None: last_measure = measures[1] if last_measure.get_measure(1) is not None and 'weight' in self._items: self._items['weight'](last_measure.get_measure(1)) self.logger.debug("Plugin '{}': weight - {}".format( self.get_fullname(), last_measure.get_measure(1))) if last_measure.get_measure(4) is not None and 'height' in self._items: self._items['height'](last_measure.get_measure(4)) self.logger.debug("Plugin '{}': height - {}".format( self.get_fullname(), last_measure.get_measure(4))) if last_measure.get_measure( 5) is not None and 'fat_free_mass' in self._items: self._items['fat_free_mass'](last_measure.get_measure(5)) self.logger.debug("Plugin '{}': fat_free_mass - {}".format( self.get_fullname(), last_measure.get_measure(5))) if last_measure.get_measure( 6) is not None and 'fat_ratio' in self._items: self._items['fat_ratio'](last_measure.get_measure(6)) self.logger.debug("Plugin '{}': fat_ratio - {}".format( self.get_fullname(), last_measure.get_measure(6))) if last_measure.get_measure( 8) is not None and 'fat_mass_weight' in self._items: self._items['fat_mass_weight'](last_measure.get_measure(8)) self.logger.debug("Plugin '{}': fat_mass_weight - {}".format( self.get_fullname(), last_measure.get_measure(8))) if last_measure.get_measure( 9) is not None and 'diastolic_blood_pressure' in self._items: self._items['diastolic_blood_pressure']( last_measure.get_measure(9)) self.logger.debug( "Plugin '{}': diastolic_blood_pressure - {}".format( self.get_fullname(), last_measure.get_measure(9))) if last_measure.get_measure( 10) is not None and 'systolic_blood_pressure' in self._items: self._items['systolic_blood_pressure']( last_measure.get_measure(10)) self.logger.debug( "Plugin '{}': systolic_blood_pressure - {}".format( self.get_fullname(), last_measure.get_measure(10))) if last_measure.get_measure( 11) is not None and 'heart_pulse' in self._items: self._items['heart_pulse'](last_measure.get_measure(11)) self.logger.debug("Plugin '{}': heart_pulse - {}".format( self.get_fullname(), last_measure.get_measure(11))) if last_measure.get_measure( 12) is not None and 'temperature' in self._items: self._items['temperature'](last_measure.get_measure(12)) self.logger.debug("Plugin '{}': temperature - {}".format( self.get_fullname(), last_measure.get_measure(12))) if last_measure.get_measure(54) is not None and 'spo2' in self._items: self._items['spo2'](last_measure.get_measure(54)) self.logger.debug("Plugin '{}': spo2 - {}".format( self.get_fullname(), last_measure.get_measure(54))) if last_measure.get_measure( 71) is not None and 'body_temperature' in self._items: self._items['body_temperature'](last_measure.get_measure(71)) self.logger.debug("Plugin '{}': body_temperature - {}".format( self.get_fullname(), last_measure.get_measure(71))) if last_measure.get_measure( 72) is not None and 'skin_temperature' in self._items: self._items['skin_temperature'](last_measure.get_measure(72)) self.logger.debug("Plugin '{}': skin_temperature - {}".format( self.get_fullname(), last_measure.get_measure(72))) if last_measure.get_measure( 76) is not None and 'muscle_mass' in self._items: self._items['muscle_mass'](last_measure.get_measure(76)) self.logger.debug("Plugin '{}': muscle_mass - {}".format( self.get_fullname(), last_measure.get_measure(76))) if last_measure.get_measure( 77) is not None and 'hydration' in self._items: self._items['hydration'](last_measure.get_measure(77)) self.logger.debug("Plugin '{}': hydration - {}".format( self.get_fullname(), last_measure.get_measure(77))) if last_measure.get_measure( 88) is not None and 'bone_mass' in self._items: self._items['bone_mass'](last_measure.get_measure(88)) self.logger.debug("Plugin '{}': bone_mass - {}".format( self.get_fullname(), last_measure.get_measure(88))) if last_measure.get_measure( 91) is not None and 'pulse_wave_velocity' in self._items: self._items['pulse_wave_velocity'](last_measure.get_measure(91)) self.logger.debug("Plugin '{}': pulse_wave_velocity - {}".format( self.get_fullname(), last_measure.get_measure(91))) if 'height' in self._items and ( 'bmi' in self._items or 'bmi_text' in self._items) and last_measure.get_measure(1) is not None: if self._items['height']() > 0: bmi = round( last_measure.get_measure(1) / ((self._items['height']()) * (self._items['height']())), 2) if 'bmi' in self._items: self._items['bmi'](bmi) if 'bmi_text' in self._items: if bmi < 16: self._items['bmi_text']('starkes Untergewicht') elif 16 <= bmi < 17: self._items['bmi_text']('mäßiges Untergewicht ') elif 17 <= bmi < 18.5: self._items['bmi_text']('leichtes Untergewicht ') elif 18.5 <= bmi < 25: self._items['bmi_text']('Normalgewicht') elif 25 <= bmi < 30: self._items['bmi_text']('Präadipositas (Übergewicht)') elif 30 <= bmi < 35: self._items['bmi_text']('Adipositas Grad I') elif 35 <= bmi < 40: self._items['bmi_text']('Adipositas Grad II') elif 40 <= bmi: self._items['bmi_text']('Adipositas Grad III') else: self.logger.error( "Plugin '{}': Cannot calculate BMI: height is 0, please set height (in m) for height item manually." .format(self.get_fullname())) else: self.logger.error( "Plugin '{}': Cannot calculate BMI: height and / or bmi item missing." .format(self.get_fullname()))
def nokia_sync(force=False): config = get_config() n = config['nokia'] creds = NokiaCredentials(n['access_token'], n['token_expiry'], n['token_type'], n['refresh_token'], n['user_id'], n['client_id'], n['consumer_secret']) nokia_client = NokiaApi(creds, refresh_cb=nokia_refresh_cb) measures = nokia_client.get_measures() measure = measures[0] logger.info('Recieved {} measurements'.format(len(measures))) # Now check if we need to update last_update = max([m.date.timestamp for m in measures]) logger.info('Last measurement at {}'.format(last_update)) logger.info('Last update at {}'.format(config['nokia']['last_update'])) if (config['nokia']['last_update'] >= last_update) and not force: logger.info('No new weight updates') return measures msg = '' fit = FitEncoder_Weight() fit.write_file_info() fit.write_file_creator() fit.write_device_info(datetime.timestamp(datetime.now())) for measure in measures: if (config['nokia']['last_update'] < measure.date.timestamp) or force: if measure.weight is not None: bmi = measure.weight / config['nokia']['height']**2 msg += 'New measurement at {} ({})\n'.format( str(measure.date.datetime), measure.date.humanize()) msg += 'New weight = {} kg\n'.format(measure.weight) msg += 'New fat ratio= {} %\n'.format(measure.fat_ratio) msg += 'New hydration = {} %\n'.format(measure.hydration) msg += 'New bone mass = {} kg\n'.format(measure.bone_mass) msg += 'New muscle mass = {} kg\n'.format(measure.muscle_mass) msg += 'Calculated BMI = {} kg.m^-2\n'.format(bmi) for m in msg.splitlines(): logger.info(m) # Sync Garmin logger.info('Syncing weight of {} with GARMIN.'.format( measure.weight)) fit.write_weight_scale(timestamp=measure.date.timestamp, weight=measure.weight, percent_fat=measure.fat_ratio, percent_hydration=measure.hydration, bone_mass=measure.bone_mass, muscle_mass=measure.muscle_mass, bmi=bmi) fit.finish() with GarminClient(config['garmin']['username'], config['garmin']['password']) as client: client.upload_activity(io.BytesIO(fit.getvalue()), 'fit') # Sync Strava measure = measures[0] ts = datetime.timestamp(datetime.now()) ts -= (config['nokia']['weight_int'] * 86400) weight = [m.weight for m in measures if m.date.timestamp >= ts] logger.info("Averaging {} weight measurements".format(len(weight))) weight = mean(weight) if (config['nokia']['last_update'] != measure.date.timestamp) or force: logger.info('Syncing weight of {} with STRAVA.'.format(measure.weight)) strava = Strava(config['strava']) strava_token = strava.connect() config['strava'] = strava_token strava.client.update_athlete(weight=weight) msg += 'Synced weight of {} with Strava\n'.format(measure.weight) config = get_config() config['nokia']['last_update'] = max([m.date.timestamp for m in measures]) write_config(config) send_email('New Weight Sync', msg) return measures