Connect to Arduino with Flutter via OTG Cable

590 views Asked by At

I'm currently trying to develop a very simple system: send a message to an Arduino when a button is pressed (this message will open a gate). The thing is I still didn't figured out how to connect an Arduino to an Android smartphone.

The code I'm using is as follows (it's almost identical to the example code, the only difference will be mentioned below):

import 'dart:async';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:usb_serial/transaction.dart';
import 'package:usb_serial/usb_serial.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  UsbPort? _port;
  String _status = "Idle";
  List<Widget> _ports = [];
  final List<Widget> _serialData = [];

  StreamSubscription<String>? _subscription;
  Transaction<String>? _transaction;
  UsbDevice? _device;

  final TextEditingController _textController = TextEditingController();

  Future<bool> _connectTo(device) async {
    _serialData.clear();

    if (_subscription != null) {
      _subscription!.cancel();
      _subscription = null;
    }

    if (_transaction != null) {
      _transaction!.dispose();
      _transaction = null;
    }

    if (_port != null) {
      _port!.close();
      _port = null;
    }

    if (device == null) {
      _device = null;
      setState(() {
        _status = "Disconnected";
      });
      return true;
    }

    _port = await device.create();
    if (await (_port!.open()) != true) {
      setState(() {
        _status = "Failed to open port";
      });
      return false;
    }
    _device = device;

    await _port!.setDTR(true);
    await _port!.setRTS(true);
    await _port!.setPortParameters(115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE);

    _transaction = Transaction.stringTerminated(_port!.inputStream as Stream<Uint8List>, Uint8List.fromList([13, 10]));

    _subscription = _transaction!.stream.listen((String line) {
      setState(() {
        _serialData.add(Text(line));
        if (_serialData.length > 20) {
          _serialData.removeAt(0);
        }
      });
    });

    setState(() {
      _status = "Connected";
    });
    return true;
  }

  void _getPorts() async {
    _ports = [];
    List<UsbDevice> devices = await UsbSerial.listDevices();
    if (!devices.contains(_device)) {
      _connectTo(null);
    }
    print(devices);

    devices.forEach((device) {
      _ports.add(ListTile(
          leading: Icon(Icons.usb),
          title: Text(device.productName!),
          trailing: ElevatedButton(
            child: Text(_device == device ? "Disconnect" : "Connect"),
            onPressed: () {
              _connectTo(_device == device ? null : device).then((res) {
                _getPorts();
              });
            },
          )));
    });

    setState(() {
      print(_ports);
    });
  }

  @override
  void initState() {
    super.initState();

    UsbSerial.usbEventStream!.listen((UsbEvent event) {
      _getPorts();
    });

    _getPorts();
  }

  @override
  void dispose() {
    super.dispose();
    _connectTo(null);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
      appBar: AppBar(
        title: const Text('USB Serial Plugin example app'),
      ),
      body: Center(
          child: Column(children: <Widget>[
        Text(_ports.length > 0 ? "Available Serial Ports" : "No serial devices available", style: Theme.of(context).textTheme.headline6),
        ..._ports,
        Text('Status: $_status\n'),
        Text('info: ${_port.toString()}\n'),
        ListTile(
          title: TextField(
            controller: _textController,
            decoration: const InputDecoration(
              border: OutlineInputBorder(),
              labelText: 'Text To Send',
            ),
          ),
          trailing: ElevatedButton(
            onPressed: _port == null
                ? null
                : () async {
                    if (_port == null) {
                      return;
                    }
                    String data = _textController.text + "\r\n";
                    await _port!.write(Uint8List.fromList(data.codeUnits));
                    _textController.text = "";
                  },
            child: const Text("Send"),
          ),
        ),
        Text("Result Data", style: Theme.of(context).textTheme.headline6),
        ..._serialData,
      ])),
    ));
  }
}

I'm using the usb_serial library and the example code on my app, but my smartphone doesn't really reconizes Arduino. Actually, it reconizes that something is plugged in, but it doesn't specify what has been plugged.

I've already removed the subtitle line, as suggested by this and some other answers.

Also, I've tried to add the line below on device_filter.xml, but it still doesn't work:

<usb-device vendor-id="2341" product-id="0043" />   

Both ids i got from lsusb, as it's possible to see below:

artur@artur-Vostro-3710:~$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 048: ID 1a2c:6004 China Resource Semico Co., Ltd USB Keyboard
Bus 001 Device 051: ID 413c:250e Dell Computer Corp. Dell Laser Mouse MS3220
Bus 001 Device 004: ID 8087:0032 Intel Corp. AX210 Bluetooth
Bus 001 Device 071: ID 2341:0043 Arduino SA Uno R3 (CDC ACM)
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

One thing to notice is that I've already downloaded USB OTG Checker app and my Android is compatible, also, I've downloaded the USB Serial Console and it works perfectly with my Arduino (It's a java code).

1

There are 1 answers

0
Kunal Kalwar On

This is a simple implementation of flutter app writing to a usb port, and the arduino returning the same data as a response->

This is the Arduino part->

void setup() {
  Serial.begin(115200);
  Serial.println("Serial Comm Via USB UART");
  Serial.flush();
}

void loop() {
  while(Serial.available() > 0){
    uint8_t byteFromSerial = Serial.read();
    uint8_t buff[100] = {byteFromSerial};
    String str = (char*)buff;
    Serial.print(str);
  }
}

And in flutter ->

In AndroidManifest.xml add this->

<uses-permission android:name="android.permission.USB_PERMISSION" />
<uses-feature android:name="android.hardware.usb.host" />

In pubspec.yaml file add this->

usb_serial: ^0.5.1
permission_handler: ^11.0.1

In main.dart file->

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:usb_serial/transaction.dart';
import 'package:usb_serial/usb_serial.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: USBSerialExample(),
    );
  }
}

class USBSerialExample extends StatefulWidget {
  @override
  _USBSerialExampleState createState() => _USBSerialExampleState();
}

class _USBSerialExampleState extends State<USBSerialExample> {

  final transformer = Utf8Decoder(); // Create a UTF-8 decoder
  String _status = "Idle";
  List<Widget> _ports = [];
  List<Widget> _serialData = [];
  StreamSubscription<String>? _subscription;
  Transaction<String>? _transaction;
  int _deviceId=0;
  TextEditingController _textController = TextEditingController();

  List<UsbDevice> _devices = [];
  UsbPort? _port;
  StreamSubscription<Uint8List>? subscription;

  String sendingData="";
  String receivingData="";

  @override
  void initState() {
    super.initState();

    UsbSerial.usbEventStream?.listen((UsbEvent event) {
      _getPorts();
    });

    _getPorts();
  }

  Future<bool> _connectTo(device) async {
    _serialData.clear();

    if (_subscription != null) {
      _subscription?.cancel();
      _subscription = null;
    }

    if (_transaction != null) {
      _transaction?.dispose();
      _transaction = null;
    }

    if (_port != null) {
      _port?.close();
      _port = null;
    }

    if (device == null) {
      _deviceId = -1;
      setState(() {
        _status = "Disconnected";
      });
      return true;
    }

    _port = await device.create();
    if (!await _port!.open()) {
      setState(() {
        _status = "Failed to open port";
      });
      return false;
    }

    _deviceId = device.deviceId;
    await _port?.setDTR(true);
    await _port?.setRTS(true);
    await _port?.setPortParameters(
        115200, UsbPort.DATABITS_8, UsbPort.STOPBITS_1, UsbPort.PARITY_NONE);

    _transaction = Transaction.stringTerminated(
        _port!.inputStream!, Uint8List.fromList([13, 10]));

    _subscription = _transaction?.stream.listen((String line) {
      setState(() {
        // Handle incoming responses
        _handleResponse(line);
        _serialData.add(Text(line));
        if (_serialData.length > 20) {
          _serialData.removeAt(0);
        }
      });
    });

    setState(() {
      _status = "Connected";
    });
    return true;
  }

  void _handleResponse(String response) {
    try {
        // Do something with the extracted data
        print('_handleResponse response: $response');

    } catch (e) {
      print('Error parsing JSON response-> $e');
    }
  }

  void _getPorts() async {
    _ports = [];
    List<UsbDevice> devices = await UsbSerial.listDevices();
    print(devices);

    devices.forEach((device) {
      _ports.add(ListTile(
          title: Text(device.productName!),
          trailing: ElevatedButton(
            child:
            Text(_deviceId == device.deviceId ? "Disconnect" : "Connect"),
            onPressed: () {
              _connectTo(_deviceId == device.deviceId ? null : device)
                  .then((res) {
                _getPorts();
              });
            },
          )));
    });

    setState(() {
      print(_ports);
    });
  }


  @override
  void dispose() {
    subscription?.cancel();
    _port?.close();
    super.dispose();
  }

  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        debugShowCheckedModeBanner: false,
        home: Scaffold(
          appBar: AppBar(
            title: const Text('SmartRack Usb serial Testing'),
          ),
          body: Center(
              child: Column(children: <Widget>[
                ..._ports,
                ListTile(
                  title: TextField(
                    controller: _textController,
                    decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Send Text',
                    ),
                  ),
                  trailing: ElevatedButton(
                    child: Text("Send"),
                    onPressed: _port == null
                        ? null
                        : () async {
                      if (_port == null) {
                        return;
                      }

                      if (_port == null) {
                        return;
                      }
                      String data = _textController.text + "\r\n";
                      await _port?.write(Uint8List.fromList(data.codeUnits));
                      _textController.text = "";

                    },
                  ),
                ),
                Text("Response from usb serial"),
                ..._serialData,
              ])),
        ));
  }
}