Androidアプリ開発

USBシリアル通信をAndroidで実装する

この記事は約28分で読めます。
記事内に広告が含まれています。
スポンサーリンク

この記事は Android スマホ用のアプリ開発の中で、
今後の開発で再使用性が高いと思われるコーディングをまとめたものです。
Java での開発経験、XML 構文規則、Android のアプリ開発経験がある方を対象としています。
Android のアプリ開発でお役にたててれば、嬉しいです。
(これから Android のアプリ開発や Java での開発を始めたい方への案内は、
記事の最後で紹介します)

この記事のテーマ


 USBシリアル通信をusb-serial-for-androidで実装する

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' }
    }
}
:

デバイス接続でアプリを起動したい場合、device_filter.xmlの作成とAndroidManifest.xmlに定義を追加します。詳細はこちら↓↓↓

https://github.com/mik3y/usb-serial-for-android

◎ライセンス表記
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デバイスの権限チェックは、UsbManagerhasPermissionで行います。
マルチポートの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を取得し、UsbDeviceConnectionUsbSerialPortをオープンします。
UsbSerialPortを使用して、入出力用のSerialInputOutputManagerを生成します。
SerialInputOutputManagerの生成では、入力用のリスナーを作成し、入力データのイベント処理を記述します。
出力は、SerialInputOutputManagerwriteAsyncを使用します。

    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;
            }
        }
    }

UsbDeviceConnectionclaimInterfaceで排他アクセスを獲得します。
UsbSerialProberでドライバを取得し、UsbSerialDrivergetPortsでシリアルポートを取得します。
マルチポートのUSBに接続している場合、ポート番号を指定して取得します。
シリアルポートをオープンし、setParametersで通信速度やストップビット、パリティを指定します。
入力データのイベント処理は、onNewDataonRunErrorをオーバライドします。
ポイントは、入力(受信データ)の区切り毎ではないことを念頭に、バッファに展開して、分解するようにします。
サンプルでは、改行コードをデリミタとしています。
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を習得するのに最適かと、まずは無料オンライン相談から♪

参考になったら、💛をポッチとしてね♪

スポンサーリンク
msakiをフォローする
スポンサーリンク

コメント欄

タイトルとURLをコピーしました