%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22', 'secondaryColor': '#f5f5f5', 'tertiaryColor': '#fff'}}}%%
flowchart TD
A[Read Accelerometer<br/>x, y, z values] --> B[Calculate Magnitude<br/>√(x² + y² + z²)]
B --> C[Store in Buffer<br/>Last 50 readings]
C --> D{Buffer Full?}
D -->|No| A
D -->|Yes| E[Calculate Statistics]
E --> F[Mean = average of magnitudes]
E --> G[Standard Deviation]
G --> H{Classify Activity}
H -->|σ < 0.5| I[Stationary]
H -->|0.5 ≤ σ < 2.5| J[Walking]
H -->|2.5 ≤ σ < 4.0| K[Running]
H -->|σ ≥ 4.0| L[Intense Activity]
584 Mobile Phone Labs: Web-Based Sensor Applications
584.1 Learning Objectives
By completing these labs, you will be able to:
- Access smartphone accelerometer data using Web APIs
- Implement activity recognition algorithms based on motion patterns
- Detect steps using peak detection techniques
- Build GPS tracking applications with geofencing capabilities
- Calculate distances using the Haversine formula
- Trigger notifications based on location events
584.2 Lab 1: Web-Based Accelerometer Activity Recognition
Objective: Create a web application that uses the smartphone accelerometer to recognize user activities.
Materials: - Smartphone with accelerometer - Web browser (Chrome/Edge recommended) - Text editor for HTML/JavaScript
Activity recognition is like teaching your phone to understand what you’re doing - walking, running, or sitting still. Your phone has a tiny sensor called an accelerometer that feels every movement, just like how you feel when you’re in a car that speeds up or slows down.
When you walk, your phone bounces up and down in a pattern. When you run, it bounces faster and harder. When you’re sitting still, it barely moves at all. By measuring these patterns, we can figure out what activity you’re doing!
The Sensor Squad says: “Think of the accelerometer like a tiny ball on a spring inside your phone. When you move, the ball wiggles - and we measure how much it wiggles to guess what you’re doing!”
584.2.1 Complete Application Code
<!DOCTYPE html>
<html>
<head>
<title>Activity Recognition</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
max-width: 600px;
margin: 0 auto;
}
.activity-display {
font-size: 48px;
text-align: center;
padding: 40px;
margin: 20px 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
}
.sensor-data {
background: #f5f5f5;
padding: 15px;
border-radius: 10px;
margin: 10px 0;
}
button {
width: 100%;
padding: 15px;
font-size: 18px;
margin: 10px 0;
border: none;
border-radius: 8px;
cursor: pointer;
}
.start-btn {
background: #4CAF50;
color: white;
}
.stop-btn {
background: #f44336;
color: white;
}
.stats {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin: 20px 0;
}
.stat-card {
background: white;
border: 2px solid #e0e0e0;
border-radius: 10px;
padding: 15px;
text-align: center;
}
.stat-value {
font-size: 32px;
font-weight: bold;
color: #667eea;
}
.stat-label {
font-size: 14px;
color: #666;
margin-top: 5px;
}
</style>
</head>
<body>
<h1>Activity Recognition</h1>
<button class="start-btn" onclick="startTracking()">Start Tracking</button>
<button class="stop-btn" onclick="stopTracking()">Stop Tracking</button>
<div class="activity-display" id="activity">Stationary</div>
<div class="stats">
<div class="stat-card">
<div class="stat-value" id="step-count">0</div>
<div class="stat-label">Steps</div>
</div>
<div class="stat-card">
<div class="stat-value" id="distance">0.0</div>
<div class="stat-label">Distance (m)</div>
</div>
</div>
<div class="sensor-data">
<h3>Accelerometer Data</h3>
<div id="accel-data">Waiting for sensor...</div>
</div>
<div class="sensor-data">
<h3>Activity Log</h3>
<div id="activity-log"></div>
</div>
<script>
let accelerometer = null;
let readings = [];
let stepCount = 0;
let lastPeakTime = 0;
let currentActivity = 'Stationary';
function startTracking() {
if ('Accelerometer' in window) {
try {
accelerometer = new Accelerometer({ frequency: 50 });
accelerometer.addEventListener('reading', processReading);
accelerometer.addEventListener('error', e => {
console.error('Accelerometer error:', e);
alert('Accelerometer error: ' + e.error.message);
});
accelerometer.start();
console.log('Accelerometer started');
logActivity('Tracking started');
} catch (error) {
alert('Failed to start accelerometer: ' + error.message);
}
} else {
alert('Accelerometer not supported by your browser.\n' +
'Try Chrome or Edge on Android.');
}
}
function stopTracking() {
if (accelerometer) {
accelerometer.stop();
accelerometer = null;
logActivity('Tracking stopped');
}
}
function processReading() {
const x = accelerometer.x;
const y = accelerometer.y;
const z = accelerometer.z;
const magnitude = Math.sqrt(x*x + y*y + z*z);
// Update display
document.getElementById('accel-data').innerHTML = `
X: ${x.toFixed(2)} m/s²<br>
Y: ${y.toFixed(2)} m/s²<br>
Z: ${z.toFixed(2)} m/s²<br>
<strong>Magnitude: ${magnitude.toFixed(2)} m/s²</strong>
`;
// Store reading
readings.push({ x, y, z, magnitude, timestamp: Date.now() });
// Keep last 50 readings (1 second at 50 Hz)
if (readings.length > 50) {
readings.shift();
}
// Detect activity (every 10 readings)
if (readings.length === 50 && readings.length % 10 === 0) {
detectActivity();
}
// Detect steps
detectSteps(magnitude);
}
function detectActivity() {
const magnitudes = readings.map(r => r.magnitude);
const mean = magnitudes.reduce((a, b) => a + b) / magnitudes.length;
const variance = magnitudes.reduce((sum, m) =>
sum + Math.pow(m - mean, 2), 0) / magnitudes.length;
const stdDev = Math.sqrt(variance);
let newActivity;
if (stdDev < 0.5) {
newActivity = 'Stationary';
} else if (stdDev < 2.5) {
newActivity = 'Walking';
} else if (stdDev < 4.0) {
newActivity = 'Running';
} else {
newActivity = 'Intense Activity';
}
if (newActivity !== currentActivity) {
currentActivity = newActivity;
document.getElementById('activity').textContent = currentActivity;
logActivity(`Activity changed: ${currentActivity}`);
}
}
function detectSteps(magnitude) {
const now = Date.now();
const minStepInterval = 300; // 300ms between steps
// Check if it's a peak (> 10.5 m/s²) and enough time passed
if (magnitude > 10.5 && now - lastPeakTime > minStepInterval) {
stepCount++;
lastPeakTime = now;
document.getElementById('step-count').textContent = stepCount;
// Calculate distance (0.7m per step)
const distance = (stepCount * 0.7).toFixed(1);
document.getElementById('distance').textContent = distance;
}
}
function logActivity(message) {
const log = document.getElementById('activity-log');
const time = new Date().toLocaleTimeString();
log.innerHTML = `[${time}] ${message}<br>` + log.innerHTML;
// Keep only last 10 messages
const messages = log.innerHTML.split('<br>');
if (messages.length > 10) {
log.innerHTML = messages.slice(0, 10).join('<br>');
}
}
// Request permission on iOS 13+
if (typeof DeviceMotionEvent !== 'undefined' &&
typeof DeviceMotionEvent.requestPermission === 'function') {
document.querySelector('.start-btn').addEventListener('click', () => {
DeviceMotionEvent.requestPermission()
.then(permissionState => {
if (permissionState === 'granted') {
startTracking();
} else {
alert('Permission denied for motion sensors');
}
})
.catch(console.error);
});
}
</script>
</body>
</html>584.2.2 How the Algorithm Works
The activity recognition algorithm uses statistical analysis of accelerometer readings:
584.2.3 Expected Learning Outcomes
After completing this lab, you will understand:
- Access smartphone accelerometer via Web APIs
- Implement activity recognition algorithms
- Detect steps using peak detection
- Calculate derived metrics (distance, pace)
584.2.4 Exercises
- Add gyroscope data for better activity classification (use
GyroscopeAPI) - Implement calorie estimation based on activity type and duration
- Store activity history in localStorage for later analysis
- Add data visualization with charts using Chart.js library
584.3 Lab 2: GPS Location Tracker with Geofencing Alerts
Objective: Build a web-based GPS tracker with geofencing notifications.
Materials: - Smartphone with GPS - Web browser - Internet connection
Geofencing is like drawing an invisible circle around a place on a map. When you enter or leave that circle, your phone can do something - like send a notification or play a sound.
Imagine putting an invisible fence around your home. When you leave the fence area, your phone could remind you to grab your keys! When you return, it could turn on the lights automatically.
The Sensor Squad explains: “Think of geofencing like a magic trip wire. Step over it, and something happens! It’s super useful for reminders, security, and smart home automation.”
584.3.1 Complete Application Code
<!DOCTYPE html>
<html>
<head>
<title>GPS Geofencing Tracker</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: Arial, sans-serif;
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
#map {
width: 100%;
height: 400px;
border: 2px solid #ddd;
border-radius: 10px;
margin: 20px 0;
}
.controls {
display: flex;
gap: 10px;
margin: 15px 0;
}
button {
flex: 1;
padding: 12px;
font-size: 16px;
border: none;
border-radius: 5px;
cursor: pointer;
color: white;
}
.start { background: #4CAF50; }
.stop { background: #f44336; }
.add-fence { background: #2196F3; }
.location-info {
background: #f5f5f5;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
}
.geofence-list {
margin: 20px 0;
}
.geofence-item {
background: white;
border: 1px solid #ddd;
padding: 10px;
margin: 5px 0;
border-radius: 5px;
display: flex;
justify-content: space-between;
align-items: center;
}
.alert {
padding: 15px;
margin: 10px 0;
border-radius: 5px;
font-weight: bold;
}
.alert-enter { background: #d4edda; color: #155724; }
.alert-exit { background: #fff3cd; color: #856404; }
</style>
</head>
<body>
<h1>GPS Geofencing Tracker</h1>
<div class="controls">
<button class="start" onclick="startTracking()">Start GPS</button>
<button class="stop" onclick="stopTracking()">Stop GPS</button>
<button class="add-fence" onclick="addCurrentLocationGeofence()">Add Geofence Here</button>
</div>
<div id="alerts"></div>
<div class="location-info" id="location-info">
Waiting for GPS...
</div>
<div id="map">
<p style="text-align: center; padding-top: 180px;">
Map would be displayed here (requires Google Maps API key)
</p>
</div>
<div class="geofence-list">
<h3>Geofences</h3>
<div id="geofence-list">No geofences defined</div>
</div>
<div class="location-info">
<h3>Statistics</h3>
<div id="stats">
Total distance: 0 km<br>
Tracking duration: 0 minutes<br>
Updates received: 0
</div>
</div>
<script>
let watchId = null;
let geofences = [];
let locations = [];
let totalDistance = 0;
let startTime = null;
let updateCount = 0;
let insideGeofences = new Set();
// Load saved geofences from localStorage
const saved = localStorage.getItem('geofences');
if (saved) {
geofences = JSON.parse(saved);
updateGeofenceList();
}
function startTracking() {
if ('geolocation' in navigator) {
startTime = Date.now();
watchId = navigator.geolocation.watchPosition(
handlePosition,
handleError,
{
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0
}
);
showAlert('GPS tracking started', 'alert-enter');
} else {
alert('Geolocation not supported by your browser');
}
}
function stopTracking() {
if (watchId !== null) {
navigator.geolocation.clearWatch(watchId);
watchId = null;
showAlert('GPS tracking stopped', 'alert-exit');
}
}
function handlePosition(position) {
const lat = position.coords.latitude;
const lon = position.coords.longitude;
const accuracy = position.coords.accuracy;
updateCount++;
// Calculate distance from last location
if (locations.length > 0) {
const lastLoc = locations[locations.length - 1];
const distance = calculateDistance(
lastLoc.lat, lastLoc.lon,
lat, lon
);
totalDistance += distance;
}
locations.push({ lat, lon, timestamp: Date.now() });
// Update UI
document.getElementById('location-info').innerHTML = `
<strong>Current Location</strong><br>
Latitude: ${lat.toFixed(6)}<br>
Longitude: ${lon.toFixed(6)}<br>
Accuracy: ±${accuracy.toFixed(1)} meters<br>
<a href="https://www.google.com/maps?q=${lat},${lon}" target="_blank">View on Google Maps</a>
`;
updateStats();
checkGeofences(lat, lon);
}
function handleError(error) {
const messages = {
1: 'Permission denied - enable location services',
2: 'Position unavailable',
3: 'Request timeout'
};
alert(messages[error.code] || 'Unknown error');
}
function calculateDistance(lat1, lon1, lat2, lon2) {
const R = 6371000; // Earth radius in meters
const φ1 = lat1 * Math.PI / 180;
const φ2 = lat2 * Math.PI / 180;
const Δφ = (lat2 - lat1) * Math.PI / 180;
const Δλ = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(Δφ/2) * Math.sin(Δφ/2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ/2) * Math.sin(Δλ/2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c; // Distance in meters
}
function addCurrentLocationGeofence() {
if (locations.length === 0) {
alert('No location data yet. Start GPS tracking first.');
return;
}
const lastLoc = locations[locations.length - 1];
const name = prompt('Enter geofence name:', `Geofence ${geofences.length + 1}`);
if (name) {
const radius = parseFloat(prompt('Enter radius in meters:', '100'));
geofences.push({
name,
lat: lastLoc.lat,
lon: lastLoc.lon,
radius
});
localStorage.setItem('geofences', JSON.stringify(geofences));
updateGeofenceList();
showAlert(`Geofence "${name}" added at current location`, 'alert-enter');
}
}
function updateGeofenceList() {
if (geofences.length === 0) {
document.getElementById('geofence-list').innerHTML = 'No geofences defined';
return;
}
let html = '';
geofences.forEach((fence, index) => {
html += `
<div class="geofence-item">
<div>
<strong>${fence.name}</strong><br>
${fence.lat.toFixed(6)}, ${fence.lon.toFixed(6)} (${fence.radius}m radius)
</div>
<button onclick="removeGeofence(${index})"
style="padding: 5px 10px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer;">
Remove
</button>
</div>
`;
});
document.getElementById('geofence-list').innerHTML = html;
}
function removeGeofence(index) {
geofences.splice(index, 1);
localStorage.setItem('geofences', JSON.stringify(geofences));
updateGeofenceList();
}
function checkGeofences(lat, lon) {
geofences.forEach((fence, index) => {
const distance = calculateDistance(lat, lon, fence.lat, fence.lon);
const isInside = distance <= fence.radius;
const wasInside = insideGeofences.has(index);
if (isInside && !wasInside) {
// Entered geofence
insideGeofences.add(index);
showAlert(`Entered geofence: ${fence.name}`, 'alert-enter');
// Vibrate if supported
if ('vibrate' in navigator) {
navigator.vibrate(200);
}
// Show notification if permitted
if ('Notification' in window && Notification.permission === 'granted') {
new Notification('Geofence Alert', {
body: `You entered ${fence.name}`,
icon: 'https://maps.google.com/mapfiles/ms/icons/green-dot.png'
});
}
} else if (!isInside && wasInside) {
// Exited geofence
insideGeofences.delete(index);
showAlert(`Exited geofence: ${fence.name}`, 'alert-exit');
if ('vibrate' in navigator) {
navigator.vibrate([100, 50, 100]);
}
}
});
}
function updateStats() {
const duration = startTime ? (Date.now() - startTime) / 60000 : 0;
document.getElementById('stats').innerHTML = `
Total distance: ${(totalDistance / 1000).toFixed(3)} km<br>
Tracking duration: ${duration.toFixed(1)} minutes<br>
Updates received: ${updateCount}<br>
Active geofences: ${insideGeofences.size}
`;
}
function showAlert(message, className) {
const alertDiv = document.createElement('div');
alertDiv.className = `alert ${className}`;
alertDiv.textContent = message;
const container = document.getElementById('alerts');
container.insertBefore(alertDiv, container.firstChild);
// Remove after 5 seconds
setTimeout(() => alertDiv.remove(), 5000);
}
// Request notification permission
if ('Notification' in window && Notification.permission === 'default') {
Notification.requestPermission();
}
</script>
</body>
</html>584.3.2 The Haversine Formula
Distance calculations on a sphere require the Haversine formula:
%%{init: {'theme': 'base', 'themeVariables': {'primaryColor': '#2C3E50', 'primaryTextColor': '#fff', 'primaryBorderColor': '#16A085', 'lineColor': '#E67E22', 'secondaryColor': '#f5f5f5', 'tertiaryColor': '#fff'}}}%%
flowchart LR
subgraph Input
A[Point 1<br/>lat₁, lon₁]
B[Point 2<br/>lat₂, lon₂]
end
subgraph "Haversine Calculation"
C[Convert to Radians]
D[Calculate Δφ, Δλ]
E["a = sin²(Δφ/2) + cos(φ₁)·cos(φ₂)·sin²(Δλ/2)"]
F["c = 2·atan2(√a, √(1-a))"]
G["distance = R × c"]
end
A --> C
B --> C
C --> D --> E --> F --> G
H[Distance in meters]
G --> H
Where R = 6,371 km is Earth’s radius.
584.3.3 Expected Learning Outcomes
After completing this lab, you will understand:
- Use Geolocation API for continuous tracking
- Implement geofencing with entry/exit detection
- Calculate distances using the Haversine formula
- Persist data using localStorage
- Trigger notifications on geofence events
584.3.4 Exercises
- Add polygon geofences - Support non-circular boundaries using point-in-polygon algorithms
- Implement route playback - Visualize stored location history on a map
- Send geofence events to IoT backend - Use MQTT or HTTP to report events
- Add battery drain estimation - Track GPS usage time and estimate power consumption
584.4 Summary
In these two labs, you built foundational mobile sensing applications:
Lab 1 - Activity Recognition: - Accessed accelerometer data at 50 Hz using the Generic Sensor API - Implemented statistical activity classification using standard deviation - Built step detection using peak detection with debouncing - Calculated derived metrics (distance walked)
Lab 2 - GPS Geofencing: - Implemented continuous location tracking with the Geolocation API - Built geofence management with localStorage persistence - Used the Haversine formula for accurate distance calculations - Triggered notifications and vibrations on geofence events
584.5 What’s Next
Continue to the next lab chapter to build more advanced applications:
- Progressive Web Apps with offline capability and installation
- Audio sensing with the Web Audio API for noise monitoring
584.6 Resources
584.6.1 Web APIs Used
- Generic Sensor API - Accelerometer, gyroscope access
- Geolocation API - GPS location tracking
- Notifications API - Push notifications
- Vibration API - Haptic feedback
584.6.2 Further Reading
- Activity Recognition on Mobile Devices - Android motion sensors guide
- Geofencing Best Practices - iOS geofencing guide