Skip to content

Commit

Permalink
Merge pull request #105 from RADAR-base/fitbit-hrv
Browse files Browse the repository at this point in the history
Added Fitbit Intraday HRV route
  • Loading branch information
Bdegraaf1234 authored Nov 6, 2023
2 parents 0300a6a + 8e43417 commit 78da8e3
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 1 deletion.
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/Versions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ object Versions {
const val okhttp = "4.11.0"

const val firebaseAdmin = "9.1.0"
const val radarSchemas = "0.8.4"
const val radarSchemas = "0.8.6-SNAPSHOT"
const val ktor = "2.3.5"

const val junit = "5.9.3"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ public class FitbitRestSourceConnectorConfig extends RestSourceConnectorConfig {
private static final String FITBIT_INTRADAY_HEART_RATE_TOPIC_DISPLAY = "Intraday heartrate topic";
private static final String FITBIT_INTRADAY_HEART_RATE_TOPIC_DEFAULT = "connect_fitbit_intraday_heart_rate";

private static final String FITBIT_INTRADAY_HEART_RATE_VARIABILITY_TOPIC_CONFIG = "fitbit.intraday.heart.rate.variability.topic";
private static final String FITBIT_INTRADAY_HEART_RATE_VARIABILITY_TOPIC_DOC = "Topic for Fitbit intraday intraday_heart_rate_variability";
private static final String FITBIT_INTRADAY_HEART_RATE_VARIABILITY_TOPIC_DISPLAY = "Intraday heart rate variability topic";
private static final String FITBIT_INTRADAY_HEART_RATE_VARIABILITY_TOPIC_DEFAULT = "connect_fitbit_intraday_heart_rate_variability";

private static final String FITBIT_RESTING_HEART_RATE_TOPIC_CONFIG = "fitbit.resting.heart.rate.topic";
private static final String FITBIT_RESTING_HEART_RATE_TOPIC_DOC = "Topic for Fitbit resting heart_rate";
private static final String FITBIT_RESTING_HEART_RATE_TOPIC_DISPLAY = "Resting heartrate topic";
Expand Down Expand Up @@ -458,6 +463,10 @@ public String getFitbitIntradayHeartRateTopic() {
return getString(FITBIT_INTRADAY_HEART_RATE_TOPIC_CONFIG);
}

public String getFitbitIntradayHeartRateVariabilityTopic() {
return getString(FITBIT_INTRADAY_HEART_RATE_VARIABILITY_TOPIC_CONFIG);
}

public String getFitbitRestingHeartRateTopic() {
return getString(FITBIT_RESTING_HEART_RATE_TOPIC_CONFIG);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2018 The Hyve
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.radarbase.connect.rest.fitbit.converter;

import com.fasterxml.jackson.databind.JsonNode;
import io.confluent.connect.avro.AvroData;
import org.radarbase.connect.rest.RestSourceConnectorConfig;
import org.radarbase.connect.rest.fitbit.FitbitRestSourceConnectorConfig;
import org.radarbase.connect.rest.fitbit.request.FitbitRestRequest;
import org.radarcns.connector.fitbit.FitbitIntradayHeartRateVariability;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.stream.Stream;

import static org.radarbase.connect.rest.util.ThrowingFunction.tryOrNull;

public class FitbitIntradayHeartRateVariabilityAvroConverter extends FitbitAvroConverter {
private static final Logger logger = LoggerFactory.getLogger(FitbitIntradayHeartRateVariabilityAvroConverter.class);
private String heartRateVariabilityTopic;

public FitbitIntradayHeartRateVariabilityAvroConverter(AvroData avroData) {
super(avroData);
}

@Override
public void initialize(RestSourceConnectorConfig config) {
heartRateVariabilityTopic = ((FitbitRestSourceConnectorConfig) config).getFitbitIntradayHeartRateVariabilityTopic();
logger.info("Using intraday heart rate variability topic {}", heartRateVariabilityTopic);
}

@Override
protected Stream<TopicData> processRecords(FitbitRestRequest request, JsonNode root, double timeReceived) {
JsonNode hrv = root.get("hrv");
if (hrv == null || !hrv.isArray()) {
logger.warn("No HRV is provided for {}: {}", request, root);
return Stream.empty();
}
ZonedDateTime startDate = request.getDateRange().end();

return iterableToStream(hrv)
.filter(m -> m != null && m.isObject())
.map(m -> m.get("minutes"))
.filter(minutes -> minutes != null && minutes.isArray())
.flatMap(FitbitAvroConverter::iterableToStream)
.map(tryOrNull(minuteData -> parseHrv(minuteData, startDate, timeReceived),
(a, ex) -> logger.warn("Failed to convert heart rate variability from request {}, {}", request, a, ex)));
}

private TopicData parseHrv(JsonNode minuteData, ZonedDateTime startDate, double timeReceived) {
Instant time = startDate.with(LocalDateTime.parse(minuteData.get("minute").asText())).toInstant();
JsonNode value = minuteData.get("value");
if (value == null || !value.isObject()) {
return null;
}
FitbitIntradayHeartRateVariability fitbitHrv = new FitbitIntradayHeartRateVariability(time.toEpochMilli() / 1000d,
timeReceived,
(float) value.get("dailyRmssd").asDouble(),
(float) value.get("coverage").asDouble(),
(float) value.get("hf").asDouble(),
(float) value.get("lf").asDouble());
return new TopicData(time, heartRateVariabilityTopic, fitbitHrv);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import org.radarbase.connect.rest.fitbit.route.FitbitActivityLogRoute;
import org.radarbase.connect.rest.fitbit.route.FitbitIntradayCaloriesRoute;
import org.radarbase.connect.rest.fitbit.route.FitbitIntradayHeartRateRoute;
import org.radarbase.connect.rest.fitbit.route.FitbitIntradayHeartRateVariabilityRoute;
import org.radarbase.connect.rest.fitbit.route.FitbitIntradayStepsRoute;
import org.radarbase.connect.rest.fitbit.route.FitbitRestingHeartRateRoute;
import org.radarbase.connect.rest.fitbit.route.FitbitSleepRoute;
Expand Down Expand Up @@ -94,6 +95,7 @@ private List<RequestRoute> getRoutes(FitbitRestSourceConnectorConfig config) {
if (config.hasIntradayAccess()) {
localRoutes.add(new FitbitIntradayStepsRoute(this, userRepository, avroData));
localRoutes.add(new FitbitIntradayHeartRateRoute(this, userRepository, avroData));
localRoutes.add(new FitbitIntradayHeartRateVariabilityRoute(this, userRepository, avroData));
localRoutes.add(new FitbitIntradayCaloriesRoute(this, userRepository, avroData));
}
return localRoutes;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2018 The Hyve
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package org.radarbase.connect.rest.fitbit.route;

import io.confluent.connect.avro.AvroData;
import org.radarbase.connect.rest.fitbit.converter.FitbitIntradayHeartRateAvroConverter;
import org.radarbase.connect.rest.fitbit.converter.FitbitIntradayHeartRateVariabilityAvroConverter;
import org.radarbase.connect.rest.fitbit.request.FitbitRequestGenerator;
import org.radarbase.connect.rest.fitbit.request.FitbitRestRequest;
import org.radarbase.connect.rest.fitbit.user.User;
import org.radarbase.connect.rest.fitbit.user.UserRepository;
import org.radarbase.connect.rest.fitbit.util.DateRange;

import java.time.ZonedDateTime;
import java.util.stream.Stream;

import static java.time.ZoneOffset.UTC;
import static java.time.temporal.ChronoUnit.SECONDS;

public class FitbitIntradayHeartRateVariabilityRoute extends FitbitPollingRoute {
private final FitbitIntradayHeartRateVariabilityAvroConverter converter;

public FitbitIntradayHeartRateVariabilityRoute(FitbitRequestGenerator generator,
UserRepository userRepository, AvroData avroData) {
super(generator, userRepository, "intraday_heart_rate_variability");
this.converter = new FitbitIntradayHeartRateVariabilityAvroConverter(avroData);
}

@Override
protected String getUrlFormat(String baseUrl) {
return baseUrl + "/1/user/%s/hrv/date/%s/%s/all.json";
}

protected Stream<FitbitRestRequest> createRequests(User user) {
ZonedDateTime startDate = this.getOffset(user).plus(ONE_SECOND)
.atZone(UTC)
.truncatedTo(SECONDS);
ZonedDateTime now = ZonedDateTime.now(UTC);
return Stream.of(newRequest(user, new DateRange(startDate, now),
user.getExternalUserId(), DATE_FORMAT.format(startDate), DATE_FORMAT.format(now)));
}

@Override
public FitbitIntradayHeartRateVariabilityAvroConverter converter() {
return converter;
}
}

0 comments on commit 78da8e3

Please sign in to comment.