581  Mobile Phone Sensors: Web and Native APIs

581.1 Web-Based Mobile Sensing

581.1.1 Generic Sensor API (Web Standards)

Modern web browsers provide standardized APIs for accessing smartphone sensors without requiring native apps.

581.1.1.1 Accelerometer (Web API)

<!DOCTYPE html>
<html>
<head>
    <title>Accelerometer Demo</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            padding: 20px;
        }
        .sensor-data {
            font-size: 24px;
            margin: 20px 0;
        }
        .axis {
            padding: 10px;
            margin: 5px 0;
            background: #f0f0f0;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <h1>Accelerometer Sensor</h1>
    <div id="status">Checking sensor support...</div>

    <div class="sensor-data">
        <div class="axis" id="x-axis">X: 0 m/s²</div>
        <div class="axis" id="y-axis">Y: 0 m/s²</div>
        <div class="axis" id="z-axis">Z: 0 m/s²</div>
    </div>

    <div id="activity">Activity: Unknown</div>

    <script>
        // Check if Generic Sensor API is supported
        if ('Accelerometer' in window) {
            document.getElementById('status').textContent = 'Accelerometer supported!';

            try {
                const accelerometer = new Accelerometer({ frequency: 60 });

                accelerometer.addEventListener('reading', () => {
                    document.getElementById('x-axis').textContent =
                        `X: ${accelerometer.x.toFixed(2)} m/s²`;
                    document.getElementById('y-axis').textContent =
                        `Y: ${accelerometer.y.toFixed(2)} m/s²`;
                    document.getElementById('z-axis').textContent =
                        `Z: ${accelerometer.z.toFixed(2)} m/s²`;

                    // Detect activity
                    const magnitude = Math.sqrt(
                        accelerometer.x ** 2 +
                        accelerometer.y ** 2 +
                        accelerometer.z ** 2
                    );

                    let activity = 'Stationary';
                    if (magnitude > 12) {
                        activity = 'Shaking';
                    } else if (magnitude > 10) {
                        activity = 'Walking';
                    }

                    document.getElementById('activity').textContent =
                        `Activity: ${activity}`;
                });

                accelerometer.addEventListener('error', event => {
                    console.error('Accelerometer error:', event.error.name);
                    document.getElementById('status').textContent =
                        `Error: ${event.error.message}`;
                });

                accelerometer.start();

            } catch (error) {
                console.error('Failed to start accelerometer:', error);
                document.getElementById('status').textContent =
                    `Error: ${error.message}`;
            }

        } else {
            document.getElementById('status').textContent =
                'Accelerometer not supported by your browser';
        }
    </script>
</body>
</html>

581.1.1.2 Geolocation API

<!DOCTYPE html>
<html>
<head>
    <title>GPS Location Tracker</title>
</head>
<body>
    <h1>GPS Location Tracker</h1>
    <button onclick="getCurrentLocation()">Get Current Location</button>
    <button onclick="startTracking()">Start Tracking</button>
    <button onclick="stopTracking()">Stop Tracking</button>

    <div id="location-info"></div>

    <script>
        let watchId = null;

        function getCurrentLocation() {
            if ('geolocation' in navigator) {
                navigator.geolocation.getCurrentPosition(
                    displayPosition,
                    handleError,
                    {
                        enableHighAccuracy: true,
                        timeout: 5000,
                        maximumAge: 0
                    }
                );
            } else {
                alert('Geolocation not supported');
            }
        }

        function startTracking() {
            if ('geolocation' in navigator) {
                watchId = navigator.geolocation.watchPosition(
                    displayPosition,
                    handleError,
                    {
                        enableHighAccuracy: true,
                        timeout: 5000,
                        maximumAge: 0
                    }
                );
                console.log('Started tracking with watchId:', watchId);
            }
        }

        function stopTracking() {
            if (watchId !== null) {
                navigator.geolocation.clearWatch(watchId);
                console.log('Stopped tracking');
                watchId = null;
            }
        }

        function displayPosition(position) {
            const lat = position.coords.latitude;
            const lon = position.coords.longitude;
            const accuracy = position.coords.accuracy;
            const altitude = position.coords.altitude;
            const speed = position.coords.speed;
            const heading = position.coords.heading;
            const timestamp = new Date(position.timestamp);

            const info = `
                <h2>Location Data</h2>
                <p><strong>Latitude:</strong> ${lat.toFixed(6)}</p>
                <p><strong>Longitude:</strong> ${lon.toFixed(6)}</p>
                <p><strong>Accuracy:</strong> ±${accuracy.toFixed(1)} meters</p>
                <p><strong>Altitude:</strong> ${altitude ? altitude.toFixed(1) + ' m' : 'N/A'}</p>
                <p><strong>Speed:</strong> ${speed ? speed.toFixed(1) + ' m/s' : 'N/A'}</p>
                <p><strong>Heading:</strong> ${heading ? heading.toFixed(1) + '°' : 'N/A'}</p>
                <p><strong>Timestamp:</strong> ${timestamp.toLocaleString()}</p>
                <p><a href="https://www.google.com/maps?q=${lat},${lon}" target="_blank">View on Google Maps</a></p>
            `;

            document.getElementById('location-info').innerHTML = info;

            // Send to IoT backend
            sendLocationToBackend(lat, lon, accuracy, timestamp);
        }

        function handleError(error) {
            let message = '';
            switch(error.code) {
                case error.PERMISSION_DENIED:
                    message = 'User denied geolocation permission';
                    break;
                case error.POSITION_UNAVAILABLE:
                    message = 'Location information unavailable';
                    break;
                case error.TIMEOUT:
                    message = 'Location request timeout';
                    break;
                default:
                    message = 'Unknown error occurred';
            }

            document.getElementById('location-info').innerHTML =
                `<p style="color: red;">Error: ${message}</p>`;
        }

        async function sendLocationToBackend(lat, lon, accuracy, timestamp) {
            try {
                const response = await fetch('https://iot-backend.example.com/api/location', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({
                        latitude: lat,
                        longitude: lon,
                        accuracy: accuracy,
                        timestamp: timestamp.toISOString(),
                        deviceId: 'web-client-123'
                    })
                });

                if (response.ok) {
                    console.log('Location sent to backend successfully');
                } else {
                    console.error('Failed to send location:', response.statusText);
                }
            } catch (error) {
                console.error('Error sending location:', error);
            }
        }
    </script>
</body>
</html>

581.1.1.3 Device Orientation (Gyroscope/Magnetometer)

// DeviceOrientation API - combining accelerometer, gyroscope, magnetometer
window.addEventListener('deviceorientation', (event) => {
    const alpha = event.alpha;  // Z-axis rotation (0-360°) - compass
    const beta = event.beta;    // X-axis rotation (-180 to 180°) - front-to-back tilt
    const gamma = event.gamma;  // Y-axis rotation (-90 to 90°) - left-to-right tilt

    console.log(`Alpha: ${alpha}°, Beta: ${beta}°, Gamma: ${gamma}°`);

    // Compass heading (North = 0°)
    const heading = 360 - alpha;
    document.getElementById('compass').textContent = `Heading: ${heading.toFixed(0)}°`;

    // Tilt detection
    if (Math.abs(beta) > 45 || Math.abs(gamma) > 45) {
        console.log('Device tilted significantly');
    }
});

// Request permission on iOS 13+
if (typeof DeviceOrientationEvent.requestPermission === 'function') {
    DeviceOrientationEvent.requestPermission()
        .then(permissionState => {
            if (permissionState === 'granted') {
                window.addEventListener('deviceorientation', handleOrientation);
            }
        })
        .catch(console.error);
}

581.1.1.4 Progressive Web App (PWA) for IoT Sensing

<!DOCTYPE html>
<html>
<head>
    <title>IoT Sensor PWA</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="manifest" href="manifest.json">
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 0 auto;
            padding: 20px;
        }
        .sensor-card {
            background: #fff;
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 15px;
            margin: 10px 0;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .value {
            font-size: 32px;
            font-weight: bold;
            color: #007bff;
        }
        button {
            background: #007bff;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 5px;
            font-size: 16px;
            cursor: pointer;
            margin: 5px;
        }
        button:hover {
            background: #0056b3;
        }
        #status {
            padding: 10px;
            margin: 10px 0;
            border-radius: 5px;
            background: #d4edda;
            color: #155724;
        }
    </style>
</head>
<body>
    <h1>IoT Multi-Sensor App</h1>
    <div id="status">Offline</div>

    <button onclick="startSensing()">Start Sensing</button>
    <button onclick="stopSensing()">Stop Sensing</button>
    <button onclick="sendData()">Send to Cloud</button>

    <div class="sensor-card">
        <h3>Accelerometer</h3>
        <div class="value" id="accel">0.00 m/s²</div>
    </div>

    <div class="sensor-card">
        <h3>Location</h3>
        <div class="value" id="location">Unknown</div>
    </div>

    <div class="sensor-card">
        <h3>Light Level</h3>
        <div class="value" id="light">Unknown</div>
    </div>

    <div class="sensor-card">
        <h3>Battery Level</h3>
        <div class="value" id="battery">Unknown</div>
    </div>

    <script>
        let sensorData = {
            accelerometer: null,
            location: null,
            light: null,
            battery: null,
            timestamp: null
        };

        let accelerometer = null;
        let lightSensor = null;
        let geoWatchId = null;

        // Service Worker for offline support
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('/sw.js')
                .then(reg => console.log('Service Worker registered'))
                .catch(err => console.error('Service Worker registration failed:', err));
        }

        // Check online status
        window.addEventListener('online', () => {
            document.getElementById('status').textContent = 'Online';
            document.getElementById('status').style.background = '#d4edda';
        });

        window.addEventListener('offline', () => {
            document.getElementById('status').textContent = 'Offline - Data cached locally';
            document.getElementById('status').style.background = '#f8d7da';
        });

        function startSensing() {
            // Start accelerometer
            if ('Accelerometer' in window) {
                accelerometer = new Accelerometer({ frequency: 10 });
                accelerometer.addEventListener('reading', () => {
                    const magnitude = Math.sqrt(
                        accelerometer.x ** 2 +
                        accelerometer.y ** 2 +
                        accelerometer.z ** 2
                    );
                    document.getElementById('accel').textContent =
                        `${magnitude.toFixed(2)} m/s²`;
                    sensorData.accelerometer = {
                        x: accelerometer.x,
                        y: accelerometer.y,
                        z: accelerometer.z,
                        magnitude: magnitude
                    };
                });
                accelerometer.start();
            }

            // Start GPS
            geoWatchId = navigator.geolocation.watchPosition(
                position => {
                    const lat = position.coords.latitude.toFixed(6);
                    const lon = position.coords.longitude.toFixed(6);
                    document.getElementById('location').textContent =
                        `${lat}, ${lon}`;
                    sensorData.location = {
                        latitude: position.coords.latitude,
                        longitude: position.coords.longitude,
                        accuracy: position.coords.accuracy
                    };
                },
                error => console.error('GPS error:', error),
                { enableHighAccuracy: true }
            );

            // Start ambient light sensor
            if ('AmbientLightSensor' in window) {
                lightSensor = new AmbientLightSensor();
                lightSensor.addEventListener('reading', () => {
                    document.getElementById('light').textContent =
                        `${lightSensor.illuminance.toFixed(0)} lux`;
                    sensorData.light = lightSensor.illuminance;
                });
                lightSensor.start();
            }

            // Battery status
            navigator.getBattery().then(battery => {
                function updateBattery() {
                    const level = (battery.level * 100).toFixed(0);
                    const charging = battery.charging ? ' (Charging)' : '';
                    document.getElementById('battery').textContent =
                        `${level}%${charging}`;
                    sensorData.battery = {
                        level: battery.level,
                        charging: battery.charging
                    };
                }

                battery.addEventListener('levelchange', updateBattery);
                battery.addEventListener('chargingchange', updateBattery);
                updateBattery();
            });

            console.log('Sensors started');
        }

        function stopSensing() {
            if (accelerometer) accelerometer.stop();
            if (lightSensor) lightSensor.stop();
            if (geoWatchId) navigator.geolocation.clearWatch(geoWatchId);
            console.log('Sensors stopped');
        }

        async function sendData() {
            sensorData.timestamp = new Date().toISOString();

            try {
                const response = await fetch('https://iot-backend.example.com/api/sensor-data', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify(sensorData)
                });

                if (response.ok) {
                    console.log('Data sent successfully');
                    alert('Data sent to cloud!');
                } else {
                    // Cache data if offline
                    cacheData(sensorData);
                    alert('Data cached - will sync when online');
                }
            } catch (error) {
                console.error('Error sending data:', error);
                cacheData(sensorData);
                alert('Data cached locally');
            }
        }

        function cacheData(data) {
            // Store in IndexedDB or localStorage
            const cachedData = JSON.parse(localStorage.getItem('cachedSensorData') || '[]');
            cachedData.push(data);
            localStorage.setItem('cachedSensorData', JSON.stringify(cachedData));
        }
    </script>
</body>
</html>

581.2 Native Mobile Applications

581.2.1 React Native (Cross-Platform)

// React Native IoT Sensor App
import React, { useState, useEffect } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { Accelerometer, Gyroscope, Magnetometer } from 'expo-sensors';
import * as Location from 'expo-location';
import { Audio } from 'expo-av';

export default function IoTSensorApp() {
  const [accelData, setAccelData] = useState({ x: 0, y: 0, z: 0 });
  const [gyroData, setGyroData] = useState({ x: 0, y: 0, z: 0 });
  const [location, setLocation] = useState(null);
  const [recording, setRecording] = useState(null);
  const [isSensing, setIsSensing] = useState(false);

  useEffect(() => {
    if (isSensing) {
      // Start accelerometer
      const accelSubscription = Accelerometer.addListener(data => {
        setAccelData(data);
      });
      Accelerometer.setUpdateInterval(100); // 100ms

      // Start gyroscope
      const gyroSubscription = Gyroscope.addListener(data => {
        setGyroData(data);
      });
      Gyroscope.setUpdateInterval(100);

      // Start location tracking
      startLocationTracking();

      return () => {
        accelSubscription.remove();
        gyroSubscription.remove();
      };
    }
  }, [isSensing]);

  const startLocationTracking = async () => {
    let { status } = await Location.requestForegroundPermissionsAsync();
    if (status !== 'granted') {
      console.log('Location permission denied');
      return;
    }

    Location.watchPositionAsync(
      {
        accuracy: Location.Accuracy.High,
        timeInterval: 5000,
        distanceInterval: 10
      },
      (loc) => {
        setLocation(loc.coords);
      }
    );
  };

  const startAudioRecording = async () => {
    try {
      const { status } = await Audio.requestPermissionsAsync();
      if (status !== 'granted') {
        console.log('Audio permission denied');
        return;
      }

      await Audio.setAudioModeAsync({
        allowsRecordingIOS: true,
        playsInSilentModeIOS: true,
      });

      const { recording } = await Audio.Recording.createAsync(
        Audio.RECORDING_OPTIONS_PRESET_HIGH_QUALITY
      );
      setRecording(recording);
      console.log('Recording started');
    } catch (err) {
      console.error('Failed to start recording', err);
    }
  };

  const stopAudioRecording = async () => {
    await recording.stopAndUnloadAsync();
    const uri = recording.getURI();
    console.log('Recording saved to:', uri);
    setRecording(null);

    // Upload to server
    uploadAudioFile(uri);
  };

  const uploadAudioFile = async (uri) => {
    const formData = new FormData();
    formData.append('audio', {
      uri: uri,
      type: 'audio/m4a',
      name: 'recording.m4a',
    });

    try {
      const response = await fetch('https://iot-backend.example.com/api/audio', {
        method: 'POST',
        body: formData,
      });
      console.log('Audio uploaded:', response.status);
    } catch (error) {
      console.error('Upload failed:', error);
    }
  };

  const sendSensorData = async () => {
    const data = {
      accelerometer: accelData,
      gyroscope: gyroData,
      location: location,
      timestamp: new Date().toISOString()
    };

    try {
      const response = await fetch('https://iot-backend.example.com/api/sensor-data', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data)
      });

      console.log('Data sent:', response.status);
    } catch (error) {
      console.error('Error sending data:', error);
    }
  };

  return (
    <View style={styles.container}>
      <Text style={styles.title}>IoT Sensor App</Text>

      <View style={styles.sensorCard}>
        <Text style={styles.sensorTitle}>Accelerometer</Text>
        <Text>X: {accelData.x.toFixed(2)} m/</Text>
        <Text>Y: {accelData.y.toFixed(2)} m/</Text>
        <Text>Z: {accelData.z.toFixed(2)} m/</Text>
      </View>

      <View style={styles.sensorCard}>
        <Text style={styles.sensorTitle}>Gyroscope</Text>
        <Text>X: {gyroData.x.toFixed(2)} rad/s</Text>
        <Text>Y: {gyroData.y.toFixed(2)} rad/s</Text>
        <Text>Z: {gyroData.z.toFixed(2)} rad/s</Text>
      </View>

      <View style={styles.sensorCard}>
        <Text style={styles.sensorTitle}>Location</Text>
        {location && (
          <>
            <Text>Lat: {location.latitude.toFixed(6)}</Text>
            <Text>Lon: {location.longitude.toFixed(6)}</Text>
            <Text>Accuracy: ±{location.accuracy.toFixed(1)}m</Text>
          </>
        )}
      </View>

      <Button
        title={isSensing ? "Stop Sensing" : "Start Sensing"}
        onPress={() => setIsSensing(!isSensing)}
      />

      <Button
        title={recording ? "Stop Recording" : "Start Audio Recording"}
        onPress={recording ? stopAudioRecording : startAudioRecording}
      />

      <Button
        title="Send Data to Cloud"
        onPress={sendSensorData}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
    textAlign: 'center',
  },
  sensorCard: {
    backgroundColor: 'white',
    padding: 15,
    borderRadius: 10,
    marginBottom: 15,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
    elevation: 3,
  },
  sensorTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
});

581.3 What’s Next

Now that you understand how to access smartphone sensors via Web and Native APIs, continue to:

  • Participatory Sensing: Learn about crowdsourcing applications, privacy considerations, and battery optimization
  • Mobile Phone Labs: Practice building mobile sensing applications with hands-on exercises

Previous: Mobile Phone Introduction | Return to: Mobile Phone as a Sensor Overview