Low Latency Control with Flutter and Nvidia Jetson
This guide walks you through building a low-latency control system where a Flutter app sends commands to an Nvidia Jetson device over a local network. You’ll set up a UDP-based communication channel, handle sensor feedback, and optimize both sides for minimal delay. By the end, you’ll have a working prototype that can control motors, robots, or other hardware with sub-10ms latency.
What You’ll Need
- An Nvidia Jetson device (Nano, Xavier NX, Orin Nano, etc.) with JetPack 5.0 or later
- A development machine with Flutter SDK installed (Linux, macOS, or Windows)
- Android or iOS device for running the Flutter app (or use a desktop target)
- Both devices connected to the same local network (preferably a dedicated router or switch)
- Basic familiarity with Dart, Python, and Linux command line
- Optional: GPIO pins, motor driver, or other hardware to control
Step-by-Step
- Set up the Jetson environment
Flash JetPack onto your Jetson using the NVIDIA SDK Manager. Ensure you have a stable internet connection for downloading packages. Once booted, update the system:
sudo apt update && sudo apt upgrade -yInstall Python3 and pip if not already present:
sudo apt install python3-pip - Create a UDP server on the Jetson
UDP is preferred over TCP for low latency because it avoids connection setup and retransmission delays. Create a Python script named
udp_server.py:import socket import time UDP_IP = "0.0.0.0" # Listen on all interfaces UDP_PORT = 5005 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((UDP_IP, UDP_PORT)) sock.setblocking(False) # Non-blocking to avoid delays print("UDP server listening on port", UDP_PORT) while True: try: data, addr = sock.recvfrom(1024) if data: # Process command (e.g., decode as float or string) print("Received:", data) # Add your hardware control logic here # For example, set GPIO pins or send to motor driver except socket.error: pass # No data, continue loop # Add a small sleep to yield CPU but keep responsiveness time.sleep(0.001) # 1msRun the server with
python3 udp_server.py. For production, consider using a real-time thread or a dedicated core. - Optimize Jetson for low latency
To reduce jitter, assign the server process to a dedicated CPU core and set a high priority:
sudo taskset -c 4 python3 udp_server.pyYou can also disable CPU frequency scaling for consistent performance:
sudo cpufreq-set -g performanceIf using GPIO, use the Jetson.GPIO library with proper pin configuration.
- Create a Flutter app
On your development machine, create a new Flutter project:
flutter create low_latency_control cd low_latency_controlAdd the
udppackage topubspec.yaml:dependencies: udp: ^2.0.1Run
flutter pub get. - Implement UDP client in Flutter
Replace the default
main.dartwith a simple UI that sends commands. Here’s a minimal example:import 'package:flutter/material.dart'; import 'package:udp/udp.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp(home: ControlPage()); } } class ControlPage extends StatefulWidget { @override _ControlPageState createState() => _ControlPageState(); } class _ControlPageState extends State{ UDP? _sender; String _status = "Not connected"; @override void initState() { super.initState(); _setupUDP(); } Future _setupUDP() async { _sender = await UDP.bind(Endpoint.any(port: 0)); setState(() => _status = "Ready"); } void _sendCommand(String command) async { if (_sender == null) return; final data = Uint8List.fromList(command.codeUnits); _sender!.send(data, Endpoint.unicast(InternetAddress('192.168.1.100'), port: 5005)); // Update UI immediately for responsive feel setState(() => _status = "Sent: $command"); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Control')), body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text(_status), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ ElevatedButton( onPressed: () => _sendCommand('left'), child: Text('Left'), ), ElevatedButton( onPressed: () => _sendCommand('stop'), child: Text('Stop'), ), ElevatedButton( onPressed: () => _sendCommand('right'), child: Text('Right'), ), ], ), ], ), ), ); } } Replace the IP address with your Jetson’s IP. Run the app on your device.
- Test the connection
Run the Flutter app and press a button. You should see the command printed on the Jetson’s console. Measure round-trip time using a timestamp in the app and on the server.
- Add feedback (optional)
To send sensor data back, create a separate UDP socket on the Jetson that sends data to the Flutter app’s IP and port. In Flutter, listen for incoming packets using a
Stream. This allows you to display real-time sensor values. - Optimize Flutter for low latency
Use
setStatesparingly and avoid heavy widgets in the control path. Consider usingRawKeyboardListenerfor keyboard input if you’re on desktop. For mobile, useGestureDetectorwithonPanDownandonPanEndto send continuous commands.
Common Mistakes
- Using TCP instead of UDP – TCP’s reliability comes at the cost of latency due to acknowledgments and retransmissions. For control, UDP is better even if a few packets are lost.
- Blocking socket operations – A blocking
recvfromwill stall the server until data arrives. Use non-blocking sockets or a dedicated thread. - Not binding to the correct IP – If the server binds to
127.0.0.1, it won’t receive external packets. Use0.0.0.0. - Ignoring network latency – Wi-Fi adds jitter. For critical control, use Ethernet or a dedicated access point.
- Overloading the UI thread – Sending packets directly in the UI event handler can cause frame drops. Use an isolate or a queue if you send many commands per second.
Tips
- Use a binary protocol – Instead of strings, send a struct with a command byte and parameters. This reduces packet size and parsing time.
- Pre-allocate buffers – On both sides, reuse buffers to avoid garbage collection pauses.
- Set socket options – On Linux, set
SO_REUSEADDRand consider increasing the send buffer size. - Test on a wired connection first – This isolates network issues from your code.
- Use a real-time kernel – For extreme low latency, consider the NVIDIA Jetson Real-Time Kernel, but it’s not necessary for most projects.
Troubleshooting
- No packets received on Jetson – Check firewall:
sudo ufw disableor allow port 5005. Also verify the IP address and that both devices are on the same subnet. - Flutter app crashes when sending – Ensure you’ve added the
udppackage and have internet permission on Android (add<uses-permission android:name="android.permission.INTERNET"/>to AndroidManifest.xml). - High latency despite UDP – Check for Wi-Fi interference, or use
pingto measure baseline latency. Also, ensure the server loop isn’t doing heavy work (like printing) in the critical path. - Packet loss – UDP doesn’t guarantee delivery. Add a sequence number and ignore out-of-order packets, or implement a simple acknowledgment for critical commands.
- Jetson not receiving from Flutter but works with other tools – Make sure the Flutter app is using the correct network interface. On Android, ensure the device isn’t on a different network (e.g., guest Wi-Fi).
Frequently Asked Questions
Can I use TCP instead of UDP for control?
Yes, but TCP adds latency due to connection management and retransmission. For most control applications, UDP with a simple sequence number is preferred.
What is the typical latency achievable?
On a wired Ethernet connection, you can expect 1-5ms round-trip. Over Wi-Fi, it varies but often 5-20ms. Optimizing both sides can get you close to the network’s physical limit.
How do I send sensor data back to the Flutter app?
Create a second UDP socket on the Jetson that sends data to the Flutter app’s IP and port. In Flutter, bind a UDP socket to a fixed port and listen for incoming packets.
Is it necessary to use a real-time kernel?
No, for most hobby and research projects the standard kernel is fine. Real-time kernels are needed for microsecond-level precision or when jitter is critical.
Can I use Flutter for desktop to control the Jetson?
Yes, Flutter supports Windows, macOS, and Linux. The same UDP code works, but you’ll need to handle platform-specific permissions.
What if I need to send high-frequency commands (e.g., 100Hz)?
That’s fine with UDP. Just ensure your Flutter app doesn’t create a new socket every time; reuse the same socket. Also, consider batching multiple commands into one packet.
Related
Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.