sensor_demo / app_ipad.py
radford1's picture
Rename app.py to app_ipad.py
b1f0660 verified
import gradio as gr
import boto3
import json
import time
import threading
import queue
from datetime import datetime
import pandas as pd
import os
from botocore.exceptions import NoCredentialsError, ClientError
import tempfile
import platform
class AccelerometerS3App:
def __init__(self):
self.s3_client = None
self.is_recording = False
self.data_queue = queue.Queue()
self.recording_thread = None
self.recorded_data = []
self.motion_data_buffer = []
def setup_s3_client(self, aws_access_key, aws_secret_key, region_name):
"""Initialize S3 client with credentials"""
try:
self.s3_client = boto3.client(
's3',
aws_access_key_id=aws_access_key,
aws_secret_access_key=aws_secret_key,
region_name=region_name
)
# Test connection
self.s3_client.list_buckets()
return "βœ… AWS credentials configured successfully!"
except NoCredentialsError:
return "❌ Invalid AWS credentials"
except Exception as e:
return f"❌ Error setting up S3 client: {str(e)}"
def process_motion_data_from_browser(self, motion_data_json):
"""Process motion data received from iPad browser"""
try:
if not motion_data_json or motion_data_json.strip() == "":
return "❌ No motion data received"
# Parse the JSON data from the browser
motion_data = json.loads(motion_data_json)
# Add server timestamp and process data
for reading in motion_data:
processed_reading = {
'timestamp': reading.get('timestamp', datetime.now().isoformat()),
'x': round(float(reading.get('x', 0)), 6),
'y': round(float(reading.get('y', 0)), 6),
'z': round(float(reading.get('z', 0)), 6),
'device_id': reading.get('device_id', 'ipad_unknown'),
'data_source': 'device_motion_api',
'user_agent': reading.get('user_agent', 'unknown'),
'orientation': reading.get('orientation', 'unknown')
}
self.motion_data_buffer.append(processed_reading)
return f"βœ… Processed {len(motion_data)} motion readings"
except json.JSONDecodeError as e:
return f"❌ Invalid JSON data: {str(e)}"
except Exception as e:
return f"❌ Error processing motion data: {str(e)}"
def start_recording_session(self, duration, sample_rate):
"""Start a recording session for specified duration"""
self.motion_data_buffer = []
self.is_recording = True
return f"""
πŸ”΄ Recording session started for {duration} seconds at {sample_rate} Hz
πŸ“± **iPad Instructions:**
1. Make sure your iPad is in landscape orientation
2. The motion sensor should now be active
3. Move your iPad around to generate motion data
4. Data will be collected automatically
⏱️ Recording will automatically stop after {duration} seconds.
"""
def stop_recording_session(self):
"""Stop recording and transfer data"""
if not self.is_recording:
return "❌ No recording session in progress"
self.is_recording = False
self.recorded_data = self.motion_data_buffer.copy()
return f"⏹️ Recording session stopped. {len(self.recorded_data)} samples collected from iPad sensors"
def get_device_id(self):
"""Generate a device identifier for iPad"""
return f"ipad_{int(time.time())}"
def upload_to_s3(self, bucket_name, file_prefix=""):
"""Upload recorded data to S3 bucket"""
if not self.s3_client:
return "❌ AWS credentials not configured"
if not self.recorded_data:
return "❌ No data to upload. Record some data first."
try:
# Create filename with timestamp
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"{file_prefix}ipad_accelerometer_data_{timestamp}.json"
# Prepare data for upload
upload_data = {
'device_type': 'iPad',
'device_id': self.recorded_data[0]['device_id'] if self.recorded_data else 'unknown',
'recording_info': {
'start_time': self.recorded_data[0]['timestamp'] if self.recorded_data else None,
'end_time': self.recorded_data[-1]['timestamp'] if self.recorded_data else None,
'sample_count': len(self.recorded_data),
'data_source': 'device_motion_api'
},
'data': self.recorded_data
}
# Convert to JSON and upload
json_data = json.dumps(upload_data, indent=2)
self.s3_client.put_object(
Bucket=bucket_name,
Key=filename,
Body=json_data,
ContentType='application/json'
)
return f"βœ… Successfully uploaded {filename} to s3://{bucket_name}/{filename}"
except ClientError as e:
return f"❌ AWS Error: {str(e)}"
except Exception as e:
return f"❌ Upload error: {str(e)}"
def get_data_preview(self):
"""Get a preview of recorded data"""
if not self.recorded_data:
return "No data recorded yet from iPad sensors"
# Create a DataFrame for better display
df = pd.DataFrame(self.recorded_data)
# Determine data source
data_source = self.recorded_data[0].get('data_source', 'unknown')
# Show basic statistics
preview = f"""
πŸ“Š **iPad Motion Data Summary:**
- πŸ“± Data Source: Device Motion API
- πŸ“² Device: {self.recorded_data[0].get('user_agent', 'iPad')}
- Total samples: {len(self.recorded_data)}
- Device ID: {self.recorded_data[0]['device_id']}
πŸ“ˆ **Acceleration Statistics (m/sΒ²):**
```
{df[['x', 'y', 'z']].describe().round(6)}
```
πŸ” **Latest 5 samples:**
```
{df[['timestamp', 'x', 'y', 'z']].tail().to_string(index=False)}
```
🧭 **Device Orientation:** {self.recorded_data[-1].get('orientation', 'Unknown')}
"""
return preview
# Initialize the app
app = AccelerometerS3App()
# Custom CSS for mobile-friendly interface
custom_css = """
.gradio-container {
max-width: 100% !important;
padding: 10px !important;
}
.motion-display {
font-family: 'Courier New', monospace;
background: #1e1e1e;
color: #00ff00;
padding: 15px;
border-radius: 8px;
margin: 10px 0;
}
.ipad-instructions {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
border-radius: 12px;
margin: 15px 0;
}
#motion-sensor-area {
background: #f0f0f0;
border: 2px dashed #007AFF;
border-radius: 12px;
padding: 20px;
text-align: center;
margin: 10px 0;
min-height: 200px;
}
"""
# JavaScript for iPad motion sensor access
motion_sensor_js = """
function startMotionSensor(duration, sampleRate) {
if (!window.DeviceMotionEvent) {
return "❌ Device Motion API not supported on this device";
}
let motionData = [];
let isRecording = true;
let sampleInterval = 1000 / sampleRate; // Convert Hz to milliseconds
let lastSample = 0;
// Get device information
const deviceInfo = {
userAgent: navigator.userAgent,
orientation: screen.orientation ? screen.orientation.type : 'unknown'
};
function handleMotion(event) {
const now = Date.now();
// Throttle samples based on sample rate
if (now - lastSample < sampleInterval) return;
lastSample = now;
if (!isRecording) return;
const acceleration = event.accelerationIncludingGravity;
if (acceleration) {
motionData.push({
timestamp: new Date().toISOString(),
x: acceleration.x || 0,
y: acceleration.y || 0,
z: acceleration.z || 0,
device_id: 'ipad_' + Date.now(),
...deviceInfo
});
}
}
// Request permission for iOS 13+
if (typeof DeviceMotionEvent.requestPermission === 'function') {
DeviceMotionEvent.requestPermission()
.then(permissionState => {
if (permissionState === 'granted') {
window.addEventListener('devicemotion', handleMotion);
} else {
alert('Motion permission denied. Please enable in Safari settings.');
}
})
.catch(console.error);
} else {
// For older devices or non-iOS
window.addEventListener('devicemotion', handleMotion);
}
// Stop recording after duration
setTimeout(() => {
isRecording = false;
window.removeEventListener('devicemotion', handleMotion);
// Return data to Gradio
const motionDataJson = JSON.stringify(motionData);
return motionDataJson;
}, duration * 1000);
return `Recording started for ${duration} seconds at ${sampleRate} Hz`;
}
"""
# Create Gradio interface
with gr.Blocks(title="iPad Accelerometer to S3", theme=gr.themes.Soft(), css=custom_css) as demo:
gr.Markdown("""
# πŸ“± iPad Accelerometer Data Logger
This app collects **real motion sensor data** from your iPad and uploads it to AWS S3.
**Requirements:**
- Safari browser on iPad (Motion API support)
- iPad with built-in accelerometer (all modern iPads)
- Network connection for data upload
""")
with gr.Tab("πŸ”§ Setup"):
gr.Markdown("### iPad Compatibility Check", elem_classes="ipad-instructions")
gr.HTML("""
<div id="compatibility-check" style="padding: 15px; background: #f8f9fa; border-radius: 8px; margin: 10px 0;">
<p><strong>πŸ“± Device Compatibility:</strong></p>
<p id="user-agent">Loading device info...</p>
<p id="motion-support">Checking motion sensor support...</p>
<p id="permission-status">Checking motion permissions...</p>
<p id="orientation-info">Checking orientation...</p>
<button id="permission-btn" onclick="requestMotionPermission()" style="background: #007AFF; color: white; border: none; padding: 10px 20px; border-radius: 8px; margin: 10px 0; font-size: 16px;">
πŸ”“ Request Motion Permission
</button>
<div id="permission-help" style="background: #fff3cd; padding: 10px; border-radius: 5px; margin: 10px 0; display: none;">
<strong>If permission button doesn't work:</strong><br>
1. Open iPad Settings β†’ Safari β†’ Privacy & Security<br>
2. Enable "Motion & Orientation Access"<br>
3. Refresh this page and try again
</div>
</div>
<script>
// Check device compatibility
document.getElementById('user-agent').innerHTML = 'πŸ” Device: ' + navigator.userAgent;
// Check if we're on iOS
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent);
const isIPad = /iPad/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
if (window.DeviceMotionEvent) {
document.getElementById('motion-support').innerHTML = 'βœ… Motion sensors supported';
// Check if permission API exists (iOS 13+)
if (typeof DeviceMotionEvent.requestPermission === 'function') {
document.getElementById('permission-status').innerHTML = 'πŸ”’ Motion permission required (iOS 13+)';
} else if (isIOS || isIPad) {
document.getElementById('permission-status').innerHTML = '⚠️ Older iOS - check Safari settings manually';
document.getElementById('permission-help').style.display = 'block';
} else {
document.getElementById('permission-status').innerHTML = 'βœ… No permission required';
document.getElementById('permission-btn').style.display = 'none';
}
} else {
document.getElementById('motion-support').innerHTML = '❌ Motion sensors not supported';
document.getElementById('permission-btn').style.display = 'none';
}
if (screen.orientation) {
document.getElementById('orientation-info').innerHTML = '🧭 Orientation: ' + screen.orientation.type;
} else {
document.getElementById('orientation-info').innerHTML = '🧭 Orientation API not available';
}
// Function to request motion permission
async function requestMotionPermission() {
const button = document.getElementById('permission-btn');
const statusEl = document.getElementById('permission-status');
button.disabled = true;
button.innerHTML = '⏳ Requesting permission...';
try {
if (typeof DeviceMotionEvent.requestPermission === 'function') {
const permission = await DeviceMotionEvent.requestPermission();
if (permission === 'granted') {
statusEl.innerHTML = 'βœ… Motion permission granted!';
button.innerHTML = 'βœ… Permission Granted';
button.style.background = '#28a745';
// Test motion sensors
testMotionSensors();
} else {
statusEl.innerHTML = '❌ Motion permission denied';
button.innerHTML = '❌ Permission Denied';
button.style.background = '#dc3545';
document.getElementById('permission-help').style.display = 'block';
}
} else {
// For older iOS or non-iOS devices
statusEl.innerHTML = '⚠️ Direct permission API not available';
button.innerHTML = '⚠️ Check Safari Settings';
document.getElementById('permission-help').style.display = 'block';
// Try to test sensors anyway
testMotionSensors();
}
} catch (error) {
console.error('Permission request failed:', error);
statusEl.innerHTML = '❌ Permission request failed: ' + error.message;
button.innerHTML = '❌ Request Failed';
button.style.background = '#dc3545';
document.getElementById('permission-help').style.display = 'block';
}
button.disabled = false;
}
// Test motion sensors
function testMotionSensors() {
let testCount = 0;
const maxTests = 10;
function handleTestMotion(event) {
testCount++;
const acc = event.accelerationIncludingGravity;
if (acc && (acc.x !== null || acc.y !== null || acc.z !== null)) {
document.getElementById('permission-status').innerHTML += '<br>🎯 Motion sensors working! X=' + (acc.x || 0).toFixed(2) + ', Y=' + (acc.y || 0).toFixed(2) + ', Z=' + (acc.z || 0).toFixed(2);
window.removeEventListener('devicemotion', handleTestMotion);
} else if (testCount >= maxTests) {
document.getElementById('permission-status').innerHTML += '<br>⚠️ No motion data received - check Safari settings';
window.removeEventListener('devicemotion', handleTestMotion);
}
}
window.addEventListener('devicemotion', handleTestMotion);
// Remove listener after 3 seconds
setTimeout(() => {
window.removeEventListener('devicemotion', handleTestMotion);
}, 3000);
}
</script>
""")
gr.Markdown("### AWS S3 Configuration")
with gr.Row():
aws_key = gr.Textbox(label="AWS Access Key ID", type="password")
aws_secret = gr.Textbox(label="AWS Secret Access Key", type="password")
with gr.Row():
aws_region = gr.Dropdown(
label="AWS Region",
choices=["us-east-1", "us-west-2", "eu-west-1", "ap-southeast-1", "ap-northeast-1"],
value="us-east-1"
)
setup_btn = gr.Button("Configure AWS", variant="primary")
setup_status = gr.Textbox(label="Setup Status", interactive=False)
setup_btn.click(
app.setup_s3_client,
inputs=[aws_key, aws_secret, aws_region],
outputs=[setup_status]
)
with gr.Tab("🎯 Motion Recording"):
gr.Markdown("### Record iPad Motion Data", elem_classes="ipad-instructions")
gr.HTML("""
<div style="background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 8px; padding: 15px; margin: 10px 0;">
<h4>πŸ“± iPad Recording Instructions:</h4>
<div style="background: #e7f3ff; padding: 10px; border-radius: 5px; margin: 10px 0;">
<strong>πŸ”’ IMPORTANT: Motion Permission Required</strong><br>
<strong>If you haven't seen a permission prompt:</strong><br>
1. First try the "Request Motion Permission" button in the Setup tab<br>
2. If that doesn't work, manually enable in Settings β†’ Safari β†’ Privacy & Security β†’ Motion & Orientation Access<br>
3. Refresh this page after changing settings
</div>
<ol>
<li><strong>Hold your iPad securely</strong> - You'll be moving it around</li>
<li><strong>Grant motion permission</strong> when prompted by Safari</li>
<li><strong>Start recording</strong> and move your iPad in different directions</li>
<li><strong>Try these motions:</strong> Tilt forward/back, left/right, rotate, gentle shaking</li>
<li><strong>Keep Safari active</strong> during recording (don't switch apps)</li>
</ol>
</div>
""")
with gr.Row():
duration = gr.Slider(
label="Recording Duration (seconds)",
minimum=5,
maximum=60,
value=15,
step=1
)
sample_rate = gr.Slider(
label="Sample Rate (Hz)",
minimum=5,
maximum=60,
value=20,
step=5
)
with gr.Row():
start_btn = gr.Button("πŸ”΄ Start iPad Recording", variant="primary", scale=2)
stop_btn = gr.Button("⏹️ Stop Recording", variant="secondary", scale=1)
recording_status = gr.Textbox(label="Recording Status", interactive=False)
# Motion sensor area
gr.HTML("""
<div id="motion-sensor-area">
<h3>πŸ“± Motion Sensor Active Area</h3>
<p>Real-time acceleration values will appear here during recording:</p>
<div class="motion-display">
<div id="accel-x">X: 0.000</div>
<div id="accel-y">Y: 0.000</div>
<div id="accel-z">Z: 0.000</div>
<div id="motion-status">Status: Ready</div>
</div>
<button id="test-motion-btn" onclick="testMotionAccess()" style="background: #17a2b8; color: white; border: none; padding: 8px 16px; border-radius: 5px; margin: 5px;">
πŸ§ͺ Test Motion Access
</button>
<p><small>Grant permission when prompted and move your iPad to see values change</small></p>
</div>
<script>
let motionDataBuffer = [];
let isRecording = false;
let recordingDuration = 15;
let sampleRate = 20;
let hasMotionPermission = false;
// Test motion access function
async function testMotionAccess() {
const statusEl = document.getElementById('motion-status');
const button = document.getElementById('test-motion-btn');
button.disabled = true;
button.innerHTML = '⏳ Testing...';
statusEl.textContent = 'Status: Requesting permission...';
try {
// Try to request permission first
if (typeof DeviceMotionEvent.requestPermission === 'function') {
const permission = await DeviceMotionEvent.requestPermission();
if (permission === 'granted') {
statusEl.textContent = 'Status: Permission granted, testing sensors...';
hasMotionPermission = true;
testSensors();
} else {
statusEl.textContent = 'Status: Permission denied - check Safari settings';
button.innerHTML = '❌ Permission Denied';
showManualInstructions();
}
} else {
// For older iOS, just try to access sensors
statusEl.textContent = 'Status: Testing sensors (no permission API)...';
testSensors();
}
} catch (error) {
console.error('Motion access test failed:', error);
statusEl.textContent = 'Status: Error - ' + error.message;
showManualInstructions();
}
button.disabled = false;
button.innerHTML = 'πŸ§ͺ Test Motion Access';
}
function testSensors() {
let testData = [];
let testCount = 0;
const maxTests = 20;
function handleTestMotion(event) {
testCount++;
const acc = event.accelerationIncludingGravity;
if (acc && (acc.x !== null || acc.y !== null || acc.z !== null)) {
const x = acc.x || 0;
const y = acc.y || 0;
const z = acc.z || 0;
// Update display
document.getElementById('accel-x').textContent = `X: ${x.toFixed(3)}`;
document.getElementById('accel-y').textContent = `Y: ${y.toFixed(3)}`;
document.getElementById('accel-z').textContent = `Z: ${z.toFixed(3)}`;
document.getElementById('motion-status').textContent = `Status: βœ… Motion sensors working! (${testCount} samples)`;
testData.push({x, y, z});
if (testData.length >= 5) {
window.removeEventListener('devicemotion', handleTestMotion);
document.getElementById('motion-status').textContent = `Status: βœ… Test complete - sensors working properly!`;
}
} else if (testCount >= maxTests) {
window.removeEventListener('devicemotion', handleTestMotion);
document.getElementById('motion-status').textContent = 'Status: ❌ No motion data - check Safari settings';
showManualInstructions();
}
}
window.addEventListener('devicemotion', handleTestMotion);
// Remove listener after 5 seconds if no data
setTimeout(() => {
if (testData.length === 0) {
window.removeEventListener('devicemotion', handleTestMotion);
document.getElementById('motion-status').textContent = 'Status: ❌ No motion data received';
showManualInstructions();
}
}, 5000);
}
function showManualInstructions() {
document.getElementById('motion-status').innerHTML = `
Status: ❌ Manual setup required<br>
<small style="color: #dc3545;">
Go to Settings β†’ Safari β†’ Privacy & Security β†’ Enable "Motion & Orientation Access"<br>
Then refresh this page and try again.
</small>
`;
}
async function startIPadRecording(duration, rate) {
motionDataBuffer = [];
isRecording = true;
recordingDuration = duration;
sampleRate = rate;
// Clear previous display
document.getElementById('accel-x').textContent = 'X: 0.000';
document.getElementById('accel-y').textContent = 'Y: 0.000';
document.getElementById('accel-z').textContent = 'Z: 0.000';
document.getElementById('motion-status').textContent = 'Status: Starting recording...';
// Request permission if needed
if (typeof DeviceMotionEvent.requestPermission === 'function' && !hasMotionPermission) {
try {
const permission = await DeviceMotionEvent.requestPermission();
if (permission === 'granted') {
hasMotionPermission = true;
startMotionCollection();
} else {
document.getElementById('motion-status').textContent = 'Status: ❌ Permission denied';
isRecording = false;
return '❌ Motion permission denied. Please enable in Safari settings.';
}
} catch (error) {
document.getElementById('motion-status').textContent = 'Status: ❌ Permission error';
isRecording = false;
return '❌ Permission request failed: ' + error.message;
}
} else {
startMotionCollection();
}
return `πŸ”΄ Recording started for ${duration} seconds at ${rate} Hz`;
}
function startMotionCollection() {
let lastSample = 0;
let sampleInterval = 1000 / sampleRate;
let sampleCount = 0;
function handleMotion(event) {
const now = Date.now();
if (now - lastSample < sampleInterval) return;
lastSample = now;
const acceleration = event.accelerationIncludingGravity;
if (acceleration && isRecording) {
const x = acceleration.x || 0;
const y = acceleration.y || 0;
const z = acceleration.z || 0;
sampleCount++;
// Update display
document.getElementById('accel-x').textContent = `X: ${x.toFixed(3)}`;
document.getElementById('accel-y').textContent = `Y: ${y.toFixed(3)}`;
document.getElementById('accel-z').textContent = `Z: ${z.toFixed(3)}`;
document.getElementById('motion-status').textContent = `Status: πŸ”΄ Recording... (${sampleCount} samples)`;
// Store data
motionDataBuffer.push({
timestamp: new Date().toISOString(),
x: x,
y: y,
z: z,
device_id: 'ipad_' + Date.now(),
user_agent: navigator.userAgent,
orientation: screen.orientation ? screen.orientation.type : 'unknown'
});
}
}
window.addEventListener('devicemotion', handleMotion);
// Auto-stop after duration
setTimeout(() => {
isRecording = false;
window.removeEventListener('devicemotion', handleMotion);
document.getElementById('motion-status').textContent = `Status: ⏹️ Recording complete (${motionDataBuffer.length} samples)`;
// Process the collected data
if (motionDataBuffer.length > 0) {
console.log('Collected', motionDataBuffer.length, 'samples');
// Here you would send the data to the Gradio backend
} else {
document.getElementById('motion-status').textContent = 'Status: ❌ No data collected';
}
}, recordingDuration * 1000);
}
function stopIPadRecording() {
isRecording = false;
document.getElementById('motion-status').textContent = `Status: ⏹️ Manually stopped (${motionDataBuffer.length} samples)`;
return `⏹️ Recording stopped. ${motionDataBuffer.length} samples collected`;
}
</script>
""")
start_btn.click(
app.start_recording_session,
inputs=[duration, sample_rate],
outputs=[recording_status]
)
stop_btn.click(
app.stop_recording_session,
outputs=[recording_status]
)
with gr.Tab("πŸ“Š Data Preview"):
with gr.Row():
refresh_btn = gr.Button("πŸ”„ Refresh Data Preview", variant="secondary")
data_preview = gr.Markdown("No data recorded yet from iPad")
refresh_btn.click(
app.get_data_preview,
outputs=[data_preview]
)
with gr.Tab("☁️ Upload to S3"):
gr.Markdown("### Upload iPad Motion Data to S3")
with gr.Row():
bucket_name = gr.Textbox(
label="S3 Bucket Name",
placeholder="my-ipad-motion-bucket"
)
file_prefix = gr.Textbox(
label="File Prefix (optional)",
placeholder="ipad_sensors/",
value=""
)
upload_btn = gr.Button("☁️ Upload to S3", variant="primary")
upload_status = gr.Textbox(label="Upload Status", interactive=False)
upload_btn.click(
app.upload_to_s3,
inputs=[bucket_name, file_prefix],
outputs=[upload_status]
)
with gr.Tab("πŸ“‹ Instructions"):
gr.Markdown("""
## iPad Motion Sensor Setup
### 1. System Requirements
- **iPad**: Any modern iPad with built-in accelerometer
- **Browser**: Safari (recommended) or Chrome on iOS
- **iOS Version**: iOS 13+ for permission prompts
- **Network**: Internet connection for S3 upload
### 2. Motion Sensor Access
The app uses the **Device Motion API** which provides:
- **Acceleration**: Device movement in 3D space
- **Real-time data**: Up to 60Hz sample rate
- **Cross-platform**: Works on any modern iPad
### 3. Data Collection Tips
- **Secure grip**: Hold your iPad firmly while recording
- **Varied motions**: Try tilting, rotating, and gentle shaking
- **Stay in Safari**: Don't switch apps during recording
- **Grant permissions**: Allow motion access when prompted
### 4. Understanding iPad Accelerometer Data
- **X-axis**: Left (-) to Right (+) when holding in landscape
- **Y-axis**: Bottom (-) to Top (+) of the screen
- **Z-axis**: Back (-) to Front (+) of the device
- **Units**: Measured in m/sΒ² (meters per second squared)
- **Gravity effect**: ~9.8 m/sΒ² when stationary
### 5. Privacy & Security
- **Motion data**: Only collected when recording is active
- **Local processing**: Data stays in your browser until upload
- **AWS S3**: Secure cloud storage with your credentials
- **No tracking**: App doesn't store personal information
### 6. Troubleshooting
**"Motion permission not prompted":**
- **iOS 13+**: The permission prompt only appears when triggered by user interaction
- **Solution 1**: Use the "Request Motion Permission" button in Setup tab
- **Solution 2**: Go to iPad Settings β†’ Safari β†’ Privacy & Security β†’ Enable "Motion & Orientation Access"
- **Solution 3**: Try the "Test Motion Access" button in the recording section
- **Important**: Permission prompts only work on HTTPS websites or localhost
**"Permission button doesn't work":**
- Clear Safari cache and cookies for this site
- Make sure you're using Safari (not Chrome or other browsers)
- Check that the website is served over HTTPS
- Try refreshing the page and clicking the button again
**"Motion sensors not supported":**
- Use Safari browser instead of Chrome
- Check iPad model (very old iPads might not be supported)
- Restart Safari and try again
**"Permission denied":**
- Go to Safari Settings > Privacy & Security
- Enable Motion & Orientation Access
- Refresh the page and try again
**"No data collected":**
- Make sure to move the iPad during recording
- Check that Safari is the active app
- Try longer recording duration (15+ seconds)
### 7. Data Format
Each uploaded JSON file contains:
```json
{
"device_type": "iPad",
"device_id": "ipad_timestamp",
"recording_info": {
"start_time": "2025-01-01T12:00:00Z",
"sample_count": 300,
"data_source": "device_motion_api"
},
"data": [
{
"timestamp": "2025-01-01T12:00:00.123Z",
"x": 0.123456,
"y": -0.654321,
"z": 9.234567,
"device_id": "ipad_1234567890",
"user_agent": "iPad Safari info",
"orientation": "landscape-primary"
}
]
}
```
### 8. Use Cases
- **Gesture recognition**: Train ML models on iPad movements
- **Activity tracking**: Monitor device usage patterns
- **Game development**: Capture motion for game controls
- **Research**: Study human-computer interaction
- **Accessibility**: Develop motion-based interfaces
""")
if __name__ == "__main__":
demo.launch(
share=True,
server_name="0.0.0.0",
server_port=7860,
show_api=False,
favicon_path=None
)