From 898c11a67b7aaa1bba988e12355330c51588b04d Mon Sep 17 00:00:00 2001 From: "Sebastian H. Gabrielli" Date: Mon, 18 Nov 2024 18:31:11 +0100 Subject: [PATCH] Add BPM tracking --- src-tauri/Cargo.lock | 7 +++++++ src-tauri/Cargo.toml | 1 + src-tauri/src/main.rs | 35 ++++++++++++++++++++++++++++++----- src/App.vue | 19 +++++++++++++------ src/components/Graph.vue | 13 ++++++++++--- 5 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 79ce484..4e37222 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -863,6 +863,12 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "find_peaks" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ba6fe8df99019a8cb0c712414e10081b3bbdb10d8f7ab95719863ced5db982" + [[package]] name = "flate2" version = "1.0.33" @@ -3524,6 +3530,7 @@ dependencies = [ "anyhow", "chrono", "env_logger", + "find_peaks", "log", "rand 0.8.5", "serde", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 064410f..233e49c 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,6 +23,7 @@ log = "0.4.22" env_logger = "0.11.5" tokio = { version = "1.41.1", features = ["full"] } thiserror = "2.0.3" +find_peaks = "0.1.5" [features] # This feature is used for production builds or when a dev server is not specified, DO NOT REMOVE!! diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5abae96..37f8d5e 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -87,12 +87,12 @@ fn create_data_listener(websockets_uri: String, data: Arc>>) .into_iter() .filter(|data| { Utc::now().signed_duration_since(data.timestamp) - <= Duration::seconds(5) + <= Duration::minutes(1) }) .collect(); ecg_data.push(ECGData { - value: voltage, + value: -voltage, timestamp: Utc::now(), }); @@ -107,9 +107,28 @@ fn create_data_listener(websockets_uri: String, data: Arc>>) }); } +#[tauri::command] +fn calculate_heartrate(data: State<'_, Arc>>>) -> Result { + let data = data.lock().expect("Failed to grab mutex lock").clone(); + + let filtered_data: Vec = data.into_iter().map(|entry| entry.value).collect(); + + let mut peak_finder = find_peaks::PeakFinder::new(&filtered_data); + peak_finder.with_min_prominence(1.); + //peak_finder.with_min_height(0.); + + Ok(peak_finder.find_peaks().len()) +} + #[tauri::command] async fn get_ecg_data(data: State<'_, Arc>>>) -> Result, Error> { - Ok(data.lock().expect("Failed to get lock").clone()) + Ok(data + .lock() + .expect("Failed to get lock") + .clone() + .into_iter() + .filter(|data| Utc::now().signed_duration_since(data.timestamp) <= Duration::seconds(5)) + .collect()) } #[tokio::main] @@ -120,11 +139,17 @@ async fn main() { let ecg_data: Arc>> = Arc::new(Mutex::new(Vec::with_capacity(2 ^ 13))); // Create the websockets listener - create_data_listener("ws://192.168.128.50:81".to_string(), ecg_data.clone()); + //create_data_listener("ws://192.168.128.50:81".to_string(), ecg_data.clone()); + create_data_listener("ws://192.168.66.210:81".to_string(), ecg_data.clone()); tauri::Builder::default() .plugin(tauri_plugin_shell::init()) - .invoke_handler(tauri::generate_handler![greet, random_data, get_ecg_data]) + .invoke_handler(tauri::generate_handler![ + greet, + random_data, + get_ecg_data, + calculate_heartrate + ]) .manage(ecg_data) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src/App.vue b/src/App.vue index 6340cea..d936c0b 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,21 +3,28 @@ // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup import Greet from "./components/Greet.vue"; import Graph from "./components/Graph.vue"; -/* import { invoke } from "@tauri-apps/api/core"; -import { onMounted } from "vue"; +import { onMounted, ref } from "vue"; + +let bpm = ref(0); + +async function refresh_bpm() { + bpm.value = await invoke("calculate_heartrate"); +} onMounted(async () => { - await invoke("create_data_listener", { - websocketsUri: "ws://192.168.128.50:81", - }); + await refresh_bpm(); + + setInterval(() => { + refresh_bpm(); + }, 250); }); -*/ diff --git a/src/components/Graph.vue b/src/components/Graph.vue index 0c0109a..38fc7a3 100644 --- a/src/components/Graph.vue +++ b/src/components/Graph.vue @@ -32,7 +32,7 @@ export default { datasets: [ { // Ensure datasets is an array - label: "My Dataset", // Add a label for the dataset + label: "ECG Measurement", // Add a label for the dataset data: [], // Initial data fill: false, // Set to true if you want the area under the line to be filled borderColor: "blue", // Set the line color @@ -51,9 +51,16 @@ export default { x: { ticks: { callback: (value, index, values) => { + /* // Show only 5 timestamps on the x-axis const step = Math.floor(values.length / 5); return index % step === 0 ? chartData.value.labels[index] : ""; // Show the label only for selected indices + */ + const totalLabels = values.length; + const step = Math.ceil(totalLabels / 5); // Use ceil to ensure we cover all labels + return index % step === 0 && index < totalLabels + ? chartData.value.labels[index] + : ""; // Show the label only for selected indices }, }, }, @@ -75,7 +82,7 @@ export default { labels: timestamps, datasets: [ { - label: "My Dataset", // Add a label for the dataset + label: "ECG Measurement", // Add a label for the dataset data: values, // Access the data property fill: false, // Set to true if you want the area under the line to be filled borderColor: "green", // Set the line color @@ -103,7 +110,7 @@ export default { labels: timestamps, datasets: [ { - label: "My Dataset", + label: "ECG Measurement", data: values, fill: false, borderColor: "green",