Beispiel #1
0
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()
Beispiel #2
0
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)
Beispiel #3
0
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
Beispiel #4
0
 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)
Beispiel #5
0
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 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
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)
Beispiel #8
0
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)
Beispiel #9
0
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
Beispiel #10
0
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
Beispiel #11
0
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