2 may
2021

Using android.hardware.usb in Flutter application

I have tried using plugins available:

flutter_android

This includes:

Sensor

SensorEvent

SensorEventListener

SensorManager

usb_serial

So i need to talk to usb devices however the plugin usb_serial does not meet my needs since i need to use more than the package provides.

Basically i either need to create my own plugin or i need to find a way to expose the native android.hardware.usb to flutter.

Need help i don't know what is best or how to do either.

COMENTARIOS

Douglas Nel

This was a long time ago but will try to help as best as possible

Since I was not able to find anything that met my needs I needed to create my own plugin using these

import com.ftdi.j2xx.D2xxManager;
import com.ftdi.j2xx.FT_Device;

My MainActivity look like this

import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.CountDownTimer;
import android.util.Log;
import android.widget.Toast;

import androidx.annotation.NonNull;

import com.ftdi.j2xx.D2xxManager;
import com.ftdi.j2xx.FT_Device;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.EventChannel;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;

public class MainActivity extends FlutterActivity {
private static final String TAG = "D2XX";
private static final String CHANNEL = "bridge";
private static final String EVENT_CHANNEL = "event";
private static final String INPUT_EVENT_CHANNEL = "input";
private D2xxManager m_deviceManager = null;
private FT_Device m_connectedDevice;
private CONTROLLER_ENUMS m_CONTROLLER_STATUS = CONTROLLER_ENUMS.Closed;
private int m_iDeviceCount = 0;
private List<D2xxManager.FtDeviceInfoListNode> mDeviceInfoListNode;
private CountDownTimer m_updateTimer;
private final String m_initialBitMask = "00111100";
private boolean m_input1State = false;
private boolean m_input2State = false;
private boolean m_output1State = false;
private boolean m_output2State = false;
private static boolean bRegisterBroadcast = true;
private long timeLeftInMilliseconds;
private boolean m_MountedState = false;
boolean isRunning = false;

private enum CONTROLLER_ENUMS {
    OK,
    Opened,
    Closed,
    Failed,
}

@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
    GeneratedPluginRegistrant.registerWith(flutterEngine);

    new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
            .setMethodCallHandler((call, result) -> {
                String strCallName = call.method.toUpperCase();

                switch (strCallName) {
                    case "INITIALIZE": {
                        m_CONTROLLER_STATUS = CONTROLLER_ENUMS.OK;
                        boolean bConnected = Initialise();
                        result.success(bConnected);
                    }
                    break;
                    case "GETDEVICE": {
                        JSONObject retVal = GetDeviceList();
                        if (retVal != null) {
                            result.success(retVal.toString());
                        }
                    }
                    break;
                    case "CONNECTDEVICE": {
                        try {
                            String strSerialNumber = call.argument("SerialNumber");
                            if (m_connectedDevice == null) {
                                boolean bConnected = ConnectToDevice(strSerialNumber);
                                result.success(bConnected);
                            } else {
                                result.success(true);
                            }
                        } catch (Exception e) {
                            Log.e(TAG, "CONNECT TO DEVICE ANDROID EXC: ", e);
                        }
                    }
                    break;
                    case "SETOUTPUT": {
                        int output = (int) call.argument("outputNumber");
                        boolean state = (boolean) call.argument("state");
                        SetOutputs(output, state);
                    }
                    break;
                    case "GETINPUT": {
                        JSONObject retVal = CheckDeviceInputs();
                        if (retVal != null) {
                            result.success(retVal.toString());
                        }
                    }
                    break;
                    default:
                        break;
                }
            });

    new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), EVENT_CHANNEL).setStreamHandler(
            new EventChannel.StreamHandler() {
                private BroadcastReceiver usbStateChangeReceiver;

                @Override
                public void onListen(Object arguments, EventChannel.EventSink events) {
                    usbStateChangeReceiver = createUSBStateChangeReceiver(events);
                    IntentFilter filter = new IntentFilter();
                    filter.addAction("android.hardware.usb.action.USB_DEVICE_ATTACHED");
                    filter.addAction("android.hardware.usb.action.USB_DEVICE_DETACHED");
                    registerReceiver(
                            usbStateChangeReceiver, filter);
                }

                @Override
                public void onCancel(Object arguments) {
                    unregisterReceiver(usbStateChangeReceiver);
                    usbStateChangeReceiver = null;
                }
            }
    );

    new EventChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), INPUT_EVENT_CHANNEL).setStreamHandler(
            new EventChannel.StreamHandler() {

                @Override
                public void onListen(Object arguments, EventChannel.EventSink events) {
                    timeLeftInMilliseconds = 30000;
                    //timer is started when device is connected so tha android knows when to tell flutter button has
                    //been pressed
                    m_updateTimer = new CountDownTimer(timeLeftInMilliseconds, 100) {
                        public void onTick(long millisUntilFinished) {
                            isRunning = true;
                            JSONObject retVal = CheckDeviceInputs();
                            if (retVal != null) {
                                events.success(retVal.toString());
                            } else {
                                events.success(null);
                            }
                        }

                        public void onFinish() {
                            isRunning = false;
                            m_updateTimer.start();
                        }
                    }.start();
                }

                @Override
                public void onCancel(Object arguments) {
                    //when event channel is closed we stop the timer then remove the method
                    if (m_updateTimer != null) {
                        isRunning = false;
                        m_updateTimer.cancel();
                    }
                    Log.w(TAG, "cancelling listener");
                }
            }
    );
}

private BroadcastReceiver createUSBStateChangeReceiver(EventChannel.EventSink events) {
    return new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {

            //watches if the usb has been plugged in or not
            String action = intent.getAction();
            if ("android.hardware.usb.action.USB_DEVICE_DETACHED".equals(action)) {
                Log.i(TAG, "Detached: ");
                Toast.makeText(context, "Device detached",
                        Toast.LENGTH_LONG).show();
                //if device usb plugs out cancel timer
                m_connectedDevice = null;
                events.success(false);
            } else if ("android.hardware.usb.action.USB_DEVICE_ATTACHED".equals(action)) {
                Log.i(TAG, "Attached:");
                Toast.makeText(context, "Device attached",
                        Toast.LENGTH_LONG).show();
                if (m_updateTimer != null) {
                    Log.e(TAG, "START THE TIMER: ");
                    m_updateTimer.start();
                }
                events.success(true);
            }
        }
    };
}

// INITIALISE
private boolean Initialise() {
    try {
        m_deviceManager = D2xxManager.getInstance(this);
        return m_deviceManager != null;
    } catch (D2xxManager.D2xxException exc) {
        Log.e(TAG, "Initialise: Failed to get instance");
        return false;
    }
}

// GET ALL CONNECTED DEVICES
private JSONObject GetDeviceList() {
    // first check if the list is empty, then create the list based on what is plugged in
    // for now only the first device will be connect
    try {
        m_iDeviceCount = m_deviceManager.createDeviceInfoList(this);
        if (m_iDeviceCount == 0)
            return null;

        D2xxManager.FtDeviceInfoListNode firstItem = m_deviceManager.getDeviceInfoListDetail(0);
        JSONObject returnData = new JSONObject();
        try {
            //create connected device info object to send to flutter
            returnData.put("ID", firstItem.id);
            returnData.put("Description", firstItem.description);
            returnData.put("BCDDevice", firstItem.bcdDevice);
            returnData.put("LineStatus", firstItem.lineStatus);
            returnData.put("ModemStatus", firstItem.modemStatus);
            returnData.put("Type", firstItem.type);
            returnData.put("SerialNumber", firstItem.serialNumber);
            return returnData;
        } catch (JSONException e) {
            e.printStackTrace();
            Log.e(TAG, "GetDeviceList: Failed" + e);
            return null;
        }
    } catch (Exception exc) {
        Log.e(TAG, "GetDeviceList: Failed" + exc);
        return null;
    }
}

// CONNECT THE DEVICE
private boolean ConnectToDevice(String strSerialNumber) {
    try {
        m_connectedDevice = m_deviceManager.openBySerialNumber(this, strSerialNumber);
        //m_initialBitMask
        if (m_connectedDevice != null && m_connectedDevice.isOpen()) {
            //printing the device details
            Log.i(TAG, "ConnectToDevice: IsOpen : ");
            Log.i(TAG, "ConnectToDevice: IsOpen : " + m_connectedDevice.getDeviceInfo().serialNumber);
            Log.i(TAG, "ConnectToDevice: description : " + m_connectedDevice.getDeviceInfo().description);
            Log.i(TAG, "ConnectToDevice: serialNumber : " + m_connectedDevice.getDeviceInfo().serialNumber);
            Log.i(TAG, "ConnectToDevice: bcdDevice : " + String.valueOf(m_connectedDevice.getDeviceInfo().bcdDevice));
            //setting bit mode
            m_connectedDevice.setBitMode(Byte.parseByte(m_initialBitMask, 2), D2xxManager.FT_BITMODE_CBUS_BITBANG);
            return true;
        } else {
            m_CONTROLLER_STATUS = CONTROLLER_ENUMS.Closed;
            return false;
        }
    } catch (Exception exc) {
        m_CONTROLLER_STATUS = CONTROLLER_ENUMS.Failed;
        Log.e(TAG, "ConnectToDevice: Failed" + exc);
        Log.i(TAG, "ConnectToDevice: IsNotOpen");
        return false;
    }
}

// SET OUTPUTS TO DEVICE
private void SetOutputs(int outputNumber, boolean bState) {
    //if device is connected you can use this to activate relays
    if (m_connectedDevice != null && m_connectedDevice.isOpen()) {
        byte lByteMask = m_connectedDevice.getBitMode();

        switch (outputNumber) {
            //relay 1
            case 1: {
                if (bState) {
                    lByteMask |= 1;
                } else {
                    lByteMask &= 0xFE;
                }
            }
            break;
            //relay 2
            case 2: {
                if (bState) {
                    lByteMask |= 2;
                } else {
                    lByteMask &= 0xFD;
                }
            }
            break;
        }
        //fire with new values
        m_connectedDevice.setBitMode(lByteMask, D2xxManager.FT_BITMODE_CBUS_BITBANG);
    }
}

// READ INTERRUPTS - Timer based
private JSONObject CheckDeviceInputs() {
    JSONObject returnData = new JSONObject();
    try {
        if (m_connectedDevice != null && m_connectedDevice.isOpen()) {
            byte inputBytes = m_connectedDevice.getBitMode();
            boolean input1 = (inputBytes & 0x04) != 0x04;
            boolean input2 = (inputBytes & 0x08) != 0x08;

            if (input1) {
                m_input1State = true;
            }

            if (input2) {
                m_input2State = true;
            }
            //if input is true global will be set above then below if the local is
            //not true anymore it will activate and deactivate the relays
            if (!input1 && m_input1State) {
                SetOutputs(2, true);
                try {
                    //delay so relay light is seen
                    Thread.sleep(200);
                    SetOutputs(2, false);
                } catch (InterruptedException ex) {
                    Log.e(TAG, "Set time out: ", ex);
                }
                m_input1State = false;
            }

            if (!input2 && m_input2State) {
                SetOutputs(1, true);
                try {
                    //delay so relay light is seen
                    Thread.sleep(200);
                    SetOutputs(1, false);
                } catch (InterruptedException ex) {
                    Log.e(TAG, "Set time out: ", ex);
                }
                m_input2State = false;
            }

            returnData.put("Input1State", input1);
            returnData.put("Input2State", input2);
            return returnData;
        } else {
            return null;
        }
    } catch (JSONException e) {
        e.printStackTrace();
        Log.e(TAG, "Failed to get DeviceStatus: Failed" + e);
        return null;
    }
}
}

On my flutter side I create a banner that listens to the I/O

import 'dart:convert';
import 'package:covidqa/Controllers/DeviceController.dart';
import 'package:covidqa/Models/IRelayDevice.dart';
import 'package:covidqa/Models/Inputs.dart';
import 'package:covidqa/StateHandler/globals.dart';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:async';

import 'package:provider/provider.dart';

class BannerView extends StatefulWidget {
      final Function(Inputs) returningInputs;
      final Function(bool) connected;
      BannerView(this.returningInputs, this.connected);

      @override
      BannerViewState createState() {
          return BannerViewState();
      }
   }

class BannerViewState extends State<BannerView> {
  DeviceController deviceController = new DeviceController();
  EventChannel eventChannel = const EventChannel('event');
  EventChannel inputEventChannel = const EventChannel('input');

  bool init;
  IRelayDevice device;
  Inputs inputs;
  String error;

  bool hasDevice = false;

  Globals globals;

  bool connected = false;

  bool inputState1 = false;
  bool inputState2 = false;
  bool outputState1 = false;
  bool outputState2 = false;

  _init() {
    deviceController.init().then((value) {
      if (value == true) {
        getDevice();
      }
    });
  }

  Future<void> getDevice() async {
    if (!mounted) {
      return;
    }
    deviceController.getDevice().then((data) => setState(() {
          device = data;
          connectDevice();
        }));
  }

  Future connectDevice() async {
    if (!mounted) {
      return;
    }
    if (device != null) {
      deviceController.connectDevice(device).then((data) => setState(() {
            widget.connected(data);
            connected = data;
            if (data) {
              inputEventChannel
                  .receiveBroadcastStream()
                  .listen(_onInput, onError: deviceController.onError);
            }
          }));
    } else {
      getDevice();
    }
  }

  @override
  void initState() {
    super.initState();
    _init();
    eventChannel
        .receiveBroadcastStream()
        .listen(_onEvent, onError: deviceController.onError);
  }

  void _onEvent(Object event) {
    if (!mounted) {
      widget.connected(false);
      device = null;
      connected = false;
      return;
    }
    setState(() {
      hasDevice = event;
      if (!hasDevice) {
        device = null;
        connected = false;
        widget.connected(false);
      } else {
        connectDevice();
      }
    });
  }

  void _onInput(Object event) {
    try {
      if (!mounted) {
        return;
      }
      if (event == null) {
        print("Result is null");
        return;
      }

      Map bodyResult = jsonDecode(event);
      inputs = Inputs.fromJson(bodyResult);
      widget.returningInputs(inputs);
    } on PlatformException catch (e) {
      print("exception" + e.toString());
    }
  }

  @override
  Widget build(BuildContext context) {
    globals = Provider.of<Globals>(context);
    return Container(
      color: connected ? Colors.green : Colors.red,
      height: MediaQuery.of(context).size.height * 0.01,
    );
  }
}

Again this was long ago and cant remember much and the code is not the most tidy but this is the just of it good luck

DEJA TU COMENTARIO

© 2017 website by Rubit Corporation