Hubungkan Flutter app ke ESP32 menggunakan kabel USB OTG. Kontrol LED dari smartphone!
USB OTG (On-The-Go) memungkinkan smartphone Android bertindak sebagai host USB, sehingga bisa berkomunikasi langsung dengan ESP32 via kabel.
Koneksi: Android (USB-C) → OTG Adapter → USB Cable → ESP32
Terminalflutter create usb_led_control
cd usb_led_control
pubspec.yaml:YAML — pubspec.yamldependencies:
flutter:
sdk: flutter
usb_serial: ^0.5.1
android/app/src/main/AndroidManifest.xml:XML — AndroidManifest.xml<manifest ...>
<!-- USB Permission -->
<uses-feature android:name="android.hardware.usb.host" />
<application ...>
<activity ...>
<intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter>
<meta-data
android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
android:resource="@xml/device_filter" />
</activity>
</application>
</manifest>
android/app/src/main/res/xml/device_filter.xml:XML — device_filter.xml<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- CP2102 -->
<usb-device vendor-id="0x10C4" product-id="0xEA60" />
<!-- CH340 -->
<usb-device vendor-id="0x1A86" product-id="0x7523" />
</resources>
Upload kode ini ke ESP32. Ia akan mendengarkan perintah JSON via Serial dan mengontrol LED.
C++ (Arduino)#include <ArduinoJson.h>
#define LED_PIN 2
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
}
void loop() {
if (Serial.available()) {
String input = Serial.readStringUntil('\n');
input.trim();
JsonDocument doc;
DeserializationError err = deserializeJson(doc, input);
if (!err) {
const char* cmd = doc["command"];
if (strcmp(cmd, "ON") == 0) {
digitalWrite(LED_PIN, HIGH);
Serial.println("{\"status\":\"OK\",\"led\":\"ON\"}");
}
else if (strcmp(cmd, "OFF") == 0) {
digitalWrite(LED_PIN, LOW);
Serial.println("{\"status\":\"OK\",\"led\":\"OFF\"}");
}
else if (strcmp(cmd, "STATUS") == 0) {
String state = digitalRead(LED_PIN) ? "ON" : "OFF";
Serial.println("{\"status\":\"OK\",\"led\":\"" + state + "\"}");
}
}
}
}
Aplikasi Flutter yang menghubungkan ke ESP32 via USB dan mengirim perintah ON/OFF.
Dart — lib/main.dartimport 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:usb_serial/usb_serial.dart';
import 'package:usb_serial/transaction.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: const Color(0xFF0F172A),
),
home: const UsbControlPage(),
);
}
}
class UsbControlPage extends StatefulWidget {
const UsbControlPage({super.key});
@override
State<UsbControlPage> createState() => _UsbControlPageState();
}
class _UsbControlPageState extends State<UsbControlPage> {
UsbPort? _port;
String _status = "Disconnected";
bool _isConnected = false;
bool _isLedOn = false;
String _lastResponse = "";
@override
void initState() {
super.initState();
_connectToDevice();
}
Future<void> _connectToDevice() async {
List<UsbDevice> devices = await UsbSerial.listDevices();
if (devices.isEmpty) {
setState(() => _status = "No USB device found");
return;
}
UsbPort? port = await devices[0].create();
if (port == null) return;
bool opened = await port.open();
if (!opened) {
setState(() => _status = "Failed to open port");
return;
}
await port.setDTR(true);
await port.setRTS(true);
await port.setPortParameters(
115200,
UsbPort.DATABITS_8,
UsbPort.STOPBITS_1,
UsbPort.PARITY_NONE,
);
// Listen for data from ESP32
port.inputStream?.listen((Uint8List data) {
String response = String.fromCharCodes(data).trim();
if (response.isNotEmpty) {
setState(() => _lastResponse = response);
_parseResponse(response);
}
});
setState(() {
_port = port;
_isConnected = true;
_status = "Connected to ${devices[0].productName}";
});
}
void _parseResponse(String response) {
try {
Map<String, dynamic> json = jsonDecode(response);
if (json['led'] == 'ON') {
setState(() => _isLedOn = true);
} else if (json['led'] == 'OFF') {
setState(() => _isLedOn = false);
}
} catch (_) {}
}
void _sendCommand(String command) {
if (_port == null) return;
String json = jsonEncode({"command": command});
_port!.write(Uint8List.fromList("$json\n".codeUnits));
}
@override
void dispose() {
_port?.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('USB LED Controller'),
backgroundColor: const Color(0xFF1E293B),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Connection status
Container(
padding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: _isConnected
? Colors.green.withAlpha(50)
: Colors.red.withAlpha(50),
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: _isConnected
? Colors.green
: Colors.red),
),
child: Text(_status,
style: TextStyle(
color: _isConnected
? Colors.greenAccent
: Colors.redAccent)),
),
const SizedBox(height: 40),
// LED icon
Icon(Icons.lightbulb,
size: 120,
color: _isLedOn ? Colors.amber : Colors.grey[700]),
const SizedBox(height: 20),
Text(_isLedOn ? 'LED: ON' : 'LED: OFF',
style: TextStyle(fontSize: 28,
fontWeight: FontWeight.bold,
color: _isLedOn
? Colors.greenAccent
: Colors.redAccent)),
const SizedBox(height: 40),
// Buttons
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: _isConnected
? () => _sendCommand('ON') : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.greenAccent,
padding: const EdgeInsets.symmetric(
horizontal: 32, vertical: 16)),
child: const Text('ON',
style: TextStyle(color: Colors.black,
fontSize: 18,
fontWeight: FontWeight.bold)),
),
const SizedBox(width: 20),
ElevatedButton(
onPressed: _isConnected
? () => _sendCommand('OFF') : null,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.redAccent,
padding: const EdgeInsets.symmetric(
horizontal: 32, vertical: 16)),
child: const Text('OFF',
style: TextStyle(color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold)),
),
],
),
const SizedBox(height: 30),
// Response
Text('Response: $_lastResponse',
style: const TextStyle(
color: Colors.grey, fontSize: 12)),
],
),
),
);
}
}
Full flow: Flutter App → USB → ESP32 → LED ON → Response → UI Update