Ejemplo n.º 1
0
    def test_too_many_requests(self):
        """
        Tests the 429 response, given in case of exceeding the rate limit
        """
        r = mock.Mock(spec=requests.Response)
        r.content = b"{'normal': 'resource'}"
        r.headers = {'Retry-After': '10'}

        f = Fitbit(**self.client_kwargs)
        f.client._request = lambda *args, **kwargs: r

        r.status_code = 429
        try:
            f.user_profile_get()
            self.assertEqual(True, False)  # Won't run if an exception's raised
        except exceptions.HTTPTooManyRequests:
            e = sys.exc_info()[1]
            self.assertEqual(e.retry_after_secs, 10)
Ejemplo n.º 2
0
    def test_too_many_requests(self):
        """
        Tests the 429 response, given in case of exceeding the rate limit
        """
        r = mock.Mock(spec=requests.Response)
        r.content = b"{'normal': 'resource'}"
        r.headers = {'Retry-After': '10'}

        f = Fitbit(**self.client_kwargs)
        f.client._request = lambda *args, **kwargs: r

        r.status_code = 429
        try:
            f.user_profile_get()
            self.assertEqual(True, False)  # Won't run if an exception's raised
        except exceptions.HTTPTooManyRequests:
            e = sys.exc_info()[1]
            self.assertEqual(e.retry_after_secs, 10)
Ejemplo n.º 3
0
    def get_profile(self, token):
        """ Returns a user profile from a token """
        fb = Fitbit(self.client_id,
                    self.client_secret,
                    access_token=token['access_token'],
                    refresh_token=token['refresh_token'],
                    expires_at=token['expires_at'],
                    refresh_cb=update_token)
        profile = fb.user_profile_get()
        print("got profile")

        return dict(first_name=profile['user']['firstName'],
                    last_name=profile['user']['lastName'],
                    username=token['user_id'],
                    token=token)
Ejemplo n.º 4
0
def fitbit_auth():
    token = None
    error = None

    client_id = '227XNF'
    client_secret = '1a53508ac0bd0aa5ffa3a9f6de07cb9d'
    redirect_uri = ('https://%s/project/default/fitbit_auth' %
                     request.env.http_host)
    code =  request.vars.code

    oauth = FitbitOauth2Client(client_id, client_secret)

    print code
    try:
        token = oauth.fetch_access_token(code, redirect_uri)
    except MissingTokenError:
        error = 'Missing access token parameter.</br>Please check that you are using the correct client_secret'
    except MismatchingStateError:
        error ='CSRF Warning! Mismatching state'
    print token

    client = Fitbit(client_id, client_secret, access_token=token["access_token"], refresh_token=token["refresh_token"])

    f_id = db.fitbit_user_t.insert(
        user_email=auth.user.email,
        fitbit_user_id=token["user_id"],
        access_token=token["access_token"],
        refresh_token=token["refresh_token"],
        expires_at=token["expires_at"],
    )
    user = client.user_profile_get()
    goals = client.activities_daily_goal()
    wt_goal = client.body_weight_goal()
    print wt_goal
    u_id = db.user_t.insert(
        user_email=auth.user.email,
        dob=user["user"]["dateOfBirth"],
        sex=user["user"]["gender"],
        height=user["user"]["height"],
        image=user["user"]["avatar150"],
        steps_target=goals["goals"]["steps"],
        weight_target=wt_goal["goal"]["weight"],
        weight=user["user"]["weight"],
    )


    redirect(URL('index'))
Ejemplo n.º 5
0
def maybe_sync_data_from_fitbit():

    r = db(db.fitbit_user_t.user_email == auth.user.email).select(db.fitbit_user_t.ALL)

    if len(r) == 0:
        return False
    else:
        if time.time() > r[0].expires_at:
            oauth = OAuth2Session(client_id)

            token = oauth.refresh_token(
                        refresh_token_url,
                        refresh_token=r[0].refresh_token,
                        auth=requests.auth.HTTPBasicAuth(client_id, client_secret)
            )

            r = db(db.fitbit_user_t.user_email == auth.user.email).update(access_token = token["access_token"],
                                                                    refresh_token = token["refresh_token"],
                                                                    expires_at = token["expires_at"])
            r = db(db.fitbit_user_t.user_email == auth.user.email).select(db.fitbit_user_t.ALL)

        client = Fitbit(client_id, client_secret, access_token=r[0].access_token,
                        refresh_token=r[0].refresh_token)
        user = client.user_profile_get()

        act = client.activity_stats()
        steps = client.time_series(resource="activities/steps", period="7d")
        wt_goal = client.body_weight_goal()
        day = time.gmtime().tm_wday
        day_list = [0, 1, 2, 3, 4, 5, 6]
        new_day_list = day_list[day+1:] + day_list[:day+1]
        row_dict = {}
        row_dict["user_email"] = auth.user.email
        week_val = 0
        for idx, day in enumerate(new_day_list):
            step_val = int(steps['activities-steps'][idx]['value'])
            row_dict["d" + str(day)] = step_val
            week_val = week_val + step_val
        row_dict["week_total"] = week_val
        row_dict["lifetime"] = act["lifetime"]["total"]["steps"]
        row_dict["last_updated_day"] = day
        u = db.user_t(db.user_t.user_email == auth.user.email).update(weight=user["user"]["weight"],
                                                                       weight_target=wt_goal["goal"]["weight"])
        s = db.steps_t.update_or_insert((db.steps_t.user_email == auth.user.email),
                                        **row_dict)
        return True
Ejemplo n.º 6
0
    def test_response_ok(self):
        """
        This mocks a pretty normal resource, that the request was authenticated,
        and data was returned.  This test should just run and not raise any
        exceptions
        """
        r = mock.Mock(spec=requests.Response)
        r.status_code = 200
        r.content = b'{"normal": "resource"}'

        f = Fitbit(**self.client_kwargs)
        f.client._request = lambda *args, **kwargs: r
        f.user_profile_get()

        r.status_code = 202
        f.user_profile_get()

        r.status_code = 204
        f.user_profile_get()
Ejemplo n.º 7
0
    def test_response_ok(self):
        """
        This mocks a pretty normal resource, that the request was authenticated,
        and data was returned.  This test should just run and not raise any
        exceptions
        """
        r = mock.Mock(spec=requests.Response)
        r.status_code = 200
        r.content = b'{"normal": "resource"}'

        f = Fitbit(**self.client_kwargs)
        f.client._request = lambda *args, **kwargs: r
        f.user_profile_get()

        r.status_code = 202
        f.user_profile_get()

        r.status_code = 204
        f.user_profile_get()
Ejemplo n.º 8
0
def setup_platform(hass, config, add_entities, discovery_info=None):
    """Set up the Fitbit sensor."""
    config_path = hass.config.path(FITBIT_CONFIG_FILE)
    if os.path.isfile(config_path):
        config_file = load_json(config_path)
        if config_file == DEFAULT_CONFIG:
            request_app_setup(
                hass, config, add_entities, config_path, discovery_info=None
            )
            return False
    else:
        save_json(config_path, DEFAULT_CONFIG)
        request_app_setup(hass, config, add_entities, config_path, discovery_info=None)
        return False

    if "fitbit" in _CONFIGURING:
        hass.components.configurator.request_done(_CONFIGURING.pop("fitbit"))

    access_token = config_file.get(ATTR_ACCESS_TOKEN)
    refresh_token = config_file.get(ATTR_REFRESH_TOKEN)
    expires_at = config_file.get(ATTR_LAST_SAVED_AT)
    if None not in (access_token, refresh_token):
        authd_client = Fitbit(
            config_file.get(ATTR_CLIENT_ID),
            config_file.get(ATTR_CLIENT_SECRET),
            access_token=access_token,
            refresh_token=refresh_token,
            expires_at=expires_at,
            refresh_cb=lambda x: None,
        )

        if int(time.time()) - expires_at > 3600:
            authd_client.client.refresh_token()

        unit_system = config.get(CONF_UNIT_SYSTEM)
        if unit_system == "default":
            authd_client.system = authd_client.user_profile_get()["user"]["locale"]
            if authd_client.system != "en_GB":
                if hass.config.units.is_metric:
                    authd_client.system = "metric"
                else:
                    authd_client.system = "en_US"
        else:
            authd_client.system = unit_system

        dev = []
        registered_devs = authd_client.get_devices()
        clock_format = config.get(CONF_CLOCK_FORMAT)
        for resource in config.get(CONF_MONITORED_RESOURCES):

            # monitor battery for all linked FitBit devices
            if resource == "devices/battery":
                for dev_extra in registered_devs:
                    dev.append(
                        FitbitSensor(
                            authd_client,
                            config_path,
                            resource,
                            hass.config.units.is_metric,
                            clock_format,
                            dev_extra,
                        )
                    )
            else:
                dev.append(
                    FitbitSensor(
                        authd_client,
                        config_path,
                        resource,
                        hass.config.units.is_metric,
                        clock_format,
                    )
                )
        add_entities(dev, True)

    else:
        oauth = FitbitOauth2Client(
            config_file.get(ATTR_CLIENT_ID), config_file.get(ATTR_CLIENT_SECRET)
        )

        redirect_uri = f"{hass.config.api.base_url}{FITBIT_AUTH_CALLBACK_PATH}"

        fitbit_auth_start_url, _ = oauth.authorize_token_url(
            redirect_uri=redirect_uri,
            scope=[
                "activity",
                "heartrate",
                "nutrition",
                "profile",
                "settings",
                "sleep",
                "weight",
            ],
        )

        hass.http.register_redirect(FITBIT_AUTH_START, fitbit_auth_start_url)
        hass.http.register_view(FitbitAuthCallbackView(config, add_entities, oauth))

        request_oauth_completion(hass)
Ejemplo n.º 9
0
def setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Set up the Fitbit sensor."""
    config_path = hass.config.path(FITBIT_CONFIG_FILE)
    if os.path.isfile(config_path):
        config_file: ConfigType = cast(ConfigType, load_json(config_path))
        if config_file == DEFAULT_CONFIG:
            request_app_setup(hass,
                              config,
                              add_entities,
                              config_path,
                              discovery_info=None)
            return
    else:
        save_json(config_path, DEFAULT_CONFIG)
        request_app_setup(hass,
                          config,
                          add_entities,
                          config_path,
                          discovery_info=None)
        return

    if "fitbit" in _CONFIGURING:
        configurator.request_done(hass, _CONFIGURING.pop("fitbit"))

    access_token: str | None = config_file.get(ATTR_ACCESS_TOKEN)
    refresh_token: str | None = config_file.get(ATTR_REFRESH_TOKEN)
    expires_at: int | None = config_file.get(ATTR_LAST_SAVED_AT)
    if (access_token is not None and refresh_token is not None
            and expires_at is not None):
        authd_client = Fitbit(
            config_file.get(CONF_CLIENT_ID),
            config_file.get(CONF_CLIENT_SECRET),
            access_token=access_token,
            refresh_token=refresh_token,
            expires_at=expires_at,
            refresh_cb=lambda x: None,
        )

        if int(time.time()) - expires_at > 3600:
            authd_client.client.refresh_token()

        if (unit_system := config[CONF_UNIT_SYSTEM]) == "default":
            authd_client.system = authd_client.user_profile_get(
            )["user"]["locale"]
            if authd_client.system != "en_GB":
                if hass.config.units.is_metric:
                    authd_client.system = "metric"
                else:
                    authd_client.system = "en_US"
        else:
            authd_client.system = unit_system

        registered_devs = authd_client.get_devices()
        clock_format = config[CONF_CLOCK_FORMAT]
        monitored_resources = config[CONF_MONITORED_RESOURCES]
        entities = [
            FitbitSensor(
                authd_client,
                config_path,
                description,
                hass.config.units.is_metric,
                clock_format,
            ) for description in FITBIT_RESOURCES_LIST
            if description.key in monitored_resources
        ]
        if "devices/battery" in monitored_resources:
            entities.extend([
                FitbitSensor(
                    authd_client,
                    config_path,
                    FITBIT_RESOURCE_BATTERY,
                    hass.config.units.is_metric,
                    clock_format,
                    dev_extra,
                ) for dev_extra in registered_devs
            ])
        add_entities(entities, True)
Ejemplo n.º 10
0
def run_api_poller():
    cfg_path = try_getenv('CONFIG_PATH')
    db_host = try_getenv('DB_HOST')
    db_port = try_getenv('DB_PORT')
    db_user = try_getenv('DB_USER')
    db_password = try_getenv('DB_PASSWORD')
    db_name = try_getenv('DB_NAME')
    redirect_url = try_getenv('CALLBACK_URL')
    units = try_getenv('UNITS', 'it_IT')

    # These are required vars, that we first  try to load from file
    client_id = try_load_var(cfg_path, 'client_id')
    client_secret = try_load_var(cfg_path, 'client_secret')
    access_token = try_load_var(cfg_path, 'access_token')
    refresh_token = try_load_var(cfg_path, 'refresh_token')
    expires_at = try_load_var(cfg_path, 'expires_at')

    # If any of the required vars is not in file, try to read from env
    # If read, save
    if not client_id:
        client_id = try_getenv('CLIENT_ID')
        save_var(cfg_path, 'client_id', client_id)
    if not client_secret:
        client_secret = try_getenv('CLIENT_SECRET')
        save_var(cfg_path, 'client_secret', client_secret)
    if not access_token:
        access_token = try_getenv('ACCESS_TOKEN')
        save_var(cfg_path, 'access_token', access_token)
    if not refresh_token:
        refresh_token = try_getenv('REFRESH_TOKEN')
        save_var(cfg_path, 'refresh_token', refresh_token)
    if not expires_at:
        expires_at = try_cast_to_int(try_getenv('EXPIRES_AT'))
        save_var(cfg_path, 'expires_at', str(expires_at))

    logger.debug(
        "client_id: %s, client_secret: %s, access_token: %s, refresh_token: %s, expires_at: %s",
        client_id, client_secret, access_token, refresh_token, expires_at)

    if not client_id:
        logging.critical("client_id missing, aborting!")
        sys.exit(1)
    if not client_secret:
        logging.critical("client_secret missing, aborting!")
        sys.exit(1)
    if not access_token:
        logging.critical("access_token missing, aborting!")
        sys.exit(1)
    if not refresh_token:
        logging.critical("refresh_token missing, aborting!")
        sys.exit(1)

    api_client = Fitbit(client_id=client_id,
                        client_secret=client_secret,
                        access_token=access_token,
                        refresh_token=refresh_token,
                        redirect_uri=redirect_url,
                        refresh_cb=partial(write_updated_credentials,
                                           cfg_path),
                        system=Fitbit.METRIC)

    user_profile = None
    while True:
        try:
            user_profile = api_client.user_profile_get()
            break
        except Timeout as ex:
            logger.warning('Request timed out, retrying in 15 seconds...')
            time.sleep(15)
        except HTTPServerError as ex:
            logger.warning(
                'Server returned exception (5xx), retrying in 15 seconds (%s)',
                ex)
            time.sleep(15)
        except HTTPTooManyRequests as ex:
            # 150 API calls done, and python-fitbit doesn't provide the retry-after header, so stop trying
            # and allow the limit to reset, even if it costs us one hour
            logger.info('API limit reached, sleeping for 3610 seconds!')
            time.sleep(3610)
        except Exception as ex:
            logger.exception('Got some unexpected exception')
            raise

    member_since = user_profile.get('user', {}).get('memberSince',
                                                    '1970-01-01')
    member_since_dt = parse(member_since, ignoretz=True)
    member_since_ts = parse(member_since, ignoretz=True).timestamp()
    logger.info('User is member since: %s (ts: %s)', member_since,
                member_since_ts)

    cur_day = datetime.utcnow()

    db_client = InfluxDBClient(db_host, db_port, db_user, db_password, db_name)
    for one_db in db_client.get_list_database():
        if one_db['name'] == db_name:
            break
    else:
        db_client.create_database(db_name)
    db_client.close()

    # First try to fill any gaps: between User_member_since and first_ts,
    # and then between last_ts and cur_day
    while True:
        for meas, series_list in BASE_SERIES.items():
            for series in series_list:
                resource = '{}/{}'.format(meas, series)
                if '_' in meas:
                    resource = resource.replace('_', '/', 1)
                if resource == 'sleep/sleep':
                    # Sleep is special, is its own main category
                    resource = 'sleep'

                db_client = InfluxDBClient(db_host, db_port, db_user,
                                           db_password, db_name)

                key_series = series
                if isinstance(series_list, dict) and series_list.get(series):
                    # Datapoints are retrieved with all keys in the same dict, so makes no sense to retrieve individual
                    # series names. Use one series as the key series.
                    key_series = series_list[series]['key_series']

                first_ts = get_first_timestamp_for_measurement(db_client,
                                                               meas,
                                                               key_series,
                                                               min_ts=cur_day)
                last_ts = get_last_timestamp_for_measurement(db_client,
                                                             meas,
                                                             key_series,
                                                             min_ts=cur_day)
                profile_to_first = int(
                    (first_ts - member_since_dt) / timedelta(days=1))
                last_to_current = int((cur_day - last_ts) / timedelta(days=1))
                logger.debug(
                    'key_series: %s, first_ts: %s, last_ts: %s, profile_to_first: %s, last_to_current: %s',
                    key_series, first_ts, last_ts, profile_to_first,
                    last_to_current)
                db_client.close()

                intervals_to_fetch = []
                if profile_to_first > 1:
                    append_between_day_series(intervals_to_fetch,
                                              member_since_dt, first_ts)
                if last_to_current > 1:
                    append_between_day_series(intervals_to_fetch, last_ts,
                                              cur_day)
                if not intervals_to_fetch:
                    logger.info(
                        'No gaps to fetch for %s, %s: fetching last day only',
                        meas, series)
                    intervals_to_fetch.append((
                        cur_day,
                        cur_day,
                    ))

                # DB can't be open here, because fitbit_fetch_datapoints can hang for a long time
                if meas == 'sleep':
                    api_client.API_VERSION = '1.2'
                datapoints = fitbit_fetch_datapoints(api_client, meas, series,
                                                     resource,
                                                     intervals_to_fetch)
                if meas == 'sleep':
                    api_client.API_ENDPOINT = '1'
                converted_dps = []
                for one_d in datapoints:
                    if not one_d:
                        continue
                    if isinstance(series_list,
                                  dict) and series_list.get(series):
                        new_dps = series_list[series]['transform'](one_d)
                        for one_dd in new_dps:
                            converted_dps.append(
                                create_api_datapoint_meas_series(
                                    one_dd['meas'], one_dd['series'],
                                    one_dd['value'], one_dd['dateTime']))
                    else:
                        converted_dps.append(
                            create_api_datapoint_meas_series(
                                meas, series, one_d.get('value'),
                                one_d.get('dateTime')))

                db_client = InfluxDBClient(db_host, db_port, db_user,
                                           db_password, db_name)
                precision = 'h'
                if meas == 'sleep':
                    precision = 's'
                logger.debug(
                    'Going to write %s points, key_series: %s, first_ts: %s, last_ts: %s, profile_to_first: %s, last_to_current: %s',
                    len(converted_dps), key_series, first_ts, last_ts,
                    profile_to_first, last_to_current)
                logger.debug('First 3: %s', converted_dps[:3])
                logger.debug('Last 3: %s', converted_dps[-3:])
                if not db_client.write_points(converted_dps,
                                              time_precision=precision,
                                              batch_size=2500):
                    logger.critical(
                        'key_series: %s, first_ts: %s, last_ts: %s, profile_to_first: %s, last_to_current: %s',
                        key_series, first_ts, last_ts, profile_to_first,
                        last_to_current)
                    raise Exception('Unable to write points!')
                db_client.close()
        logger.info('All series processed, sleeping for 4h')
        time.sleep(3610 * 4)

    sys.exit(0)
Ejemplo n.º 11
0
def index():
	if 'fitbit_access_token' not in session:
		return redirect(url_for('login'))
	client = Fitbit(CONSUMER_KEY, CONSUMER_SECRET, resource_owner_key=session['fitbit_access_token']['oauth_token'], resource_owner_secret=session['fitbit_access_token']['oauth_token_secret'])
	return str(client.user_profile_get())