この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、記事の最後で紹介します)
Android3.1以降でUSBホストモードがサポートされました。
この機能を使用すれば、接続したUSBデバイスとデータ受送信などシリアル通信の実装が可能です。
USBホストとアクセサリの概要
センサー自体はRS-232Cなどのシリアル通信で行い、通信を中継するUSBシリアル変換をもつUSBデバイスが存在します。この場合、通信相手はセンサーモジュールではなく、USB変換モジュールです。
USB変換モジュールには、FTDI、CP2102、CH340、PL2303などが存在します。
それぞれのモジュールにドライバーが存在し、アプリで個々に対応するのかなり面倒です。
usb-serial-for-androidは、モジュールの種類を意識することなく透過的にシリアル通信を可能とするライブラリです。
usb-serial-for-androidは、Androidで使用できるオープンソフトウェアライブラリです。
USB変換モジュールは、PL2303GC(Prolific)です。
usb-serial-for-androidを使用するための準備
usb-serial-for-androidを使用するには、
プロジェクトおよび、モジュールのbuild.gradleファイル、settings.gradleに定義の追加が必要です。
◎build.gradle(プロジェクト)
:
allprojects {
repositories {
:
maven { url 'https://jitpack.io' }
}
}
:
◎build.gradle(モジュール)
2024年10月現在の最新バージョンは 3.8.0 です。
dependencies {
:
implementation 'com.github.mik3y:usb-serial-for-android:3.8.0'
}
◎settings.gradle
:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
:
maven { url 'https://jitpack.io' }
}
}
:
◎ライセンス表記
usb-serial-for-androidは、MIT Licenseです。
アプリで使用する場合、ライセンス表記が必要です。
USBシリアル通信の実装
USBデバイスの接続とシリアル通信に分けて、USBシリアル通信を実装します。
USBデバイスの接続はUSBホストAPI、シリアル通信はusb-serial-for-androidを使用します。
サンプルは、サービスで実装しています。
USBホストの概要
USBデバイスの接続
USBデバイスの接続は、USBホストAPIで実装します。
UsbManagerを使用して、USBに接続しているデバイスを取得し、権限チェックを行います。
権限がない場合は、ユーザ承認リクエストを発行します。
権限がある、ユーザ承認リクエストでOKの場合は、シリアル通信の接続を生成します。
import com.hoho.android.usbserial.driver.FtdiSerialDriver;
import com.hoho.android.usbserial.driver.ProbeTable;
import com.hoho.android.usbserial.driver.UsbSerialDriver;
import com.hoho.android.usbserial.driver.UsbSerialPort;
import com.hoho.android.usbserial.driver.UsbSerialProber;
import com.hoho.android.usbserial.util.SerialInputOutputManager;
:
public class USBConnectService extends Service {
private static final String ACTION_USB_PERMISSION = "com.jiseifirm.mls.service.LocationService.USB_PERMISSION";
private UsbManager usbManager;
private UsbDevice device;
private Connect connect;
private int PORT = 0;
:
@Override
public void onCreate() {
super.onCreate();
// マルチポートのUSBに接続している場合、シリアル通信のポート(PORT)を指定
:
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
:
usbConnect();
return START_NOT_STICKY;
}
private void usbConnect() {
usbManager = (UsbManager) context.getSystemService(Service.USB_SERVICE);
// 接続するUSBデバイスのベンダーID(vender)とプロダクトID(product)
device = attachedDevice(vender, product);
if (device != null) {
if (usbManager.hasPermission(device)) {
// シリアル通信の接続を生成
//noinspection InstantiatingAThreadWithDefaultRunMethod
connect = new Connect(device);
} else {
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION).setPackage(getPackageName()), PendingIntent.FLAG_MUTABLE);
IntentFilter intentFilter = new IntentFilter(ACTION_USB_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(usbReceiver, intentFilter, RECEIVER_EXPORTED);
} else {
registerReceiver(usbReceiver, intentFilter);
}
usbManager.requestPermission(device, pendingIntent);
}
} else {
// 接続できるデバイスがない場合の処理
:
}
:
// USBデバイスの取得
private UsbDevice attachedDevice(int vender, int product) {
try {
for (UsbDevice usbDevice : usbManager.getDeviceList().values()) {
if (usbDevice.getVendorId() == vender & usbDevice.getProductId() == product) {
return usbDevice;
}
}
} catch (Exception e) {
// エラー処理
:
}
return null;
}
:
サンプルでは、接続するUSBデバイスのベンダーIDとプロダクトIDが一致するデバイスを取得しています。
取得したUSBデバイスの権限チェックは、UsbManagerのhasPermissionで行います。
マルチポートのUSBに接続している場合、シリアル通信のポート(PORT)を指定します。
◎承認リクエストの結果確認
承認リクエストの結果確認はBroadcastReceiverで行います。
// 承認リクエストの結果確認
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
if (ACTION_USB_PERMISSION.equals(intent.getAction())) {
synchronized (this) {
unregisterReceiver(usbReceiver);
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
// OKの場合の処理
:
} else {
// キャンセルの場合の処理
:
}
}
}
}
}
:
ユーザ承認リクエストの発行はPendingIntentを行いますが、PendingIntent生成時にFLAG_MUTABLEを指定しないと、Intentに結果がセットされないので、注意が必要です。
詳しくは、USBデバイスの権限チェックとユーザ承認リクエストを参照ください。
シリアル通信
シリアル通信は、usb-serial-for-androidで実装します。
サンプルでは、通信用スレッドとして分離するために内部クラスとして実装しています。
USBデバイスの入出力のUsbEndpointを取得し、UsbDeviceConnection、UsbSerialPortをオープンします。
UsbSerialPortを使用して、入出力用のSerialInputOutputManagerを生成します。
SerialInputOutputManagerの生成では、入力用のリスナーを作成し、入力データのイベント処理を記述します。
出力は、SerialInputOutputManagerのwriteAsyncを使用します。
private class Connect extends Thread {
private UsbEndpoint input;
private UsbEndpoint output;
private SerialInputOutputManager serialInputOutputManager;
public UsbSerialPort usbSerialPort;
private StringBuilder buffer = new StringBuilder();
public Connect(UsbDevice usbDevice) {
UsbInterface foundInterface = null;
for (int j = 0; j < usbDevice.getInterfaceCount(); j++) {
UsbInterface deviceInterface = usbDevice.getInterface(j);
UsbEndpoint foundInEndpoint = null;
UsbEndpoint foundOutEndpoint = null;
for (int i = deviceInterface.getEndpointCount() - 1; i > -1; i--) {
UsbEndpoint interfaceEndpoint = deviceInterface.getEndpoint(i);
if (interfaceEndpoint.getDirection() == UsbConstants.USB_DIR_IN) {
if (interfaceEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
foundInEndpoint = interfaceEndpoint;
}
}
if (interfaceEndpoint.getDirection() == UsbConstants.USB_DIR_OUT) {
if (interfaceEndpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
foundOutEndpoint = interfaceEndpoint;
}
}
if ((foundInEndpoint != null) && (foundOutEndpoint != null)) {
input = foundInEndpoint;
output = foundOutEndpoint;
break;
}
}
if ((input != null) && (output != null)) {
foundInterface = deviceInterface;
break;
}
}
UsbInterface usbInterface = foundInterface;
UsbDeviceConnection connection = usbManager.openDevice(usbDevice);
if (connection != null && usbInterface != null) {
connection.claimInterface(usbInterface, true);
}
List<UsbSerialDriver> usbSerialDriverList = UsbSerialProber.getDefaultProber().findAllDrivers(usbManager);
if (usbSerialDriverList == null) {
//noinspection unchecked
usbSerialDriverList = (List<UsbSerialDriver>) usbSerialProber().probeDevice(usbDevice);
}
if (connection != null && usbSerialDriverList != null && !usbSerialDriverList.isEmpty()) {
UsbSerialDriver usbSerialDriver = usbSerialDriverList.get(0);
usbSerialPort = usbSerialDriver.getPorts().get(usbSerialDriver.getPorts().size() > PORT ? PORT : 0);
try {
usbSerialPort.open(connection);
usbSerialPort.setParameters(baud(BAUD), 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE);
} catch (Exception e) {
disconnect();
}
serialInputOutputManager = new SerialInputOutputManager(usbSerialPort, new SerialInputOutputManager.Listener() {
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Override
public void onNewData(byte[] data) {
buffer.append(new String(data));
int pos = buffer.toString().indexOf("\r\n");
if (pos > 0) {
// データ受信処理
// 受信データは、buffer.substring(0, pos) + "\r\n"
buffer = new StringBuilder(buffer.substring(pos + 2));
}
}
@SuppressLint("UnspecifiedRegisterReceiverFlag")
@Override
public void onRunError(Exception e) {
if (device != null) {
// 再接続
if (usbManager.hasPermission(device)) {
if (serialInputOutputManager != null) {
serialInputOutputManager.stop();
new Thread(serialInputOutputManager).start();
}
} else {
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION).setPackage(getPackageName()), PendingIntent.FLAG_MUTABLE);
IntentFilter intentFilter = new IntentFilter(ACTION_USB_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(usbReceiver, intentFilter, RECEIVER_EXPORTED);
} else {
registerReceiver(usbReceiver, intentFilter);
}
usbManager.requestPermission(usbDevice, pendingIntent);
}
}
}
});
serialInputOutputManager.setReadBufferSize(256);
serialInputOutputManager.setReadTimeout(4000);
new Thread(serialInputOutputManager).start();
}
}
private UsbSerialProber usbSerialProber() {
ProbeTable customTable = new ProbeTable();
customTable.addProduct(0x1234, 0x0001, FtdiSerialDriver.class);
customTable.addProduct(0x1234, 0x0002, FtdiSerialDriver.class);
return new UsbSerialProber(customTable);
}
// 出力 //
private void write(byte[] data) {
serialInputOutputManager.writeAsync(data);
}
// 通信速度 //
public int baud(int baud) {
switch (baud) {
case 10: // 921,600 bps
return 921600;
case 9: // 460,800 bps
return 460800;
case 8: // 230,400 bps
return 230400;
case 7: // 115,200 bps
return 115200;
case 6: // 57,600 bps
return 57600;
case 5: // 38,400 bps
return 38400;
case 4: // 19,200 bps
return 19200;
case 2: // 4,800 bps
return 4800;
case 1: // 2,400 bps
return 2400;
case 0: // 1,200 bps
return 1200;
default: // 9,600 bps
return 9600;
}
}
// 切断 //
public void close() {
if (serialInputOutputManager != null) {
// シリアル通信マネージャを停止
serialInputOutputManager.stop();
serialInputOutputManager.setListener(null);
serialInputOutputManager = null;
}
if (usbSerialPort != null) {
try {
usbSerialPort.close();
} catch (IOException e) {
// エラー処理
:
}
usbSerialPort = null;
}
}
}
UsbDeviceConnectionのclaimInterfaceで排他アクセスを獲得します。
UsbSerialProberでドライバを取得し、UsbSerialDriverのgetPortsでシリアルポートを取得します。
マルチポートのUSBに接続している場合、ポート番号を指定して取得します。
シリアルポートをオープンし、setParametersで通信速度やストップビット、パリティを指定します。
入力データのイベント処理は、onNewDataとonRunErrorをオーバライドします。
ポイントは、入力(受信データ)の区切り毎ではないことを念頭に、バッファに展開して、分解するようにします。
サンプルでは、改行コードをデリミタとしています。
onRunErrorでは、接続が切れた場合の再接続などのエラーハンドリングを記述します。
入力バッファはsetReadBufferSize、タイムアウトはsetReadTimeoutで指定します。
USBデバイスの切断
サービスで実装する場合、USBデバイスの切断は終了時に行います。
:
@Override
public void onDestroy() {
// USB切断
if (connect != null) {
connect.close();
connect = null;
}
super.onDestroy();
}
USBシリアル通信をusb-serial-for-androidで実装しているAndroidアプリです。
今回は、ここまでです。
誤字脱字、意味不明でわかりづらい、
もっと詳しく知りたいなどのご意見は、
このページの最後にあるコメントか、
こちらから、お願いいたします♪
ポチッとして頂けると、
次のコンテンツを作成する励みになります♪
これからAndroidのアプリ開発やJavaでの開発を始めたい方へ
初めての Android のアプリ開発では、アプリケーション開発経験がない方や、
アプリケーション開発経験がある方でも、Java や C# などのオブジェクト指向言語が初めての方は、
書籍などによる独学ではアプリ開発できるようになるには、
かなりの時間がかかりますので、オンラインスクールでの習得をおススメします。
未経験者からシステムエンジニアを目指すのに最適かと、まずは無料相談から♪
未経験者からプログラマーを目指すのに最適かと、まずは無料カウンセリングから♪
カリキュラムとサポートがしっかりしています、お得なキャンペーンとかいろいろやっています♪
ゲーム系に強いスクール、UnityやUnrealEngineを習得するのに最適かと、まずは無料オンライン相談から♪
参考になったら、💛をポッチとしてね♪
コメント欄