Arduino + MATLAB Sleep Tracker
A real-time sleep quality monitoring system that uses an Arduino Nano to collect physiological and environmental data during sleep, streams it over serial to MATLAB, and produces live dashboards, a scored sleep report, and personalised recommendations.
Table of Contents
- Project Overview
- Hardware
- Arduino Firmware
- MATLAB Application
- Live Dashboards
- End-of-Session Reports
- Scoring Algorithm — Deep Dive
- Snore Detection & Analysis
- Setup & Usage
- Tuning the Thresholds
- File Structure
Project Overview
The system monitors five dimensions of sleep quality simultaneously:
| Dimension | Sensor Used | What It Measures |
|---|---|---|
| Movement | MMA8452Q accelerometer | Body restlessness during sleep |
| Snoring frequency | Analogue sound sensor | How often snoring events occur |
| Snoring regularity | Sound sensor (derived) | Consistency of snoring rhythm (CoV) |
| Sweating | DHT11 humidity sensor | Elevated ambient humidity from perspiration |
| Temperature comfort | DHT11 temperature sensor | Whether the room is within the ideal sleep temperature range |
Data is sampled at approximately 10 Hz (100 ms Arduino loop delay), streamed as CSV over USB serial at 9600 baud, ingested by MATLAB in real time, scored, and visualised across three live figure windows. When the session ends, MATLAB saves a PNG report, a .mat data file, a .csv data file, and a plain-text summary — then opens a final summary dashboard.
Hardware
Arduino Nano
The Arduino Nano acts as the central data acquisition unit. It reads all three sensors on every loop iteration and prints a comma-separated line to the serial port. It is powered via USB, which also carries the serial data stream to the host PC running MATLAB.
DHT11 — Humidity & Temperature Sensor
What it measures: Relative humidity (0–100 %) and ambient temperature (0–50 °C) with ±5 % and ±2 °C accuracy respectively.
Why both channels matter for sleep:
- Humidity is a proxy for sweating. When a person sweats during sleep, the local air humidity around the body rises. The system flags humidity readings above
SWEAT_LEVEL(default 60 %) as a sweating event and penalises the sweat score accordingly. - Temperature affects sleep stage progression. Research shows that a skin temperature of approximately 30 °C (corresponding to a room temperature of roughly 20–40 °C depending on bedding) is optimal for deep sleep. The system penalises the temperature score when the reading falls outside
TEMP_IDEAL = [20 40].
Sampling rate: The DHT11 is a slow sensor — it can only be read reliably every 2 seconds. The firmware enforces this with a millis() interval (dhtInterval = 2000 ms). Between reads, the previously cached values are re-transmitted so that the CSV stream remains continuous at 10 Hz.
Connection: Data pin → Arduino digital pin 6 (defined as DHT11PIN). Requires the dht11 Arduino library.
MMA8452Q — 3-Axis Accelerometer
What it measures: Linear acceleration along X, Y, and Z axes as raw 12-bit signed integers (range ±2 g in default configuration), communicated over I²C.
Why it matters for sleep: Body movement during sleep is one of the strongest indicators of sleep depth. Frequent, large movements indicate light sleep or waking; sustained stillness indicates deep sleep.
I²C address: 0x1C (the ADDR pin is pulled low). The firmware puts the device into active mode by writing 0x01 to CTRL_REG1 (register 0x2A).
Reading procedure: The firmware requests 6 bytes starting at register 0x01 (OUT_X_MSB). Each axis is reconstructed as a 16-bit signed integer by combining the MSB and LSB bytes.
Connection: SDA → Arduino A4, SCL → Arduino A5 (standard Nano I²C pins). Powered at 3.3 V.
Sound Sensor
What it measures: Ambient sound level as a raw 10-bit ADC value (0–1023) on analogue pin A1. Most common breakout boards use an electret microphone with a simple amplifier and a variable gain potentiometer.
Why it matters: Snoring produces characteristic bursts of loud sound. The system uses a configurable threshold (SNORE_THRESHOLD, default 700 out of 1023) to detect snore events, tracks their timing, and analyses the interval pattern to assess breathing regularity.
Connection: Analogue output → Arduino A1 (SOUND_SENSOR_PIN). Powered at 5 V.
Arduino Firmware
Dependencies
dht11.h— DHT11 sensor library (install via Arduino Library Manager or manually)Wire.h— Built-in I²C library (standard with Arduino IDE)
Pin Assignments
| Pin | Purpose |
|---|---|
| D6 | DHT11 data |
| A1 | Sound sensor analogue output |
| A4 (SDA) | MMA8452Q I²C data |
| A5 (SCL) | MMA8452Q I²C clock |
Initialisation
In setup(), the firmware:
- Opens serial at 9600 baud.
- Starts the I²C bus with
Wire.begin(). - Sets A1 as input.
- Wakes the MMA8452Q from standby by writing active mode to
CTRL_REG1. - Prints the CSV header line
Hum,Temp,X,Y,Z,Sound— MATLAB detects this string to confirm the connection is live.
Main Loop & Serial Protocol
Every 100 ms (delay(100) at the end of loop()), the firmware:
- Checks if 2 seconds have elapsed since the last DHT11 read; if so, it reads fresh humidity and temperature, otherwise it reuses cached values.
- Reads the sound sensor with
analogRead(SOUND_SENSOR_PIN). - Reads 6 bytes from the MMA8452Q and reconstructs X, Y, Z as 16-bit integers.
- Prints a comma-separated line:
<humidity>,<temperature>,<X>,<Y>,<Z>,<sound>
followed by a newline (\n). MATLAB uses this newline as the terminator character.
MATLAB Application
The MATLAB script (sleep_monitor_dashboard.m) is the brain of the system. It handles serial I/O, signal processing, scoring, visualisation, and report generation.
Configuration Parameters
All user-tunable parameters sit at the top of the script for easy adjustment:
| Parameter | Default | Description |
|---|---|---|
COM_PORT |
'COM12' |
Serial port the Arduino is connected to |
BAUD_RATE |
9600 |
Must match the Arduino firmware |
PREALLOC |
10000 |
Number of samples to pre-allocate in memory |
MOVE_THRESHOLD |
1500 |
IMU deviation (EMA-filtered) above this = movement event |
SNORE_THRESHOLD |
700 |
Sound ADC value (0–1023) above this = snore burst |
SNORE_GAP |
15 |
Minimum samples between consecutive snore detections (debounce) |
SWEAT_LEVEL |
60 |
Humidity % above this = sweating penalty |
TEMP_IDEAL |
[20 40] |
°C range considered comfortable for sleep |
IREG_WIN |
10 |
Number of recent snore intervals used for regularity calculation |
WINDOW_SEC |
30 |
Rolling time window (seconds) for snore interval analysis |
Serial Connection & Handshake
MATLAB opens the serial port with serialport(), sets the terminator to LF (line feed, matching Arduino's println), and enters a 30-attempt handshake loop that reads lines until it finds one containing the string 'Hum' — the CSV header sent by the Arduino on startup. A pause(2) before the loop gives the Arduino time to reset after the serial port opens.
Data Buffers
All sensor channels and computed metrics are stored in pre-allocated nan-filled row vectors of length PREALLOC. If the session runs longer than expected and idx exceeds the pre-allocated size, each buffer is extended by another PREALLOC samples using MATLAB's dynamic array growth. Storing nan rather than zero ensures that averaging and plotting functions correctly ignore uninitialised elements.
The buffers are:
| Buffer | Content |
|---|---|
t_buf |
Sample index (x-axis for all plots) |
hum_buf |
Raw humidity readings |
temp_buf |
Raw temperature readings |
ax_buf, ay_buf, az_buf |
Raw accelerometer axes |
snd_buf |
Raw sound ADC readings |
mag_buf |
Computed IMU deviation from EMA baseline |
stage_buf |
Estimated sleep stage (0 = Deep, 1 = Light, 2 = Awake) |
total_buf |
Overall sleep score (0–100) per sample |
ms_buf |
Movement sub-score |
ss_buf |
Snore frequency sub-score |
rs_buf |
Snore regularity sub-score |
ws_buf |
Sweat sub-score |
ts_buf |
Temperature sub-score |
Signal Processing
IMU Deviation (Movement Detection)
Raw 3-axis accelerometer values are first combined into a scalar magnitude:
mag = sqrt(X² + Y² + Z²)
A slow exponential moving average (EMA) tracks the baseline resting magnitude using a 0.98/0.02 weighting:
imu_baseline = 0.98 × imu_baseline + 0.02 × mag
The deviation from baseline is then:
deviation = |mag − imu_baseline|
This approach filters out the constant gravity component and slow positional drift, responding only to sudden changes caused by movement. Any deviation above MOVE_THRESHOLD increments the cumulative move_count.
Rolling Window Analysis
All sub-scores are computed over a rolling window of the most recent 100 samples (win = max(1, idx-99):idx), making the live score reflect current sleep state rather than the entire session average.
Sleep Stage Estimation
A simple three-level stage classifier runs on every sample:
| Condition | Stage | Label |
|---|---|---|
deviation > 2 × MOVE_THRESHOLD |
2 | Awake |
deviation > MOVE_THRESHOLD OR sound > SNORE_THRESHOLD |
1 | Light sleep |
| Otherwise | 0 | Deep sleep |
This is displayed as a staircase plot in Figure 2 and stored in stage_buf.
Scoring Engine
Five sub-scores (each 0–100) are computed each sample and blended into a weighted total:
Total = round(0.30×MS + 0.20×SS + 0.20×RS + 0.15×WS + 0.15×TS)
| Score | Weight | Formula |
|---|---|---|
| Movement Score (MS) | 30% | max(0, 100 × (1 − 2.5 × move_rate)) |
| Snore Frequency Score (SS) | 20% | max(0, 100 × (1 − 3.0 × snore_rate)) |
| Snore Regularity Score (RS) | 20% | max(0, 100 × (1 − 2 × CoV)) |
| Sweat Score (WS) | 15% | max(0, 100 × (1 − sweat_penalty)) |
| Temperature Score (TS) | 15% | max(0, 100 × (1 − temp_penalty)) |
Where:
move_rate= fraction of the last 100 samples with movement above thresholdsnore_rate= fraction of the last 100 samples with sound above thresholdCoV= coefficient of variation of snore intervals (standard deviation / mean) over the lastIREG_WINintervals in the 30-second rolling windowsweat_penalty = max(0, (humidity − SWEAT_LEVEL) / (100 − SWEAT_LEVEL))temp_penalty = max(0, (temp − TEMP_IDEAL(2)) / 10) + max(0, (TEMP_IDEAL(1) − temp) / 10)
Snore Detection & Analysis
Snore detection uses a combined threshold and debounce approach:
- A snore burst is registered when
sound > SNORE_THRESHOLDAND at leastSNORE_GAPsamples (15 by default) have passed since the last detection. This prevents a single prolonged snore from being counted multiple times. - Each snore event records both its sample index (
snore_times) and wall-clock time (snore_wall_times). - At each sample, events older than
WINDOW_SECseconds are pruned from both arrays, keeping only the most recent 30-second window. - The inter-burst intervals are computed as
diff(snore_times). - The Coefficient of Variation (CoV) of the most recent
IREG_WINintervals is computed asσ/μ. A CoV of 0 means perfectly regular snoring; a high CoV means chaotic, irregular breathing. - The Regularity Score maps CoV to 0–100:
max(0, 100 × (1 − 2 × CoV)), reaching zero at CoV ≥ 0.5.
The snore interval pattern is visualised in real time in Figure 2, with a mean line and ±1 SD dotted bounds drawn dynamically.
Live Dashboards
Figure 1 — Raw Sensor Feed
A 3×2 tiled layout showing the raw streaming data for all six channels:
| Tile | Channel | Colour |
|---|---|---|
| Top-left | Humidity (%) | Blue |
| Top-right | Temperature (°C) | Orange |
| Mid-left | Accelerometer X | Green |
| Mid-right | Accelerometer Y | Yellow |
| Bottom-left | Accelerometer Z | Purple |
| Bottom-right | Sound (ADC) | Red |
Each panel auto-scales its x-axis to the current sample count. This figure is primarily a diagnostic view to confirm the sensors are producing sensible values.
Figure 2 — Sleep Analytics
A 2×2 tiled layout of derived signals:
- Movement Activity (top-left): The EMA-filtered IMU deviation with a dashed yellow threshold line. Spikes above the line represent detected movements.
- Snore Interval Pattern (top-right): A rolling scatter/line plot of snore inter-burst intervals within the last 30 seconds, with a yellow mean line and orange dotted ±1 SD bounds. The CoV and regularity score are printed in the top-left corner of the panel.
- Sweat Level (bottom-left): Raw humidity with a dashed red sweating threshold line.
- Sleep Stage Estimate (bottom-right): A staircase plot with y-axis labels Deep (0), Light (1), Awake (2).
Figure 3 — Live Sleep Score Gauge
A dedicated full-screen window showing:
- A semicircular gauge with red/amber/green colour bands (0–33/33–67/67–100) and a white needle pointing to the current total score. The score number is displayed in large text at the centre.
- A quality label (
POOR / FAIR / GREAT) below the gauge. - Cumulative movement and snore burst counts in the bottom corners.
- A horizontal bar chart at the bottom showing all five sub-scores side by side with colour-coded bars.
- A prominent red STOP button to end the session gracefully.
The gauge and bar chart are redrawn every 5 samples (mod(idx,5) == 0) to balance responsiveness with performance.
End-of-Session Reports
When the main loop exits (Stop button, figure closed, or Ctrl+C), MATLAB calls saveSleepReport() followed by runSleepSummary().
Sleep Session Report Figure
An 8-panel tiled figure (4×2) showing the full session history:
- Panels 1–6: Time-series of all five sub-scores plus the overall score. Each panel includes the raw noisy signal (faint) and a 50-sample moving average (bright), plus dashed 75 and 50 threshold lines.
- Panel 7: Raw sound signal with snore burst markers as red scatter dots.
- Panel 8: Full-session snore interval pattern with mean line, ±1 SD bounds, and the full-session CoV annotated.
This figure is rendered with Visible','off' (invisible) so it can be saved to PNG without appearing on screen, then closed.
Sleep Summary Dashboard
A rich analytics dashboard showing:
- Header bar with title and generation timestamp.
- Four stat pills: average score, volatility (standard deviation of score), first-half vs second-half trend, and total sample count.
- Panel A — Score Gauge: A re-rendered semicircular gauge with the session-average score, colour-coded verdict (EXCELLENT / GOOD / FAIR / POOR / VERY POOR), and three quality pills showing what percentage of the session was Great (≥75), Fair (50–74), and Poor (<50).
- Panel B — Score Over Time: A smoothed overall score time-series with shaded green/amber/red quality bands and a trend annotation.
- Panel C — Sub-Score Breakdown: A horizontal bar chart of the five session-average sub-scores, with a gold star marking the best and an orange triangle marking the weakest.
- Panel D — Sparklines: Five mini time-series (one per sub-score) with colour-coded background (green/amber/red) based on whether the score passed 75 or 50.
- Panel E — Recommendations: Up to four personalised tips generated by the scoring logic, each with a numbered icon. Tips are only generated for dimensions that scored below 60.
Saved Files
All output files use a SleepReport_YYYY-MM-DD_HH-MM-SS base name:
| File | Contents |
|---|---|
SleepReport_<timestamp>.png |
Sleep Session Report figure (150 dpi) |
SleepReport_<timestamp>.mat |
MATLAB workspace data (all score buffers, snore data) |
SleepReport_<timestamp>.csv |
Tabular data: Sample, TotalScore, MovementScore, SnoreFreqScore, SnoreRegScore, SweatScore, TempScore |
SleepReport_<timestamp>_Dashboard.png |
Sleep Summary Dashboard figure (150 dpi) |
SleepReport_<timestamp>_Summary.txt |
Plain-text verdict, sub-scores, and tips |
Scoring Algorithm — Deep Dive
Movement Score
move_rate = (samples with deviation > MOVE_THRESHOLD) / 100
MS = max(0, 100 × (1 − 2.5 × move_rate))
A move_rate of 40% (40 out of 100 recent samples with movement) drives the score to zero. The 2.5 multiplier means the score reaches zero at 40% movement density.
Snore Frequency Score
snore_rate = (samples with sound > SNORE_THRESHOLD) / 100
SS = max(0, 100 × (1 − 3.0 × snore_rate))
The 3.0 multiplier is more aggressive — the score hits zero at ~33% snoring density, reflecting that sustained loud snoring is more disruptive than the equivalent amount of movement.
Snore Regularity Score
CoV = std(recent_intervals) / mean(recent_intervals)
RS = max(0, 100 × (1 − 2 × CoV))
If fewer than 3 intervals are available, RS defaults to 100 (no penalty for insufficient data). The regularity score captures the difference between rhythmic, periodic snoring (likely normal) and erratic, arrhythmic snoring (potentially indicative of obstructive sleep apnoea).
Sweat Score
sweat_penalty = max(0, (humidity − 60) / 40)
WS = max(0, 100 × (1 − sweat_penalty))
Humidity at exactly 60% incurs no penalty; at 100% the penalty is 1.0 (score = 0).
Temperature Score
temp_penalty = max(0, (temp − 40) / 10) + max(0, (20 − temp) / 10)
TS = max(0, 100 × (1 − temp_penalty))
Temperatures between 20 °C and 40 °C incur no penalty. Outside this range, the penalty rises by 10% per degree of deviation, reaching zero at 10 °C above or below the boundary.
Setup & Usage
Hardware wiring summary:
DHT11 → D6 (data), 5V, GND
MMA8452Q → A4 (SDA), A5 (SCL), 3.3V, GND
Sound sensor→ A1 (AOUT), 5V, GND
Step-by-step:
- Wire the three sensors to the Arduino Nano as above.
- Install the
dht11library in the Arduino IDE. - Open the Arduino
.inofile, verifyDHT11PIN,MMA8452_ADDRESS, andSOUND_SENSOR_PINmatch your wiring, and upload to the Nano. - Open a Serial Monitor briefly to confirm lines like
45.00,22.00,1024,-512,16384,230are appearing, then close it. - In MATLAB, set
COM_PORTat the top of the script to match the port shown in Device Manager (Windows) or/dev/tty...(Mac/Linux). - Run the script. Three full-screen figure windows will open.
- Place the sensor assembly near (not on) the sleeping person.
- When the sleep session ends, press the STOP button in Figure 3. Reports are saved automatically to the MATLAB working directory.
Tuning the Thresholds
| If you see this problem | Adjust this |
|---|---|
| Too many false movement detections (e.g. from vibrations) | Increase MOVE_THRESHOLD |
| Genuine movements not detected | Decrease MOVE_THRESHOLD |
| Snores not being detected (quiet sleeper) | Decrease SNORE_THRESHOLD; also increase mic gain via the potentiometer on the sensor board |
| Ambient noise triggering false snore events | Increase SNORE_THRESHOLD |
| Same snore being counted multiple times | Increase SNORE_GAP |
| Sleep in a hot environment triggering sweat penalty unfairly | Increase SWEAT_LEVEL |
| Temperature score always low | Widen TEMP_IDEAL, e.g. [15 45] |
File Structure
project/
│
├── sleep_monitor_dashboard.m ← MATLAB acquisition, analysis & reporting
├── sleep_tracker.ino ← Arduino firmware
│
└── (generated at runtime)
├── SleepReport_<timestamp>.png
├── SleepReport_<timestamp>.mat
├── SleepReport_<timestamp>.csv
├── SleepReport_<timestamp>_Dashboard.png
└── SleepReport_<timestamp>_Summary.txt
Log in or sign up for Devpost to join the conversation.