Java - Use usb4java to detect USB devices plug and unplug

Java要存取到OS上面USB device相關的資訊本身不支援,一種是透過JNI的方式,讓Java呼叫C來幫忙存取,而C現成的library可以想到知名的libusb。在這邊使用的usb4java就是基於libusb來開發,可以說是libusb for java的代表,目前也有列在libusb的github上! 可惜的是在2018年後就沒有再出新版本,但並不影響今日要實作的部分,因為只有用到list出目前電腦上有哪些連接的USB devices,來偵測當下有哪些裝置plug or unplug!
首先,請至usb4java github下載目前最新的v1.3.0版本,解壓縮後將
commons-lang3-3.8.1.jar
libusb4java-1.3.0-win32-x86-64.jar (在此以Windows環境開發)
usb4java-1.3.0.jar
等加入到build path內

再來說明程式碼的部分:
#UsbDevice.java

import java.util.Objects;

public class UsbDevice {
	private short vid;
	private short pid;
	private int bus;
	private int port;
	private int device;
	
	public UsbDevice(int bus, int port, int device, short vid, short pid){
		this.bus = bus;
		this.port = port;
		this.device = device;
		this.vid = vid;
		this.pid = pid;
	}
	
	public short getVid() {
		return vid;
	}
	
	public short getPid() {
		return pid;
	}

	public int getDevice() {
		return device;
	}

	public void setDevice(int device) {
		this.device = device;
	}

	public int getBus() {
		return bus;
	}

	public int getPort() {
		return port;
	}

	@Override
	public int hashCode() {
		return Objects.hash(bus, device, port);
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		UsbDevice other = (UsbDevice) obj;
		return bus == other.bus && device == other.device && port == other.port;
	}

	@Override
	public String toString() {
		return "UsbDevice [vid=" + String.format("0x%04x", vid) + ", pid=" + String.format("0x%04x", pid) + ", bus=" + bus + ", port=" + port + ", device=" + device +"]";
	}
}

定義存放USB device的Vendor ID、Product ID、bus number、port number及device address等資訊
從Vendor ID、Product ID可以得知是否為相同的USB裝置
從bus number、port number及device address可以得知插入的位置
這邊的equals是判斷哪些device被插入或移除

#UsbDeviceDetectService.java

import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.usb4java.Context;
import org.usb4java.Device;
import org.usb4java.DeviceDescriptor;
import org.usb4java.DeviceList;
import org.usb4java.LibUsb;
import org.usb4java.LibUsbException;

public abstract class UsbDeviceDetectService {
	private boolean enabled = false;
	private Timer timer;
	private Context context = new Context();
	
	protected abstract void getConnectedDevices(List<UsbDevice> usbDeviceList);
	
	protected void addUsbServicesListener() {
		timer = new Timer();
		timer.schedule(new TimerTask(){
			@Override
			public void run() {
				DeviceList list = new DeviceList();
				List<UsbDevice> plugTemp = new ArrayList<>();
				
				int result = LibUsb.getDeviceList(context, list);
			    if (result < 0) {
			    	cancel();
			    	throw new LibUsbException("Unable to get device list", result);
			    }
			    // Iterate over all devices and scan for the right one
			    try {
			        for (Device device: list){
			            DeviceDescriptor descriptor = new DeviceDescriptor();
			            result = LibUsb.getDeviceDescriptor(device, descriptor);
			            if (result != LibUsb.SUCCESS) 
			            	throw new LibUsbException("Unable to read device descriptor", result);
			            
			            UsbDevice usbDevice = new UsbDevice(LibUsb.getBusNumber(device), LibUsb.getPortNumber(device), LibUsb.getDeviceAddress(device), 
			            		descriptor.idVendor(), descriptor.idProduct());
			            plugTemp.add(usbDevice);
			        }
			        getConnectedDevices(plugTemp);
			    } finally{
			        // Ensure the allocated device list is freed
			        LibUsb.freeDeviceList(list, true);
			    }
			}
		}, 0 , 3000);
	}
	
	public void removeUsbServicesListener() {
		LibUsb.exit(context);
		context = null;
		if(timer != null){
			timer.cancel();
			timer = null;
		}
	}
	
	public boolean isEnabled() {
		return this.enabled;
	}

	public void setEnabled(boolean enabled) {
		this.enabled = enabled;
		if (this.enabled) {
			int result = LibUsb.init(context);
			if (result != LibUsb.SUCCESS) 
				throw new LibUsbException("Unable to initialize libusb.", result);
			addUsbServicesListener();
		} else {
			removeUsbServicesListener();
		}
	}
}

此抽象類別需定義getConnectedDevices method要做什麼處理,當下傳入的usbDeviceList為目前正連接著的USB device,用一個timer每3秒輪詢一次作為間隔,以此達到隨時detect是否有新插入或移除的裝置!

#Main.java

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main {
	private List<UsbDevice> connectedLists = new ArrayList<>();
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		new Main();
	}
	
	public Main() {
		UsbDeviceDetectService service = new UsbDeviceDetectService() {
			boolean firstDetected = true;
			
			@Override
			protected void getConnectedDevices(List<UsbDevice> usbDeviceList) {
				// TODO Auto-generated method stub
				for(UsbDevice device : usbDeviceList) {
					if(!connectedLists.contains(device)) {
						connectedLists.add(device);
						if(!firstDetected) {
							System.out.println(">> Plug: "+device);
						}
					}
				}
				
				firstDetected = false;
				
				if(connectedLists.size() > usbDeviceList.size()) {
					List<UsbDevice> unplugList = new ArrayList<>();
					for(UsbDevice device : connectedLists) {
						if(!usbDeviceList.contains(device)) {
							unplugList.add(device);
							System.out.println("<< Unplug: "+device);
						}
					}
					connectedLists.removeAll(unplugList);
				}
			}
		};
		
		Scanner input = new Scanner(System.in);
		try {
			while(true) {
				System.out.println("(1)Start USB Service(2)List Connected Devices(3)Terminate");
				int option = input.nextInt();
				if(option == 1) {
					if(!service.isEnabled()) {
						service.setEnabled(true);
						System.out.println("Start USB Detect Listener");
					}else {
						System.out.println("USB Detect has been started");
					}
				}else if(option == 2) {
					System.out.println("Connected Devices:");
					for(UsbDevice device : connectedLists) {
						System.out.println(device);
					}
				}else if(option == 3) {
					if(service.isEnabled()) {
						service.setEnabled(false);
						System.out.println("Terminate and remove USB Detect Listener");
						break;
					}else {
						System.out.println("Terminate");
					}
				}else {
					continue;
				}
				System.out.println();
			}
		} finally {
			input.close();
		}
	}
}

主程式提供一操作,分別為啟動USB detect server及列出目前連接的裝置及終止程式運作
其覆寫的getConnectedDevices method內簡單判斷目前什麼裝置plug or unplug,並撇除了啟動USB detect時本身已插著的裝置

執行結果如下:

留言