Wearable Activity Tracker

I developed a full-stack IoT system using motion sensors, cloud services, and PWA to classify user activities in real time.

ExpertFull instructions provided5 days101
Wearable Activity Tracker

Things used in this project

Hardware components

RT-Thread RT-Spark Dev Board (STM32F407ZGT6)
×1
USB Cable, USB Type C Plug
USB Cable, USB Type C Plug
×1

Software apps and online services

Mango Cloud
RT-Thread Studio
VS Code
Microsoft VS Code
MQTT
MQTT

Story

Read more

Schematics

STM32 Schematic Diagram

Full viewing of STM32 Schematic Diagram, which you can locate the pins of sensors used.

Code

Progressive Web App (PWA)

HTML
This is the HTML frontend for a browser-based activity tracker PWA that displays real-time movement data, variance, and a chart of activity levels. It works together with the app.js script that fetches or simulates sensor data and updates the UI dynamically.
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Activity PWA</title>
    <link rel="manifest" href="manifest.json">
    <style>
        /* Paste the same CSS from the previous dashboard.html here */
        :root { --bg: #0f172a; --card: #1e293b; --text: #f8fafc; --muted: #94a3b8; --idle: #22c55e; --walk: #f59e0b; --run: #ef4444; }
        body { font-family: system-ui, sans-serif; background: var(--bg); color: var(--text); margin: 0; padding: 20px; display: flex; justify-content: center; }
        .container { width: 100%; max-width: 600px; display: grid; gap: 20px; }
        .status-card { background: var(--card); border-radius: 20px; padding: 40px; text-align: center; border: 2px solid transparent; transition: 0.3s; }
        .icon-circle { width: 120px; height: 120px; border-radius: 50%; margin: 0 auto 20px; display: flex; align-items: center; justify-content: center; font-size: 3rem; border: 6px solid var(--muted); }
        h1 { margin: 0; font-size: 2.5rem; }
        .metrics-row { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; }
        .metric-box { background: var(--card); padding: 20px; border-radius: 12px; }
        .value { font-size: 1.5rem; font-weight: bold; }
        .chart-box { background: var(--card); padding: 15px; border-radius: 12px; height: 150px; }
        canvas { width: 100%; height: 100%; }
        /* Themes */
        .theme-IDLE .status-card { border-color: var(--idle); } .theme-IDLE .icon-circle { border-color: var(--idle); color: var(--idle); }
        .theme-WALKING .status-card { border-color: var(--walk); } .theme-WALKING .icon-circle { border-color: var(--walk); color: var(--walk); }
    </style>
</head>
<body>
    <div class="container" id="main-wrapper">
        <div class="status-card">
            <div class="icon-circle"><span id="icon">🛑</span></div>
            <h1 id="activity-text">WAITING</h1>
            <p id="timestamp" style="color:#94a3b8">Timestamp: --</p>
        </div>
        <div class="metrics-row">
            <div class="metric-box">
                <div style="color:#94a3b8; font-size:0.8rem">VARIANCE</div>
                <div class="value" id="variance">0.0000</div>
            </div>
            <div class="metric-box">
                <div style="color:#94a3b8; font-size:0.8rem">BUFFER</div>
                <div class="value" id="buffer">0 / 5</div>
            </div>
        </div>
        <div class="chart-box"><canvas id="chart"></canvas></div>
    </div>

    <script>
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('./sw.js');
        }
    </script>
    <script src="app.js"></script>
</body>
</html>

Progressive Web App (PWA)

JavaScript
This is a browser-based JavaScript activity tracker that reads accelerometer-like data, calculates movement variance, and classifies activity as IDLE, WALKING, or RUNNING. It fetches live or simulated data every second, updates a simple UI, and draws a variance chart in real time.
// --- CONFIGURATION ---
const CONFIG = {
    SENSITIVITY: 16384.0,
    THRESH_WALK: 0.03,
    THRESH_RUN: 0.30,
    WINDOW_SIZE: 5,
    API_URL: "/raw-data" // We now fetch RAW data, not decisions change this into "http://localhost:5000/raw-data" when on device
};

// --- STATE MANAGEMENT ---
let magnitudeBuffer = []; // Replaces Python's deque
let currentActivity = "WAITING";
let varianceHistory = new Array(50).fill(0);
let lastProcessedId = null;

// --- MATH HELPERS (Replacing Numpy) ---
function calculateStandardDeviation(array) {
    if (array.length === 0) return 0;
    const n = array.length;
    const mean = array.reduce((a, b) => a + b) / n;
    const variance = array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n;
    return Math.sqrt(variance);
}

function calculateMagnitude(x, y, z) {
    // Normalization logic from Python
    const gx = x / CONFIG.SENSITIVITY;
    const gy = y / CONFIG.SENSITIVITY;
    const gz = z / CONFIG.SENSITIVITY;
    return Math.sqrt(gx * gx + gy * gy + gz * gz);
}

// --- MAIN LOOP (Replaces Python 'while True') ---
async function processLoop() {
    try {
        // 1. Fetch RAW Data (You need a tiny endpoint that just returns the latest MongoDB doc)
        // For testing without backend, we can simulate raw data here.
        const response = await fetch(CONFIG.API_URL); 
        const doc = await response.json();

        // 2. Check for New Data
        if (doc._id !== lastProcessedId) {
            lastProcessedId = doc._id;

            // 3. Process Data (The Python Logic)
            const mag = calculateMagnitude(doc.accel.x, doc.accel.y, doc.accel.z);
            magnitudeBuffer.push(mag);

            // Update UI with real-time buffering status
            updateUI(mag, doc.timestamp);

            // 4. Decision Window Logic
            if (magnitudeBuffer.length >= CONFIG.WINDOW_SIZE) {
                // Calculate Variance
                const variance = calculateStandardDeviation(magnitudeBuffer);
                
                // Determine Activity
                if (variance > CONFIG.THRESH_RUN) currentActivity = "RUNNING";
                else if (variance > CONFIG.THRESH_WALK) currentActivity = "WALKING";
                else currentActivity = "IDLE";

                console.log(Decision: ${currentActivity} (Var: ${variance.toFixed(4)}));
                
                // Clear Buffer (Non-overlapping window)
                magnitudeBuffer = []; 
                
                // Save for chart
                updateChart(variance);
            }
        }
    } catch (error) {
        console.log("Waiting for data stream...", error);
    }
}

// --- UI UPDATES ---
const ui = {
    text: document.getElementById('activity-text'),
    var: document.getElementById('variance'),
    buf: document.getElementById('buffer'),
    time: document.getElementById('timestamp'),
    wrapper: document.getElementById('main-wrapper'),
    icon: document.getElementById('icon')
};

function updateUI(currentMag, timestamp) {
    // Calculate interim variance for display
    const currentVar = calculateStandardDeviation(magnitudeBuffer);
    
    ui.text.innerText = currentActivity;
    ui.var.innerText = currentVar.toFixed(4);
    ui.buf.innerText = ${magnitudeBuffer.length} / ${CONFIG.WINDOW_SIZE};
    
    // Format Timestamp
    const date = new Date(parseInt(timestamp));
    ui.time.innerText = date.toLocaleTimeString();

    // Theme Logic
    ui.wrapper.className = theme-${currentActivity};
    if (currentActivity === "WALKING") ui.icon.innerText = "🚶";
    else if (currentActivity === "RUNNING") ui.icon.innerText = "🏃";
    else ui.icon.innerText = "🛑";
}

// --- CHARTING ---
const ctx = document.getElementById('chart').getContext('2d');
function updateChart(newVariance) {
    varianceHistory.push(newVariance);
    varianceHistory.shift();
    
    const w = ctx.canvas.width = ctx.canvas.parentElement.clientWidth;
    const h = ctx.canvas.height = ctx.canvas.parentElement.clientHeight;
    
    ctx.clearRect(0, 0, w, h);
    ctx.beginPath();
    ctx.strokeStyle = '#38bdf8';
    ctx.lineWidth = 2;
    
    const step = w / varianceHistory.length;
    const max = 0.5;

    varianceHistory.forEach((val, i) => {
        const x = i * step;
        const y = h - (Math.min(val, max) / max) * h;
        if(i===0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
    });
    ctx.stroke();
}

// --- INITIALIZATION ---
// Start the loop every 1 second (1000ms)
setInterval(processLoop, 1000);

// Simulation Mode (If no backend available)
let simStep = 0;
function runSimulation() {
    simStep++;
    // Fake raw data stream
    const rawData = {
        id: "sim" + simStep,
        timestamp: Date.now(),
        accel: { 
            // Simulate random movement or stillness
            x: (Math.sin(simStep/10) * 5000) + Math.random()*500, 
            y: 500, 
            z: 16384 
        }
    };
    
    // Inject into the processing loop manually for demo
    lastProcessedId = rawData._id;
    const mag = calculateMagnitude(rawData.accel.x, rawData.accel.y, rawData.accel.z);
    magnitudeBuffer.push(mag);
    updateUI(mag, rawData.timestamp);
    
    if (magnitudeBuffer.length >= CONFIG.WINDOW_SIZE) {
        const variance = calculateStandardDeviation(magnitudeBuffer);
        if (variance > CONFIG.THRESH_WALK) currentActivity = "WALKING";
        else currentActivity = "IDLE";
        magnitudeBuffer = [];
        updateChart(variance);
    }
}
// Uncomment this to test PWA without any backend:
// setInterval(runSimulation, 1000);

RT-Thread RTOS

C/C++
This is a C program for an embedded IoT device running RT-Thread RTOS. It connects to Wi-Fi, reads data from an ICM20608 IMU sensor over I2C, and publishes messages via MQTT to a remote broker in real time. Essentially, it’s a sensor-to-cloud IoT application for motion tracking or monitoring, designed to run on microcontrollers with RT-Thread.
/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2023-5-10      ShiHao       first version
 */

#include <rtthread.h>
#include <rtdevice.h>
#include <board.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <arpa/inet.h>
#include <netdev.h>
#include "paho_mqtt.h"
#include <wlan_mgnt.h>
#include "icm20608.h"

#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

/* Used to confirm the flashed firmware matches this build */
#define FW_BUILD_TAG "PING_IP_20251219B"

/**
 * MQTT URI farmat:
 * domain mode
 * tcp://broker.emqx.io:1883
 *
 * ipv4 mode
 * tcp://192.168.10.1:1883
 * ssl://192.168.10.1:1884
 *
 * ipv6 mode
 * tcp://[fe80::20c:29ff:fe9a:a07e]:1883
 * ssl://[fe80::20c:29ff:fe9a:a07e]:1884
 */
#define MQTT_URI "tcp://mde3bba0.ala.dedicated.aws.emqxcloud.com:1883"
#define MQTT_USERNAME "spark1"
#define MQTT_PASSWORD "spark1"
#define MQTT_SUBTOPIC "/mqtt/sensor/"
#define MQTT_PUBTOPIC "/mqtt/sensor/"

#define WIFI_SSID     "POCO"
#define WIFI_PASSWORD "123456789"

#define ICM20608_I2C_BUS_NAME "i2c2"
#define ICM20608_PUB_PERIOD_MS 1000

#define ICM_THREAD_STACK_SIZE 2048
#define ICM_THREAD_PRIORITY   20
#define ICM_THREAD_TICK       10

/* define MQTT client context */
static MQTTClient client;
static void mq_start(void);
static void mq_publish(const char *send_str);
static void icm20608_entry(void *parameter);
static void start_icm20608_thread(void);

char sup_pub_topic[48] = {0};

/* 连接成功的回调函数 */
static void wlan_connect_handler(int event, struct rt_wlan_buff *buff, void *parameter)
{
    rt_kprintf("%s\n", _FUNCTION_);
    if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
    {
        rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
    }
}

/* 连接失败的回调函数 */
static void wlan_connect_fail_handler(int event, struct rt_wlan_buff *buff, void *parameter)
{
    rt_kprintf("%s\n", _FUNCTION_);
    if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info)))
    {
        rt_kprintf("ssid : %s \n", ((struct rt_wlan_info *)buff->data)->ssid.val);
    }
}

int main(void)
{
    LOG_I("FW build: %s", FW_BUILD_TAG);
    start_icm20608_thread();

    /* 注册 wlan 回调函数 */
    rt_wlan_register_event_handler(RT_WLAN_EVT_READY, (void (*)(int, struct rt_wlan_buff *, void *))mq_start, RT_NULL);
    /* 初始化自动连接功能 */
    rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION);
    rt_wlan_config_autoreconnect(RT_TRUE);
    if (rt_wlan_connect(WIFI_SSID, WIFI_PASSWORD) != RT_EOK)
    {
        LOG_W("wifi connect request failed (ssid=%s)", WIFI_SSID);
    }
    /*  */
    rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED, wlan_connect_handler, RT_NULL);
    rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED_FAIL, wlan_connect_fail_handler, RT_NULL);

    return 0;
}

static void mqtt_sub_callback(MQTTClient *c, MessageData *msg_data)
{
    *((char *)msg_data->message->payload + msg_data->message->payloadlen) = '\0';
    LOG_D("Topic: %.*s receive a message: %.*s",
          msg_data->topicName->lenstring.len,
          msg_data->topicName->lenstring.data,
          msg_data->message->payloadlen,
          (char *)msg_data->message->payload);

    return;
}

static void mqtt_sub_default_callback(MQTTClient *c, MessageData *msg_data)
{
    *((char *)msg_data->message->payload + msg_data->message->payloadlen) = '\0';
    LOG_D("mqtt sub default callback: %.*s %.*s",
          msg_data->topicName->lenstring.len,
          msg_data->topicName->lenstring.data,
          msg_data->message->payloadlen,
          (char *)msg_data->message->payload);
    return;
}

static void mqtt_connect_callback(MQTTClient *c)
{
    LOG_I("Start to connect mqtt server");
}

static void mqtt_online_callback(MQTTClient *c)
{
    LOG_D("Connect mqtt server success");
    LOG_D("Publish message: Hello,RT-Thread! to topic: %s", sup_pub_topic);
    mq_publish("Hello,RT-Thread!");
}

static void mqtt_offline_callback(MQTTClient *c)
{
    LOG_I("Disconnect from mqtt server");
}

/* Set system time for TLS certificate validation */
static void set_system_time(void)
{
    /* Set time to December 19, 2025, 12:00:00 UTC */
    /* This is needed because soft RTC starts at epoch (1970) */
    /* and TLS certificate validation will fail without correct time */
    time_t now;
    struct tm tm_now = {0};
    
    tm_now.tm_year = 2025 - 1900;  /* Years since 1900 */
    tm_now.tm_mon = 12 - 1;         /* Month 0-11 */
    tm_now.tm_mday = 29;
    tm_now.tm_hour = 12;
    tm_now.tm_min = 0;
    tm_now.tm_sec = 0;
    
    now = mktime(&tm_now);
    if (now != (time_t)-1)
    {
        /* Use RT-Thread's RTC device to set time */
        rt_device_t rtc_dev = rt_device_find("rtc");
        if (rtc_dev != RT_NULL)
        {
            rt_device_control(rtc_dev, RT_DEVICE_C

Credits

JUNIE JEBREEL BOTAWAN
2 projects • 1 follower
PRINCESS LAURON
4 projects • 2 followers
HEART LAHOYLAHOY
4 projects • 1 follower
JARMAINE MESBAHODDIN ABDUL
3 projects • 2 followers
Paul Rodolf Castor
1 project • 1 follower
Paul Rodolf P. Castor
12 projects • 6 followers

Comments