tahamajs's picture
download
raw
23.3 kB
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta charset="utf-8">
<title>README</title>
<style>
body { font-family: Tahoma, 'Segoe UI', sans-serif; line-height: 2.0; font-size: 11pt; color: #0f172a; padding: 30px; background-color: #ffffff; }
h1 { color: #1e1b4b; border-bottom: 3px solid #4f46e5; padding-bottom: 10px; font-size: 22pt; margin-top: 30px; }
h2 { color: #1e293b; background-color: #f1f5f9; border-right: 5px solid #3b82f6; padding: 8px 12px; font-size: 16pt; margin-top: 25px; }
h3 { color: #2563eb; font-size: 13pt; margin-top: 20px; }
p { text-align: justify; margin-bottom: 15px; }
pre { background-color: #0f172a; color: #f8fafc; padding: 15px; border-radius: 6px; font-family: Courier, monospace; font-size: 10pt; overflow-x: auto; }
code { background-color: #f1f5f9; color: #0f172a; padding: 2px 5px; border-radius: 4px; font-family: Courier, monospace; }
table { border-collapse: collapse; width: 100%; margin: 20px 0; }
th, td { border: 1px solid #cbd5e1; padding: 10px; text-align: right; }
th { background-color: #f1f5f9; }
ul, ol { padding-right: 25px; }
</style>
</head>
<body>
<div class="container">
<h1>📱 Air Mouse Pro: Cyber-Physical Remote Control System</h1>
<p>Welcome to the definitive repository for the <strong>Air Mouse Pro</strong> system, a state-of-the-art Cyber-Physical System (CPS) designed to turn a standard Android smartphone into a low-latency, high-precision, wireless remote pointer, touchpad simulator, gaming controller, and system command center. </p>
<p>Developed as part of the Cyber-Physical &amp; Embedded Systems curriculum, this project demonstrates advanced concepts in real-time sensor processing, sensor fusion, wireless communication protocols, noise filtration, and system-level performance profiling.</p>
<hr />
<h2>🏗️ System Architecture</h2>
<p>The Air Mouse system is divided into two main components:
1. <strong>Android Client (<a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android">code/android</a>)</strong>: Written in Kotlin using Jetpack Compose (Material 3), Dagger Hilt for dependency injection, Room DB for local storage, and standard Android Sensors APIs. It captures raw IMU signals, runs a manual implementation of the Madgwick AHRS algorithm, detects gestures, and transmits processed commands.
2. <strong>Go PC Server (<a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/pc/airmouse_go_new">code/pc/airmouse_go_new</a>)</strong>: A concurrent, multi-protocol Go executable that acts as the coordinator. It manages connection channels (TCP, WebSocket, and UDP), processes incoming command streams (applying Kalman filters, tremor filters, and trajectory predictions), and replicates mouse/keyboard events on the host OS via native robotgo/PyAutoGUI bindings.</p>
<pre><code class="language-mermaid">graph TD
%% Android Client Pipeline
subgraph Android Client (Codebase: code/android)
A[Android IMU Sensors] --&gt;|Raw Accel / Gyro / Mag| B[3-Axis Calibration Manager]
B --&gt;|Corrected IMU Data| C[Madgwick AHRS Sensor Fusion]
C --&gt;|Euler Angles / Quaternions| D[Gesture Detector &amp; Sensitivity Mapper]
D --&gt;|Move / Click / Scroll Events| E[Protocol Serializer]
E --&gt;|UDP / WebSocket / TCP| F[Connection Manager]
end
%% Wireless Network
subgraph Network Transport (LAN Wi-Fi / USB)
F --&gt;|High-Frequency Stream: Port 9093| G((UDP Input Socket))
F --&gt;|Reliable Commands: Port 9091| H((WebSocket Socket))
I((UDP Broadcast: Port 9092)) &lt;.-&gt;|mDNS Auto-Discovery| F
end
%% PC Desktop Server
subgraph Go PC Server (Codebase: code/pc/airmouse_go_new)
G --&gt; J[Server Protocol Listeners]
H --&gt; J
J --&gt; K[Go Connection Hub &amp; Client Registry]
K --&gt; L[Adaptive Smoothing &amp; Tremor Filters]
L --&gt; M[Predictive Kalman Filter]
M --&gt; N[Host Controller API Translation]
N --&gt;|Native Events| O[PyAutoGUI / RobotGo API]
end
%% Host OS Desktop
subgraph Host OS Desktop
O --&gt; P[Cursor Translation &amp; Key Injector]
end
classDef android fill:#3DDC84,stroke:#333,stroke-width:2px,color:#000;
classDef server fill:#00ADD8,stroke:#333,stroke-width:2px,color:#fff;
classDef network fill:#FF9900,stroke:#333,stroke-width:2px,color:#000;
class A,B,C,D,E,F android;
class J,K,L,M,N,O server;
class G,H,I network;
</code></pre>
<hr />
<h2>🧮 Cyber-Physical &amp; Embedded Systems Concepts</h2>
<p>To achieve an experience comparable to commercial hardware, the project implements several key digital signal processing (DSP) and networking architectures.</p>
<h3>1. Motion Sensing &amp; IMU Kinematics</h3>
<p>The system leverages the mobile device's Inertial Measurement Unit (IMU) using three primary sensors:
* <strong>Accelerometer (<code>Sensor.TYPE_ACCELEROMETER</code>)</strong>: Measures proper acceleration ($\vec{a}$). While highly reliable for identifying the gravity vector ($\vec{g}$), it is prone to high-frequency noise from linear movements and hand tremors.
* <strong>Gyroscope (<code>Sensor.TYPE_GYROSCOPE</code>)</strong>: Measures angular velocity ($\vec{\omega}$). Integrating angular velocity over time provides highly responsive short-term orientation changes but suffers from cumulative drift over time due to low-frequency noise (bias).
* <strong>Magnetometer (<code>Sensor.TYPE_MAGNETIC_FIELD</code>)</strong>: Measures Earth's magnetic field ($\vec{B}$) to establish a geographic heading, acting as an absolute yaw reference. It is highly susceptible to hard-iron and soft-iron magnetic interference from nearby metals and electronics.</p>
<h3>2. Sensor Fusion (Madgwick AHRS)</h3>
<p>To resolve the limitations of individual sensors, orientation is tracked using the <strong>Madgwick AHRS (Attitude and Heading Reference System)</strong> algorithm, implemented in <a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/sensors/SensorFusion.kt">SensorFusion.kt</a>.
* <strong>Integration</strong>: The gyroscope readings are integrated to predict the orientation quaternion ($q$).
* <strong>Correction</strong>: A gradient-descent optimization calculates the error between the predicted orientation and the measurements from the accelerometer (for pitch/roll) and magnetometer (for yaw/heading).
* <strong>Fusing Gain ($\beta$)</strong>: The parameter $\beta$ (default <code>0.041</code>) controls the trade-off between responsive gyroscope integration and drift-correcting accelerometer/magnetometer references:</p>
<p>$$\beta = \sqrt{\frac{3}{4}} \cdot \tilde{\omega}_{\text{max}}$$</p>
<p>where $\tilde{\omega}_{\text{max}}$ represents the maximum gyroscope measurement error.</p>
<h3>3. Noise Filtration &amp; Smoothing</h3>
<ul>
<li><strong>Kalman Filtering (1D &amp; 2D)</strong>: Implemented in the Go server (<a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/pc/airmouse_go_new/internal/jitter/kalman1d.go">kalman2d.go</a>), it uses a linear quadratic estimation to model the velocity and acceleration of the mouse pointer. It acts as a predictive filter that minimizes latency by anticipating the next cursor coordinate.</li>
<li><strong>Tremor Filter</strong>: A low-pass moving-average filter running on the server that filters out high-frequency micro-shakes ($&gt;6\text{ Hz}$) typical of human hand tremors while preserving deliberate pointer trajectories.</li>
<li><strong>B-Spline Path Humanizer</strong>: Smooths out discrete grid coordinates into continuous, organic curves before translating them to the host screen, improving pointer control.</li>
</ul>
<h3>4. Low-Latency Wireless Communication</h3>
<p>A dual-channel protocol is used to balance latency and reliability:
* <strong>UDP Data Stream (Port 9093)</strong>: High-frequency pointer updates ($\approx 50\text{--}100\text{ Hz}$) are sent over connectionless, unreliable UDP. This avoids the handshake, congestion control, and TCP head-of-line blocking overhead, keeping local transmission latency below $1.5\text{ ms}$.
* <strong>WebSocket / TCP Channel (Ports 9090 &amp; 9091)</strong>: Critical operations (mouse clicks, scrolls, keyboard shortcuts, files) require guaranteed delivery. These are sent over WebSockets with an application-layer <strong>ACK-and-Retransmission</strong> protocol:
* Messages are assigned an incremental ID.
* The server replies with an <code>ack</code> packet.
* If the client does not receive an ACK within $500\text{ ms}$, the command is retransmitted up to $3$ times before logging a network fault.</p>
<hr />
<h2>🛠️ Calibration Mathematical Models</h2>
<p>To eliminate noise, sensor offsets, and soft/hard-iron distortions, the Android application features a calibration module:</p>
<h3>1. Gyroscope Bias Calibration</h3>
<p>Computes the mean angular velocity offsets while the device is kept flat and stationary over $100$ samples:</p>
<p>$$\vec{\omega}<em>{\text{bias}} = \frac{1}{N}\sum</em>{i=1}^{N} \vec{\omega}_{\text{raw}, i}$$</p>
<p>Future readings are corrected by subtraction:</p>
<p>$$\vec{\omega}<em>{\text{corrected}} = \vec{\omega}</em>{\text{raw}} - \vec{\omega}_{\text{bias}}$$</p>
<h3>2. Magnetometer Hard-Iron &amp; Soft-Iron Calibration</h3>
<p>During a figure-8 motion, the app captures $200$ samples to map the local magnetic field. It identifies the maximum and minimum values on each axis to compute hard-iron offsets and soft-iron scaling factors:</p>
<p>$$\vec{B}_{\text{offset}} = \frac{\max(\vec{B}) + \min(\vec{B})}{2}$$</p>
<p>$$\vec{B}<em>{\text{scale}} = \frac{\vec{B}</em>{\text{avg_range}}}{\max(\vec{B}) - \min(\vec{B})}$$</p>
<p>$$\vec{B}<em>{\text{corrected}} = (\vec{B}</em>{\text{raw}} - \vec{B}<em>{\text{offset}}) \odot \vec{B}</em>{\text{scale}}$$</p>
<p>where $\vec{B}_{\text{avg_range}}$ is the average coordinate span across the three axes.</p>
<h3>3. Accelerometer 6-Position Reference Calibration</h3>
<p>Aligns the device along the 6 orthogonal axes ($x+, x-, y+, y-, z+, z-$ facing the earth's gravity $g = 9.81\text{ m/s}^2$) to solve for scale and offset parameters:</p>
<p>$$\vec{a}<em>{\text{scale}} = \frac{\sum (\vec{a}</em>{\text{measured}} \cdot \vec{a}<em>{\text{expected}})}{\sum (\vec{a}</em>{\text{measured}}^2)}$$</p>
<p>$$\vec{a}<em>{\text{offset}} = \frac{\sum (\vec{a}</em>{\text{expected}} - \vec{a}<em>{\text{measured}} \cdot \vec{a}</em>{\text{scale}})}{N}$$</p>
<p>$$\vec{a}<em>{\text{corrected}} = \frac{\vec{a}</em>{\text{raw}} - \vec{a}<em>{\text{offset}}}{\vec{a}</em>{\text{scale}}}$$</p>
<hr />
<h2>🌟 Complete Features Breakdown</h2>
<table>
<thead>
<tr>
<th style="text-align: left;">Feature</th>
<th style="text-align: left;">Subsystem</th>
<th style="text-align: left;">Description</th>
<th style="text-align: left;">Code References</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><strong>Real-time Pointer</strong></td>
<td style="text-align: left;">Motion Engine</td>
<td style="text-align: left;">Uses the Madgwick AHRS output to map device pitch and roll changes to cursor coordinates, filtering out low-level noise via a customizable dead zone.</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/sensors/GestureDetector.kt">GestureDetector.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>3-Axis Calibration Center</strong></td>
<td style="text-align: left;">Local Pre-processing</td>
<td style="text-align: left;">A Wizard UI that guides users through gyroscope, accelerometer (6-position), and magnetometer calibration.</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/sensors/CalibrationHelper.kt">CalibrationHelper.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>Touchpad Simulator</strong></td>
<td style="text-align: left;">Touch Input</td>
<td style="text-align: left;">Converts mobile screen touches to pointer movement, supporting tap-to-click, double-tap, and two-finger scroll gestures.</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/presentation/ui/touchpad/TouchpadViewModel.kt">TouchpadViewModel.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>Custom Gestures Studio</strong></td>
<td style="text-align: left;">AI Classification</td>
<td style="text-align: left;">Records gesture paths and uses a Dynamic Time Warping (DTW) algorithm and particle filters to trigger custom keyboard macros (e.g. circle for "Browser Refresh").</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/sensors/EnhancedGestureDetector.kt">EnhancedGestureDetector.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>Voice Commands</strong></td>
<td style="text-align: left;">Natural Interface</td>
<td style="text-align: left;">Recognizes speech input (using Android's <code>SpeechRecognizer</code>) to execute keyboard shortcuts or media commands (e.g., "mute", "play").</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/ui/VoiceCommandFragment.kt">VoiceCommandFragment.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>File Transfer Queue</strong></td>
<td style="text-align: left;">Network Utility</td>
<td style="text-align: left;">A TCP socket queue that allows users to send files between their mobile device and PC by dragging and dropping.</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/files/FileTransferService.kt">FileTransferService.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>Screen Mirroring</strong></td>
<td style="text-align: left;">Video Streaming</td>
<td style="text-align: left;">Streams the PC desktop back to the phone screen using a JPEG frame sequence over UDP, validated by Start-Of-Image (<code>0xFF 0xD8</code>) headers.</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/mirroring/ScreenMirroringService.kt">ScreenMirroringService.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>Gaming Mode</strong></td>
<td style="text-align: left;">Specialized Input</td>
<td style="text-align: left;">Converts device steering (yaw/roll tilt) to keyboard inputs (<code>a</code>/<code>d</code>) for driving simulators, and gestures to key presses (<code>space</code>/<code>r</code>) for shooter controls.</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/gaming/GameProfilesManager.kt">GameProfilesManager.kt</a></td>
</tr>
<tr>
<td style="text-align: left;"><strong>Theme/Preferences Sync</strong></td>
<td style="text-align: left;">State Syncing</td>
<td style="text-align: left;">Syncs themes (20+ presets) and calibration parameters between the client and server using JSON profile configurations.</td>
<td style="text-align: left;"><a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/sync/DataSyncManager.kt">DataSyncManager.kt</a></td>
</tr>
</tbody>
</table>
<hr />
<h2>🔌 Port Architecture &amp; Discovery</h2>
<p>To avoid network conflicts with web services, the system runs on dedicated ports:</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Port</th>
<th style="text-align: left;">Protocol</th>
<th style="text-align: left;">Purpose</th>
<th style="text-align: left;">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><strong><code>9090</code></strong></td>
<td style="text-align: left;">TCP</td>
<td style="text-align: left;">File Queue &amp; Configuration Sync</td>
<td style="text-align: left;">Stateful TCP connection for high-throughput files and profiles.</td>
</tr>
<tr>
<td style="text-align: left;"><strong><code>9091</code></strong></td>
<td style="text-align: left;">WebSocket</td>
<td style="text-align: left;">Reliable Event Commands</td>
<td style="text-align: left;">Handles clicks, drags, scroll wheel ticks, and custom macros.</td>
</tr>
<tr>
<td style="text-align: left;"><strong><code>9092</code></strong></td>
<td style="text-align: left;">UDP</td>
<td style="text-align: left;">Auto-Discovery</td>
<td style="text-align: left;">Broadcast/Listening channel for instant client-server pairing.</td>
</tr>
<tr>
<td style="text-align: left;"><strong><code>9093</code></strong></td>
<td style="text-align: left;">UDP</td>
<td style="text-align: left;">High-Frequency Sensor Stream</td>
<td style="text-align: left;">Streams raw gyroscope coordinate outputs to minimize latency.</td>
</tr>
</tbody>
</table>
<h3>Auto-Discovery Protocol Flow</h3>
<pre><code>Android Client PC Go Server
| |
| ---- [UDP Broadcast: 9092] &quot;AIRMOUSE_DISCOVER&quot; --------------&gt; |
| | (Receive broadcast)
| | (Read host configuration)
| &lt;--- [UDP Unicast: Client Port] &quot;AIRMOUSE_SERVER:9093:Name:3.0&quot; |
| |
(Parse payload info)
(Save host IP/Port)
| |
| ---- [WebSocket: 9091] Connect / Hello -----------------------&gt; |
</code></pre>
<hr />
<h2>🚀 Installation &amp; Setup Guides</h2>
<h3>1. PC Go Server Setup</h3>
<p>Ensure you have <a href="https://go.dev/">Go 1.23+</a> installed.</p>
<pre><code class="language-bash"># Navigate to the server directory
cd code/pc/airmouse_go_new
# Download and verify dependencies
make deps
# Build the executable for your current OS
make build
# Run the server
./airmouse-server
</code></pre>
<blockquote>
<p>[!NOTE]
If you have compiler problems with Fyne (GUI toolkit) dependencies, you can compile a non-GUI console version by running:
<code>go build -tags noai -o airmouse-server ./cmd/airmouse-server</code></p>
</blockquote>
<hr />
<h3>2. Operating System Permissions</h3>
<p>To translate network commands to cursor movement, you must grant the server system permissions:</p>
<h4>macOS</h4>
<p>Since macOS restricts virtual inputs, you must add the terminal program (e.g., Terminal, iTerm2, or VS Code) or the compiled <code>airmouse-server</code> binary to the Accessibility list:
1. Open <strong>System Settings</strong> -&gt; <strong>Privacy &amp; Security</strong> -&gt; <strong>Accessibility</strong>.
2. Click the <code>+</code> button and add your terminal application or the compiled <code>airmouse-server</code> binary.
3. Enable the checkbox.</p>
<h4>Linux</h4>
<p>On Linux, the server requires permissions to write to the kernel user input interface (<code>/dev/uinput</code>) or utilize X11 utilities:</p>
<pre><code class="language-bash"># Grant access to uinput
sudo usermod -aG input $USER
sudo udevadm trigger
# If uinput is not available, install xdotool for X11 fallback:
sudo apt-get install xdotool
</code></pre>
<h4>Windows</h4>
<p>Run the command prompt or powershell as <strong>Administrator</strong> before executing the server to allow the insertion of simulated keyboard events into privileged applications.</p>
<hr />
<h3>3. Android Client Compilation &amp; Run</h3>
<p>Ensure you have <strong>Android Studio (Hedgehog or newer)</strong> and <strong>JDK 17</strong> installed.</p>
<pre><code class="language-bash"># Navigate to the Android directory
cd code/android
# Clean the workspace
./gradlew clean
# Build the debug APK
./gradlew assembleDebug
# Install on a connected physical device or emulator via ADB
adb install -r app/build/outputs/apk/debug/app-debug.apk
</code></pre>
<hr />
<h2>🧪 Test Suites</h2>
<h3>1. Go PC Server Test Suite</h3>
<p>The Go server includes unit tests for sensor fusion, jitter buffers, and protocol parsing.</p>
<pre><code class="language-bash"># Run all unit tests
make test
# Run tests with race condition detection
go test -v -race -timeout 30s ./...
# Run tests with a HTML coverage report
make test-coverage
# Run benchmarks for sensor processing loops
make bench
</code></pre>
<h3>2. Android Client Test Suite</h3>
<p>The Android codebase contains unit tests (for use cases and ViewModels) and instrumented Compose tests.</p>
<pre><code class="language-bash"># Run unit tests on host JVM
./gradlew testDebugUnitTest
# Run instrumented UI and integration tests (requires connected device/emulator)
./gradlew connectedAndroidTest
</code></pre>
<hr />
<h2>📊 Perfetto Profiling &amp; Analysis</h2>
<p>To analyze the performance of the system and measure sensor-to-cursor latency, the project integrates with <strong>Google Perfetto</strong>.</p>
<h3>1. Custom Tracepoints</h3>
<p>Trace points are inserted in <a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/android/app/src/main/java/com/airmouse/sensors/SensorService.kt">SensorService.kt</a> to monitor the processing pipeline:
* <code>AirMouseApp.Sensors.sensor_read</code>: Measures time spent reading raw sensors.
* <code>AirMouseApp.Filter.complementary</code>: Tracks the Madgwick fusion execution.
* <code>AirMouseApp.Filter.compute_delta</code>: Tracks gesture calculations.
* <code>AirMouseApp.Communication.send</code>: Measures transmission time.</p>
<h3>2. Recording a Performance Trace</h3>
<p>Run the trace recorder script using the provided <a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/config.pbtx">config.pbtx</a>:</p>
<pre><code class="language-bash"># Record a 15-second trace from your device
python3 &quot;Files/12- record_android_trace&quot; -c &quot;config.pbtx&quot; -o trace_file.perfetto-trace -t 15s
</code></pre>
<h3>3. Running the Python Analyzer</h3>
<p>Use the custom SQL script <a href="file:///Users/tahamajs/Documents/uni/CPS/Files/ComputerAssignments/CA2/code/pc/perfetto_analyzer.py">perfetto_analyzer.py</a> to query the trace database and extract performance metrics:</p>
<pre><code class="language-bash"># Analyze trace file
python3 code/pc/perfetto_analyzer.py trace_file.perfetto-trace
</code></pre>
<p>The analyzer script evaluates the recorded trace to output:
* Average sensor callback processing times.
* CPU thread scheduling and waiting overheads.
* Madgwick filter execution times.
* End-to-end latency from hardware register readings to network transmissions.</p>
<hr />
<p><em>Developed by the Cyber-Physical &amp; Embedded Systems Lab. For issues or feature requests, contact the project maintainers.</em></p>
</div>
</body>
</html>

Xet Storage Details

Size:
23.3 kB
·
Xet hash:
4b9a6908574ba71ba8c0b095cb920fe81337b6f951649f8e62c5a439a5f85400

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.