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