Skip to content
Merged

Ble #13

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e01a43c
feat: script for BLE reading
denjev03 Mar 3, 2026
e8398a0
feat: script for sending IMU data from Arduino
denjev03 Mar 3, 2026
14a27a0
feat: added globale variables
denjev03 Mar 7, 2026
5f36a4f
feat: cleand up file structure
denjev03 Mar 7, 2026
fdaae67
Modified IMU_data_collection.py and added data
HenrikOen Mar 7, 2026
ad9afe4
added measurement data
HenrikOen Mar 7, 2026
77ae299
plotting of IMU-data
GustavNat Mar 7, 2026
0f03f65
Update IMU_data_collection.py
OdinV-ntnu Mar 12, 2026
ccdf4d1
feat: added labels to csv
denjev03 Mar 12, 2026
6a6c755
Merge branch 'main' of https://github.com/CogitoNTNU/GymbroAI into BLE
OdinV-ntnu Mar 12, 2026
7388412
Merge branch 'BLE' of https://github.com/CogitoNTNU/GymbroAI into BLE
OdinV-ntnu Mar 12, 2026
a8567e8
feat: added labels to csv file for new recordings
denjev03 Mar 17, 2026
43e367d
Script for counting repetitions in python. It is to test the dsp and …
GustavNat Mar 17, 2026
933d914
Merge branch 'BLE' of https://github.com/CogitoNTNU/GymbroAI into BLE
GustavNat Mar 17, 2026
55ed2dc
updated version of rep_counter
GustavNat Mar 19, 2026
1a51b8e
Added index file for dashboard
OdinV-ntnu Mar 24, 2026
1c34b47
working version of rep_counter implemented in python and rep_counter …
GustavNat Apr 7, 2026
c8279d2
feat: added some shi
denjev03 Apr 9, 2026
5111096
feat: added counter
denjev03 Apr 9, 2026
8c45c8e
mordi
OdinV-ntnu Apr 9, 2026
3b8514a
feat: added ino file
denjev03 Apr 14, 2026
f406539
feat: added counters
denjev03 Apr 14, 2026
0e29129
feat: updated website
denjev03 Apr 14, 2026
d8ff030
feat: updated website
denjev03 Apr 14, 2026
bfecb0b
feat: clean up crew
denjev03 Apr 16, 2026
d8247e4
feat: readme
denjev03 Apr 21, 2026
ba0fffc
Fikset på nettsiden
OdinV-ntnu Apr 21, 2026
789f11a
Opprettet med Colab
denjev03 Apr 21, 2026
54e4b79
Update Colab link in README
denjev03 Apr 21, 2026
a5fcea2
feat: removed some shi
denjev03 Apr 21, 2026
d691682
fix: file structure
denjev03 Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,10 @@ lerna-debug.log

# System Files
.DS_Store
Thumbs.db
Thumbs.db

# Runtime file, changes every inference session
src/wearable/visualisation/counts.json

# Raw data (optional - remove these lines if you want data tracked)
src/wearable/data_collection/data/*.csv
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"liveServer.settings.port": 5501
}
1,816 changes: 1,816 additions & 0 deletions GymBro_AI.ipynb

Large diffs are not rendered by default.

8 changes: 7 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
license-files = ["LICENSE"]
dependencies = []
dependencies = [
"bleak>=2.1.1",
"matplotlib>=3.10.8",
"pandas>=3.0.1",
"python-dotenv>=1.2.2",
"tensorflow>=2.21.0",
]


[dependency-groups]
Expand Down
1,128 changes: 933 additions & 195 deletions requirements.txt

Large diffs are not rendered by default.

69 changes: 69 additions & 0 deletions src/wearable/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Wearable — Treningsgjenkjenning

Klassifiserer treningsøvelser i sanntid med IMU-data på Arduino Nano 33 BLE Sense. Sender resultat over BLE.

Støttede øvelser: **bicep curl**, **shoulder press**, **rows**

______________________________________________________________________

## Arduino-biblioteker

Installer via Arduino Library Manager:

- `Arduino_BMI270_BMM150`
- `ArduinoBLE`
- `Arduino_TensorFlowLite`

______________________________________________________________________

## Rask start

En ferdig `model.h` ligger i `arduino/`. Vil du bare teste systemet, hopp rett til [steg 3](#steg-3--deploy).

______________________________________________________________________

## Steg 1 — Samle treningsdata

Flash `arduino/imu_stream.ino` til brettet. Det streamer rådata over BLE.

Start innsamling:

```bash
python data_collection/collect_data.py --label bicep_curl
```

Trykk `Ctrl+C` for å stoppe. Gjenta for `shoulder_press` og `rows`. Sikt på 50–100 repetisjoner per øvelse.

______________________________________________________________________

## Steg 2 — Tren modellen

[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://github.com/CogitoNTNU/GymbroAI/blob/BLE/GymBro_AI.ipynb)

Last opp CSV-filene fra `data_collection/data/` når notatboken ber om det. Last ned og legg outputfiler her:

```
models/model.h5
models/model_quantized.tflite
arduino/model.h
```

______________________________________________________________________

## Steg 3 — Deploy

Flash `arduino/imu_deploy.ino`. Åpne Serial Monitor (9600 baud) og sjekk at du ser `Advertising..`. Utfør en repetisjon — du skal se noe slikt:

```
Gesture: bicep_curl (94.2% confidence)
```

______________________________________________________________________

## Steg 4 — Visualisering

```bash
python visualisation/receive_results.py
```

Åpne `visualisation/index.html` i en nettleser for sanntidsvisning.
236 changes: 236 additions & 0 deletions src/wearable/arduino/imu_deploy.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
/*
* Exercise Recognition — Arduino Nano 33 BLE Sense
* ================================================
* Full int8-quantized TFLite model + BLE communication.
*
*/

#include <ArduinoBLE.h>
#include "Arduino_BMI270_BMM150.h"
#include <TensorFlowLite.h>
#include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
#include <tensorflow/lite/micro/micro_log.h>
#include <tensorflow/lite/micro/micro_interpreter.h>
#include <tensorflow/lite/schema/schema_generated.h>

// ─── Include the int8-quantized model header ────────────────────
// This header defines: g_model[], g_model_len, kNumSamples,
// kNumFeatures, kNumClasses, kGestureNames[]
#include "model.h"

// ─── Configuration ──────────────────────────────────────────────
const float accelerationThreshold = 2.5; // G's to trigger capture
const int numSamples = kNumSamples; // 119 (from model header)

int samplesRead = numSamples;

// ─── BLE Setup ──────────────────────────────────────────────────
BLEService classService("db6d5260-ae3e-4421-a65c-73ca64cc7d3a");
BLECharacteristic gestureChar("db6d5260-ae3e-4421-a65c-73ca64cc7d3b",
BLERead | BLENotify, 32);

// ─── TFLite Micro Setup ─────────────────────────────────────────
// Op resolver with 6 ops:
// FullyConnected, Relu, Softmax, Reshape — model layers
// Quantize, Dequantize — added by converter for float32 I/O on int8 model
static tflite::MicroMutableOpResolver<6> tflOpsResolver;

const tflite::Model* tflModel = nullptr;
tflite::MicroInterpreter* tflInterpreter = nullptr;
TfLiteTensor* tflInputTensor = nullptr;
TfLiteTensor* tflOutputTensor = nullptr;

// Tensor arena — 16KB is plenty for this int8 model
// (int8 uses ~4x less working memory than float32)
constexpr int tensorArenaSize = 16 * 1024;
byte tensorArena[tensorArenaSize] __attribute__((aligned(16)));

// Placement-new buffer for interpreter (avoids heap fragmentation)
static uint8_t interpreterBuffer[sizeof(tflite::MicroInterpreter)]
__attribute__((aligned(16)));


void setup() {
Serial.begin(9600);
//while (!Serial);

Serial.println("=== Exercise Recognition ===");
Serial.print("Classes: ");
Serial.println(kNumClasses);
for (int i = 0; i < kNumClasses; i++) {
Serial.print(" ");
Serial.print(i);
Serial.print(": ");
Serial.println(kGestureNames[i]);
}

// ── Initialize IMU ──
if (!IMU.begin()) {
Serial.println("ERROR: IMU init failed!");
while (1);
}
Serial.print("Accel rate: ");
Serial.print(IMU.accelerationSampleRate());
Serial.println(" Hz");

// ── Initialize BLE ──
if (!BLE.begin()) {
Serial.println("ERROR: BLE init failed!");
while (1);
}
BLE.setLocalName("CogitoIMU");
BLE.setAdvertisedService(classService);
classService.addCharacteristic(gestureChar);
BLE.addService(classService);
BLE.advertise();
Serial.println("BLE advertising...");

// ── Register TFLite ops ──
// These 6 ops cover the full int8-quantized model:
tflOpsResolver.AddFullyConnected();
tflOpsResolver.AddRelu();
tflOpsResolver.AddSoftmax();
tflOpsResolver.AddReshape();
tflOpsResolver.AddQuantize();
tflOpsResolver.AddDequantize();

// ── Load model ──
tflModel = tflite::GetModel(g_model);
if (tflModel->version() != TFLITE_SCHEMA_VERSION) {
Serial.print("ERROR: Model schema mismatch! Got ");
Serial.print(tflModel->version());
Serial.print(", expected ");
Serial.println(TFLITE_SCHEMA_VERSION);
while (1);
}

// ── Create interpreter ──
tflInterpreter = new (interpreterBuffer) tflite::MicroInterpreter(
tflModel, tflOpsResolver, tensorArena, tensorArenaSize);

if (tflInterpreter->AllocateTensors() != kTfLiteOk) {
Serial.println("ERROR: AllocateTensors() failed!");
while (1);
}

tflInputTensor = tflInterpreter->input(0);
tflOutputTensor = tflInterpreter->output(0);

// ── Print diagnostics ──
Serial.print("Arena used: ");
Serial.print(tflInterpreter->arena_used_bytes());
Serial.print(" / ");
Serial.print(tensorArenaSize);
Serial.println(" bytes");

Serial.print("Input: type=");
Serial.print(tflInputTensor->type); // 1=float32, 9=int8
Serial.print(", bytes=");
Serial.println(tflInputTensor->bytes);

Serial.print("Output: type=");
Serial.print(tflOutputTensor->type);
Serial.print(", bytes=");
Serial.println(tflOutputTensor->bytes);

Serial.println("Ready — waiting for motion...\n");
}


void loop() {
BLEDevice central = BLE.central();

if (central) {
Serial.print("Connected: ");
Serial.println(central.address());

while (central.connected()) {
float aX, aY, aZ, gX, gY, gZ;

// ── Wait for significant motion ──
while (samplesRead == numSamples) {
BLE.poll(); // Keep BLE alive while waiting
if (IMU.accelerationAvailable()) {
IMU.readAcceleration(aX, aY, aZ);
float aSum = fabs(aX) + fabs(aY) + fabs(aZ);
if (aSum >= accelerationThreshold) {
samplesRead = 0;
break;
}
}
}

// ── Collect samples ──
while (samplesRead < numSamples) {
// NOTE: No BLE.poll() during data collection to avoid
// interrupt-driven corruption of the sample buffer
if (IMU.accelerationAvailable() && IMU.gyroscopeAvailable()) {
IMU.readAcceleration(aX, aY, aZ);
IMU.readGyroscope(gX, gY, gZ);

// Normalize to [0, 1] — same formula as Python training
int idx = samplesRead * 6;
tflInputTensor->data.f[idx + 0] = (aX + 4.0f) / 8.0f;
tflInputTensor->data.f[idx + 1] = (aY + 4.0f) / 8.0f;
tflInputTensor->data.f[idx + 2] = (aZ + 4.0f) / 8.0f;
tflInputTensor->data.f[idx + 3] = (gX + 2000.0f) / 4000.0f;
tflInputTensor->data.f[idx + 4] = (gY + 2000.0f) / 4000.0f;
tflInputTensor->data.f[idx + 5] = (gZ + 2000.0f) / 4000.0f;

samplesRead++;
}
}

// ── Run inference (BLE paused) ──
Serial.println("Invoking...");

// CRITICAL: Do NOT call BLE.poll() between filling input and
// reading output. SoftDevice interrupts can corrupt FPU state
// and tensor arena during inference.
TfLiteStatus invokeStatus = tflInterpreter->Invoke();

if (invokeStatus != kTfLiteOk) {
Serial.println("ERROR: Invoke failed!");
samplesRead = numSamples; // Reset for next attempt
continue;
}

// ── Read results ──
int maxIndex = 0;
float maxValue = tflOutputTensor->data.f[0];

// Check for NaN (safety net)
if (isnan(maxValue)) {
Serial.println("WARNING: NaN detected in output — skipping");
samplesRead = numSamples;
continue;
}

for (int i = 1; i < kNumClasses; i++) {
float val = tflOutputTensor->data.f[i];
if (!isnan(val) && val > maxValue) {
maxValue = val;
maxIndex = i;
}
}

// Print result
Serial.print(">>> ");
Serial.print(kGestureNames[maxIndex]);
Serial.print(" (");
Serial.print(maxValue * 100.0f, 1);
Serial.println("%)");

// ── Send via BLE ──
char buffer[32];
snprintf(buffer, sizeof(buffer), "%s|%.1f",
kGestureNames[maxIndex], maxValue * 100.0f);
gestureChar.writeValue(buffer);

// Resume BLE polling
BLE.poll();
}

Serial.println("Disconnected");
}
}
Loading