diff --git a/app.py b/app.py
index 4f7f965..9da9ca1 100644
--- a/app.py
+++ b/app.py
@@ -68,6 +68,15 @@ class CadenceReading(db.Model):
power = db.Column(db.Integer, nullable=False)
+class HeartRateReading(db.Model):
+ __tablename__ = 'heartrate_readings'
+ id = db.Column(db.Integer, primary_key=True)
+ workout_id = db.Column(db.Integer, db.ForeignKey(
+ 'workouts.id', ondelete='CASCADE'), nullable=False)
+ created_at = db.Column(db.DateTime, nullable=False)
+ bpm = db.Column(db.Integer, nullable=False)
+
+
@app.route('/', methods=['GET'])
def get_workouts():
return render_users_and_workouts()
@@ -113,21 +122,31 @@ def workouts(user_id):
elif request.method == 'POST':
user = User.query.get(user_id)
+ app.logger.info(f'Creating workout for user {user.name} ({user.id})')
data = request.json
- data = data['workout']
+ workout = data['workout'] or []
+ heart_rate = data['heart_rate'] or []
# create a new workout
workout = Workout(user_id=user_id, bike_id=user.bike_id)
db.session.add(workout)
db.session.commit()
+ app.logger.info(
+ f'Workout({workout.id}) created for user {user.name} ({user.id}) with {len(workout)} cadence readings and {len(heart_rate)} heart rate readings')
+
# add cadence readings to the workout
- for d in data:
+ for w in workout:
cadence_reading = CadenceReading(
- workout_id=workout.id, created_at=d['timestamp'], rpm=d['rpm'], distance=d['distance'], speed=d['speed'], calories=d['calories'], power=d['power'])
+ workout_id=workout.id, created_at=w['timestamp'], rpm=w['rpm'], distance=w['distance'], speed=w['speed'], calories=w['calories'], power=w['power'])
db.session.add(cadence_reading)
+ for h in heart_rate:
+ heart_rate_reading = HeartRateReading(
+ workout_id=workout.id, created_at=h['timestamp'], bpm=h['bpm'])
+ db.session.add(heart_rate_reading)
+
db.session.commit()
return jsonify({'message': 'Workout created successfully.'}), 201
diff --git a/database/001_create_tables.sql b/database/001_create_tables.sql
index 1510077..4518881 100644
--- a/database/001_create_tables.sql
+++ b/database/001_create_tables.sql
@@ -25,6 +25,14 @@ CREATE TABLE
CONSTRAINT unique_cadence_reading_per_workout_time UNIQUE (workout_id, created_at)
);
+CREATE TABLE
+ heartrate_readings (
+ id SERIAL PRIMARY KEY,
+ workout_id INTEGER NOT NULL REFERENCES workouts (id) ON DELETE CASCADE,
+ created_at TIMESTAMP NOT NULL,
+ bpm INTEGER NOT NULL
+ );
+
CREATE TABLE
bikes (
id SERIAL PRIMARY KEY,
diff --git a/templates/new_workout.html b/templates/new_workout.html
index cc1ccb1..b034e4b 100644
--- a/templates/new_workout.html
+++ b/templates/new_workout.html
@@ -29,7 +29,19 @@
@@ -115,6 +127,8 @@
}
});
+
+
const integerNumber = (num) => parseInt(num);
const decimalNumber = (num) => parseFloat(num.toFixed(1));
@@ -167,7 +181,10 @@
isRunning = false;
clearInterval(intervalId);
toggleButton.textContent = 'Start';
+ // Disconnect from cadence sensor
disconnect();
+ // Disconnect from heart rate sensor
+ disconnectHeartRateMonitor();
fetch("{{ url_for('workouts', user_id=user.id) }}", {
method: "POST",
@@ -175,7 +192,7 @@
Accept: "application/json",
"Content-Type": "application/json",
},
- body: JSON.stringify({ workout: workout }),
+ body: JSON.stringify({ workout: workout, heart_rate: heartRateData }),
}).then(res => res.json())
.then(res => {
workout = [];
@@ -283,10 +300,10 @@
if (characteristic) {
try {
await characteristic.stopNotifications();
- log("> Notifications stopped");
+ console.log("> Notifications stopped");
characteristic.removeEventListener(
"characteristicvaluechanged",
- handleNotifications
+ parseCSC
);
} catch (error) {
console.log("Argh! " + error);
@@ -391,6 +408,94 @@
return (energy * 0.238902957619) / 1000;
}
+ // Heart Rate monitor code
+ const heartRateButton = document.getElementById('heart_rate_button');
+
+ let isHearRateMonitorActive = false;
+ let heartRateCharateristic = null;
+ let heartRateData = [];
+
+ heartRateButton.addEventListener('click', async () => {
+ console.log('heartRateButton clicked, isHearRateMonitorActive: ', isHearRateMonitorActive);
+ if (isHearRateMonitorActive) {
+ console.log('Stopping heart rate monitor...');
+ await disconnectHeartRateMonitor();
+
+ isHearRateMonitorActive = false;
+ heartRateButton.classList.remove('text-red-800');
+ heartRateButton.classList.add('text-gray-700');
+ } else {
+ console.log('Starting heart rate monitor...');
+ await heartRateMonitorConnect();
+
+ isHearRateMonitorActive = true;
+ heartRateButton.classList.remove('text-gray-700');
+ heartRateButton.classList.add('text-red-800');
+ }
+ });
+
+ function parseHeartRate(value) {
+ const flags = value.getUint8(0);
+ const rate16Bits = flags & 0x1;
+ let heartRate;
+
+ if (rate16Bits) {
+ heartRate = value.getUint16(1, /*littleEndian=*/ true);
+ } else {
+ heartRate = value.getUint8(1);
+ }
+
+ return heartRate;
+ }
+
+
+ function heartRateChange(event) {
+ const currentHeartRate = parseHeartRate(event.target.value);
+ heartRateData = [...heartRateData, { timestamp: new Date(), heartRate: currentHeartRate }];
+ console.log('currentHeartRate:', currentHeartRate);
+ }
+
+ function heartRateMonitorConnect() {
+ return navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] })
+ .then(device => {
+ return device.gatt.connect();
+ })
+ .then(server => {
+ return server.getPrimaryService('heart_rate')
+ })
+ .then(service => {
+ return service.getCharacteristic('heart_rate_measurement')
+ })
+ .then(character => {
+ heartRateCharateristic = character;
+ return heartRateCharateristic.startNotifications().then(_ => {
+ heartRateCharateristic.addEventListener('characteristicvaluechanged', heartRateChange);
+ })
+ .catch(e => console.error(e));
+ })
+ };
+
+ async function disconnectHeartRateMonitor() {
+ if (heartRateCharateristic) {
+ try {
+ await heartRateCharateristic.stopNotifications();
+ console.log("> Notifications stopped");
+ heartRateCharateristic.removeEventListener(
+ "characteristicvaluechanged", heartRateChange);
+
+ // Disconnect from GATT server
+ const device = heartRateCharateristic.service.device;
+ if (device.gatt.connected) {
+ device.gatt.disconnect();
+ console.log('Disconnected from heart rate monitor');
+ }
+ } catch (error) {
+ console.log("Argh! " + error);
+ swal("Oops", error, "error");
+ }
+ }
+ }
+