[Unity VR과 Android BLE] #9 Unity에서 BLE 장치 제어

in kr-dev •  8 days ago

Unity VR앱과 안드로이드 앱을 integration하는 앱을 만들어 봅니다. 또 안드로이드의 BLE 장치 제어를 Unity에서 하는 내용을 다룹니다.


출처: https://medium.com/@narendrapal39/android-add-support-library-for-unity-205b45ca243d

이전글 - [Unity VR과 Android BLE] #8 안드로이드 Plugin


드디어 Unity에서 BLE 장치 제어하는 부분입니다. 이번 시리즈글의 꽃이라고 할 수 있는 부분이죠~
사실 매우 간단한 건데... 왜 그렇게 찾아도 찾아도 안나오는 것일까요? Unity와 안드로이드 통합에 대해 무지한 결과로 고생을 하게 된걸까요???

AndroidManifest.xml

안드로이드에서 BLE 장치를 검색하고, 연결하기 위해서는 아래와 같은 권한이 필요합니다.

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

<permission android:name="android.permission.BLUETOOTH" android:label="BLUETOOTH"/>
<permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

BLE 권한 설정은 다음글을 참고하시면 됩니다.
[Android App BLE] #3 BLE를 위한 권한 설정 및 화면 구성

저는 당연히 안드로이드 라이브러리의 AndroidManifest.xml 파일에 위와 같은 권한을 추가했습니다. 그리고 안드로이드 라이브러리에 BLE 장치 연결하는 함수를 만들었죠. 그리고 Unity에서 이전글에서 했듯이 안드로이드 라이브러리의 함수를 호출하였습니다.

당연히 되야 된다고 생각했지만 Unity 앱으르 실행하면 ACCESS_FINE_LOCATION 권한이 없다는 에러 메시지만 나올 뿐이었습니다. 이래 저래 찾아보니 AndroidManifest.xml 파일을 Unity의 Assets -> Plugins->Android에 추가해야 된다고 하여 안드로이드 라이브러리의 AndroidManifest.xml 파일을 해당 폴더에 추가해 봤습니다.

그래서 역시 똑같이 권한이 없다는 에러 메시지만 발생합니다!!!

당췌 그 원인을 알 수 없던 차에 검색에 검색을 거듭하던 중 2010년의 아래 글이 제게 희망을 줬습니다. 보통 Unity 개발할 때 오래된 글은 버전차이로 검색되어도 무시하는데 이번에는 더이상 검색할 것도 없는 심정이라 보게 되었죠.

https://answers.unity.com/questions/38222/android-plugin-and-permissions.html

The default AndroidManifest.xml is found in "Unity3\Editor\Data\PlaybackEngines\androidplayer"
I copied it to my android plugin folder and extended it with my needed permissions. Worked without any problems!

아!!! Unity에도 AndroidManifest.xml 파일이 있는 것입니다. 그리고 바로 그 파일에 권한 설정을 해야 하는 것이었습니다.

오 땡스 갓~

위 사이트대로 폴더를 찾아가보니 아래와 같이 정말 AndroidManifest.xml 파일이 있는 것이었습니다!

image.png

위치가 한 단계 더 들어가지만(Apk폴더 밑) 이것을 Unity의 Assets->Plugins->Android에 복사하고 권한을 추가하니 짜잔하고 됩니다!!! 감격!

AndroidManifest.xml 파일을 Assets -> Plugins -> Android에 추가한 후 아래와 같이 권한 부분을 추가합니다. 여기서는 코드 전체를 올립니다.

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.unity3d.player"
    xmlns:tools="http://schemas.android.com/tools"
    android:installLocation="preferExternal">
    <supports-screens
        android:smallScreens="true"
        android:normalScreens="true"
        android:largeScreens="true"
        android:xlargeScreens="true"
        android:anyDensity="true"/>

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <permission android:name="android.permission.BLUETOOTH" android:label="BLUETOOTH"/>
    <permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
  
    <application
        android:theme="@style/UnityThemeSelector"
        android:icon="@mipmap/app_icon"
        android:label="@string/app_name">
        <activity android:name="com.unity3d.player.UnityPlayerActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <meta-data android:name="unityplayer.UnityActivity" android:value="true" />
        </activity>
    </application>
</manifest>

위 코드를 보면 다음과 같은 부분이 있습니다.

<activity android:name="com.unity3d.player.UnityPlayerActivity"

Unity에서 Activity를 가지고 있는 것입니다. 그래서 이전글에서 이 Activity 인스턴스를 얻어다가 안드로이드 라이브러리에 전달했던 것이었습니다.

이제 좀 이해가 됩니다. 이해가 된 상태로 검색을 해보니 보다 자세한 설명이 되어 있는 사이트를 발견했습니다.

image.png
출처: Unity Android plugin tutorial

자세히 보지 읽어 보지 않았지만, 느낌으로 Unity의 Android Activity에 대한 구성에 대한 설명임을 알 수 있었습니다. 아는 만큼 보이나 봅니다.

여러분들도 이제 저처럼 고생하지 마세요!

BLE 장치 연결 및 제어

안드로이드에서 BLE 장치 연결 및 제어하는 것은 이전에 구현했었습니다.
[Android App BLE] #7 BLE 장치 제어

그래서 이 코드를 사용하면 Unity에서 BLE 장치 연결하고 제어하는 것은 비교적 쉽게 됩니다. 가장 중요한 권한 문제를 해결했으니까 말이죠~

이전 코드를 거의 그대로 사용할건데, VR 앱이다 보니 VR에서 BLE 장치 스캔하고 연결하는 작업을 없애고 이미 Pairing된 장치로 바로 연결하는 코드를 구현할 것입니다. 아마 이런 방식이 나중에 앱을 만드는데 더욱 실용적일 것입니다. 한가지 좀 아쉬운 점은 Pairing된 장치가 한개만 있어야 하는 것입니다. 나중에 복수의 pairing된 장치가 있어도 특정 장치에 바로 연결할 수 있는 코드를 구상해 보는 걸로 하고 지금은 일단 스마트폰에 pairing된 장치는 한개로 가정하고 코딩을 하겠습니다. 스캔 코드가 빠지다 보니 코드가 간결해졌습니다. 코드에 대한 설명은 이전 BLE 개발 시리즈글에 다 되어 있는 부분이라 여기서는 소스 코드 전체를 올리겠습니다.

안드로이드 라이브러리의 AndroidManifest.xml

안드로이드 라이브러리의 AndroiManifest.xml 파일에도 BLE 장치를 사용하기 위한 권한을 추가합니다. 왜냐하면 코드에서 BLE 장치를 사용하기 위한 함수들이 쓰이는데 이를 위해 권한이 필요합니다.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="club.etain.blelibrary">

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

    <permission android:name="android.permission.BLUETOOTH" android:label="BLUETOOTH" />
    <permission android:name="android.permission.BLUETOOTH_ADMIN" />
    <permission android:name="android.permission.ACCESS_FINE_LOCATION" />

</manifest>

Constants.java

이것은 이전의 BLE 장치 제어 코드와 거의 동일합니다. 추가된 부분은 Unity에서 안드로이드 라이브러리 함수들을 호출할건데, 그 결과로 메시지를 출력하도록 하기 위한 것입니다.

package club.etain.blelibrary;

import java.util.UUID;

public class Constants {
    // tag for log message
    public static String TAG= "BLEControl";
    // initialization message
    public static String INIT_MSG= "initialized";
    // connection message
    public static String CONNECT_MSG= "connected";
    // control message
    public static String CONTROL_MSG= "controlling";
    // used to identify adding bluetooth names
    public static int REQUEST_ENABLE_BT= 1;
    // used to request fine location permission
    public static int REQUEST_FINE_LOCATION= 2;

    //// focus v3 service. refer. https://www.foc.us/bluetooth-low-energy-api
    // service and uuid
    public static String SERVICE_STRING = "0000aab0-f845-40fa-995d-658a43feea4c";
    public static UUID UUID_TDCS_SERVICE= UUID.fromString(SERVICE_STRING);
    // command uuid
    public static String CHARACTERISTIC_COMMAND_STRING = "0000AAB1-F845-40FA-995D-658A43FEEA4C";
    public static UUID UUID_CTRL_COMMAND = UUID.fromString( CHARACTERISTIC_COMMAND_STRING );
    // response uuid
    public static String CHARACTERISTIC_RESPONSE_STRING = "0000AAB2-F845-40FA-995D-658A43FEEA4C";
    public static UUID UUID_CTRL_RESPONSE = UUID.fromString( CHARACTERISTIC_COMMAND_STRING );
}

BluetoothUtils.java

이것도 이전 BLE 장치 제어 코드와 동일합니다.

package club.etain.blelibrary;

import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattService;
import android.support.annotation.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import static club.etain.blelibrary.Constants.CHARACTERISTIC_COMMAND_STRING;
import static club.etain.blelibrary.Constants.CHARACTERISTIC_RESPONSE_STRING;
import static club.etain.blelibrary.Constants.SERVICE_STRING;

public class BluetoothUtils {
    /*
Find characteristics of BLE
@param gatt gatt instance
@return list of found gatt characteristics
 */
    public static List<BluetoothGattCharacteristic> findBLECharacteristics(BluetoothGatt _gatt ) {
        List<BluetoothGattCharacteristic> matching_characteristics = new ArrayList<>();
        List<BluetoothGattService> service_list = _gatt.getServices();
        BluetoothGattService service = findGattService(service_list);
        if (service == null) {
            return matching_characteristics;
        }

        List<BluetoothGattCharacteristic> characteristicList = service.getCharacteristics();
        for (BluetoothGattCharacteristic characteristic : characteristicList) {
            if (isMatchingCharacteristic(characteristic)) {
                matching_characteristics.add(characteristic);
            }
        }

        return matching_characteristics;
    }

    /*
    Find command characteristic of the peripheral device
    @param gatt gatt instance
    @return found characteristic
     */
    @Nullable
    public static BluetoothGattCharacteristic findCommandCharacteristic( BluetoothGatt _gatt ) {
        return findCharacteristic( _gatt, CHARACTERISTIC_COMMAND_STRING );
    }

    /*
    Find response characteristic of the peripheral device
    @param gatt gatt instance
    @return found characteristic
     */
    @Nullable
    public static BluetoothGattCharacteristic findResponseCharacteristic( BluetoothGatt _gatt ) {
        return findCharacteristic( _gatt, CHARACTERISTIC_RESPONSE_STRING );
    }

    /*
    Find the given uuid characteristic
    @param gatt gatt instance
    @param uuid_string uuid to query as string
     */
    @Nullable
    private static BluetoothGattCharacteristic findCharacteristic(BluetoothGatt _gatt, String _uuid_string) {
        List<BluetoothGattService> service_list= _gatt.getServices();
        BluetoothGattService service= BluetoothUtils.findGattService( service_list );
        if( service == null ) {
            return null;
        }

        List<BluetoothGattCharacteristic> characteristicList = service.getCharacteristics();
        for( BluetoothGattCharacteristic characteristic : characteristicList) {
            if( matchCharacteristic( characteristic, _uuid_string ) ) {
                return characteristic;
            }
        }

        return null;
    }

    /*
    Match the given characteristic and a uuid string
    @param characteristic one of found characteristic provided by the server
    @param uuid_string uuid as string to match
    @return true if matched
     */
    private static boolean matchCharacteristic( BluetoothGattCharacteristic _characteristic, String _uuid_string ) {
        if( _characteristic == null ) {
            return false;
        }
        UUID uuid = _characteristic.getUuid();
        return matchUUIDs( uuid.toString(), _uuid_string );
    }

    /*
    Find Gatt service that matches with the server's service
    @param service_list list of services
    @return matched service if found
     */
    @Nullable
    private static BluetoothGattService findGattService(List<BluetoothGattService> _service_list) {
        for (BluetoothGattService service : _service_list) {
            String service_uuid_string = service.getUuid().toString();
            if (matchServiceUUIDString(service_uuid_string)) {
                return service;
            }
        }
        return null;
    }

    /*
    Try to match the given uuid with the service uuid
    @param service_uuid_string service UUID as string
    @return true if service uuid is matched
     */
    private static boolean matchServiceUUIDString(String _service_uuid_string) {
        return matchUUIDs( _service_uuid_string, SERVICE_STRING );
    }

    /*
    Check if there is any matching characteristic
    @param characteristic query characteristic
     */
    private static boolean isMatchingCharacteristic( BluetoothGattCharacteristic _characteristic ) {
        if( _characteristic == null ) {
            return false;
        }
        UUID uuid = _characteristic.getUuid();
        return matchCharacteristicUUID( uuid.toString() );
    }

    /*
    Query the given uuid as string to the provided characteristics by the server
    @param characteristic_uuid_string query uuid as string
    @return true if the matched is found
     */
    private static boolean matchCharacteristicUUID( String _characteristic_uuid_string ) {
        return matchUUIDs( _characteristic_uuid_string, CHARACTERISTIC_COMMAND_STRING, CHARACTERISTIC_RESPONSE_STRING );
    }

    /*
    Try to match a uuid with the given set of uuid
    @param uuid_string uuid to query
    @param matches a set of uuid
    @return true if matched
     */
    private static boolean matchUUIDs( String _uuid_string, String... _matches ) {
        for( String match : _matches ) {
            if( _uuid_string.equalsIgnoreCase(match) ) {
                return true;
            }
        }

        return false;
    }
}

BLEControl.java

메인 소스 코드입니다. BLE 장치 스캔을 하지 않아 이전 코드보다 간결해졌습니다. Import 부분은 생략했습니다. 그리고 Unity에서 호출할 함수 2개(connectExternal, controlExternal)가 추가되었습니다.

package club.etain.blelibrary;

import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.util.Log;
import android.widget.Toast;

import java.util.List;
import java.util.Set;

import static club.etain.blelibrary.Constants.CONNECT_MSG;
import static club.etain.blelibrary.Constants.CONTROL_MSG;
import static club.etain.blelibrary.Constants.INIT_MSG;
import static club.etain.blelibrary.Constants.REQUEST_FINE_LOCATION;
import static club.etain.blelibrary.Constants.TAG;
import static club.etain.blelibrary.Constants.REQUEST_ENABLE_BT;

public class BLEControl {
    // current activity
    protected Activity current_activity_;

    //// ble variables
    // ble adapter
    private BluetoothAdapter ble_adapter_;
    // paired devices
    private Set<BluetoothDevice> paired_devices_;
    // flag for connection
    private boolean connected_ = false;
    // flag for control started
    private boolean control_started_ = false;
    // BLE Gatt
    private BluetoothGatt ble_gatt_;

    // set current activity by unity
    public void setActivity(Activity _activity) {
        current_activity_= _activity;


    }

    // Unity calls this function
    public String connectExternal() {
        Log.d(TAG,"External call from unity: connect");

        String msg = init();
        if (!INIT_MSG.equals(msg)) {
            return msg;
        }

        // connect to the paired device
        String connect_msg = connect();
        if (!connected_) {
            return connect_msg;
        }

        return connect_msg;
    }

    // Unity calls this function
    public String controlExternal(){
        Log.d(TAG, "External call from unity: control");
        // stimulate
        String control_msg= controlBLE();
        if( !CONTROL_MSG.equals( control_msg ) ) {
            return control_msg;
        }
        return control_msg;
    }

    // initialize
    public String init() {
        BluetoothManager ble_manager;
        ble_manager = (BluetoothManager)current_activity_.getSystemService(Context.BLUETOOTH_SERVICE);
        // set ble adapter
        ble_adapter_ = ble_manager.getAdapter();

        // finish app if the BLE is not supported
        if (!current_activity_.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            return "BLE is not supported";
        }
        return INIT_MSG;
    }

    // connect to the paired ble device
    private String connect() {
        // check ble available
        boolean available= checkBLEAvailable();
        if( !available )
            return "Error! BLE is not available";

        // disconnect gatt server
        disconnectGattServer();

        // try to connect to the only one paired device
        paired_devices_= ble_adapter_.getBondedDevices();
        // check if it is one
        if( paired_devices_.size() > 1 || paired_devices_.size() < 1 )
        {
            return "Error! Please pair only the one device.";
        }
        // connect to the paired device
        for( BluetoothDevice device : paired_devices_ ) {
            String device_addr= device.getAddress();
//            if( MAC_ADDR.equals( device_addr ) )
            {
                Log.d(TAG, "connecting device: " + device_addr);
                Toast.makeText(current_activity_, "connecting device: " + device_addr, Toast.LENGTH_SHORT).show();
                connectDevice(device);
                connected_= true;
                return CONNECT_MSG;
            }
        }
        return "Error! No paired device found";
    }

    //    @RequiresPermission(Manifest.permission.BLUETOOTH)
    private boolean checkBLEAvailable() {
        // check ble adapter and ble enabled
        if (ble_adapter_ == null || !ble_adapter_.isEnabled()) {
            Log.d( TAG, "Error. BLE is not enabled");
            requestEnableBLE();
            return false;
        }
        // check if location permission
        if (current_activity_.checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            Log.d( TAG, "Error. no fine location permission");
            requestLocationPermission();
            return false;
        }

        // disconnect gatt server
        disconnectGattServer();

        return true;
    }

    private void requestEnableBLE () {
        Intent ble_enable_intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        current_activity_.startActivityForResult(ble_enable_intent, REQUEST_ENABLE_BT);
    }

    private void requestLocationPermission () {
        current_activity_.requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, REQUEST_FINE_LOCATION);
    }


    // Connect to the ble device
    private void connectDevice(BluetoothDevice _device) {
        GattClientCallback gatt_client_cb = new GattClientCallback();
        ble_gatt_ = _device.connectGatt(current_activity_, false, gatt_client_cb);
    }


    // Disconnect Gatt Server
    public void disconnectGattServer() {
        Log.d(TAG, "Closing Gatt connection");
        // reset the connection flag
        connected_ = false;
        // disconnect and close the gatt
        if (ble_gatt_ != null) {
            ble_gatt_.disconnect();
            ble_gatt_.close();
        }
    }


    // Gatt Client Callback class
    private class GattClientCallback extends BluetoothGattCallback {
        @Override
        public void onConnectionStateChange(BluetoothGatt _gatt, int _status, int _new_state) {
            super.onConnectionStateChange(_gatt, _status, _new_state);
            if (_status == BluetoothGatt.GATT_FAILURE) {
                disconnectGattServer();
                return;
            } else if (_status != BluetoothGatt.GATT_SUCCESS) {
                disconnectGattServer();
                return;
            }
            if (_new_state == BluetoothProfile.STATE_CONNECTED) {
                // set the connection flag
                connected_ = true;
                Log.d(TAG, "Connected to the GATT server");
                _gatt.discoverServices();
            } else if (_new_state == BluetoothProfile.STATE_DISCONNECTED) {
                disconnectGattServer();
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt _gatt, int _status) {
            super.onServicesDiscovered(_gatt, _status);
            // check if the discovery failed
            if (_status != BluetoothGatt.GATT_SUCCESS) {
                Log.e(TAG, "Device service discovery failed, status: " + _status);
                return;
            }
            // find discovered characteristics
            List<BluetoothGattCharacteristic> matching_characteristics = club.etain.blelibrary.BluetoothUtils.findBLECharacteristics(_gatt);
            if (matching_characteristics.isEmpty()) {
                Log.e(TAG, "Unable to find characteristics");
                return;
            }
            // log for successful discovery
            Log.d(TAG, "Services discovery is successful");
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt _gatt, BluetoothGattCharacteristic _characteristic) {
            super.onCharacteristicChanged(_gatt, _characteristic);

            Log.d(TAG, "characteristic changed: " + _characteristic.getUuid().toString());
            readCharacteristic(_characteristic);
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt _gatt, BluetoothGattCharacteristic _characteristic, int _status) {
            super.onCharacteristicWrite(_gatt, _characteristic, _status);
            if (_status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "Characteristic written successfully");
            } else {
                Log.e(TAG, "Characteristic write unsuccessful, status: " + _status);
                disconnectGattServer();
            }
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            super.onCharacteristicRead(gatt, characteristic, status);
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d(TAG, "Characteristic read successfully");
                readCharacteristic(characteristic);
            } else {
                Log.e(TAG, "Characteristic read unsuccessful, status: " + status);
                // Trying to read from the Time Characteristic? It doesn't have the property or permissions
                // set to allow this. Normally this would be an error and you would want to:
                // disconnectGattServer();
            }
        }

        // Log the value of the characteristic
        // @param characteristic
        private void readCharacteristic(BluetoothGattCharacteristic _characteristic) {
            byte[] msg = _characteristic.getValue();
            Log.d(TAG, "read: " + msg.toString());
        }
    }

    // control BLE device
    private String controlBLE() {
        Log.d(TAG, "starting control");
        // check connection
        if (!connected_) {
            Log.e(TAG, "Error. Failed to control due to no connection");
            return "Failed to control due to no connection";
        }
        // find command characteristics from the GATT server
        BluetoothGattCharacteristic cmd_characteristic = BluetoothUtils.findCommandCharacteristic(ble_gatt_);
        // disconnect if the characteristic is not found
        if (cmd_characteristic == null) {
            Log.e(TAG, "Error. Unable to find cmd characteristic");
            disconnectGattServer();
            return "Error. Unable to find cmd characteristic";
        }
        // start control
        startControl(cmd_characteristic, 2);

        return CONTROL_MSG;
    }

    // Start control BLE device
    // @param cmd_characteristic command characteristic instance
    // @param program_id program id
    private void startControl(BluetoothGattCharacteristic _cmd_characteristic, final int _program_id) {
        // build protocol
        byte[] cmd_bytes = new byte[6];
        cmd_bytes[0] = 2;
        cmd_bytes[1] = 7;
        cmd_bytes[2] = (byte) (_program_id);
        cmd_bytes[3] = 0;
        cmd_bytes[4] = 0;
        cmd_bytes[5] = 0;

        // set values to the characteristic
        _cmd_characteristic.setValue(cmd_bytes);
        // write the characteristic
        boolean success = ble_gatt_.writeCharacteristic(_cmd_characteristic);
        // check the result
        if (success) {
            Log.d(TAG, "Wrote: 02 07 " + _program_id + " 00 00 00");
            // set flag
            control_started_ = true;
        } else {
            Log.e(TAG, "Failed to start control");
        }
    }
}

이것으로 안드로이드 라이브러리 구현은 끝났습니다.

AndroidWrapper.cs 업데이트

아직 조금 남았습니다. 안드로이드 라이브러리에서 구현한 것을 Unity에서 호출하는 부분을 구현하면 됩니다. 이건 정말 쉽죠. 이미 구현 로직을 다 만들어 놔서 그냥 함수만 호출해 주면 됩니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class AndroidWrapper : MonoBehaviour
{
    // android object
    private AndroidJavaObject AndroidObject = null;
    // text 
    public Text Message;

    private AndroidJavaObject GetJavaObject()
    {
        if (AndroidObject == null)
        {
            AndroidObject = new AndroidJavaObject("club.etain.blelibrary.BLEControl");
        }
        return AndroidObject;
    }

    // Use this for initialization
    void Start()
    {
        // Retrieve current Android Activity from the Unity Player
        AndroidJavaClass jclass = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject activity = jclass.GetStatic<AndroidJavaObject>("currentActivity");

        // Pass reference to the current Activity into the native plugin,
        // using the 'setActivity' method that we defined in the ImageTargetLogger Java class
        GetJavaObject().Call("setActivity", activity);

        // connect to the paired BLE device
        Message.text = GetJavaObject().Call<string>("connectExternal");
    }

   // Called when a user clicks "Control BLE" Button
    public void OnClickControlBLE()
    {
        // control the BLE device
        Message.text = GetJavaObject().Call<string>("controlExternal");
    }

    // Update is called once per frame
    void Update()
    {

    }
}

이렇게 수정한 후에 Button1의 Pointer Click 이벤트에 위에서 작성한 스크립트 함수를 호출하도록 합니다. 이 때 주의할 것은 Object 필드에 스크립트 파일을 드래그 앤 드랍하는게 아니라 PluginScript 객체를 드래그 앤 드랍해야 합니다. 아래 그림처럼요. 그런 다음 Function 필드에 방금 수정한 AndroidWrapper.cs의 OnClickControlBLE 함수를 연결시킵니다.
image.png

결과 확인

VR앱이라 이번에도 결과확인은 직접 VR을 쓰고 해야 합니다. 여기서는 Button이 클릭됐을 때 로그 메시지로 결과를 확인합니다.
image.png

Unity에서 Build and Run으로 안드로이드 폰에 앱을 전송, 설치 한 후 VR을 쓰고 "Control BLE" 버튼을 눌렀을 때 제대로 동작하는 것을 확인했습니다. 단, 안드로이드 폰과 페어링된 장치가 하나 뿐이어야 하는 제약이 있습니다. 이건 나중에 해결해 보도록 하겠습니다.

참고로, 이글과 같이 VR앱 개발하는데 안드로이드 라이브러리 개발이 동시에 필요하다면, 개발 속도를 올리기 위해, 별도의 VR앱이 아닌 별도의 안드로이드 라이브러리 프로젝트를 만드는 것이 좋습니다. 이렇게 하면 Toast 메시지도 띄울 수 있어서 개발이 좀 편합니다. 그래도 안드로이드의 기능을 이용하는 것이기 때문에 Unity에서 Build and Run 과정을 거쳐야만 합니다. 이게 가장 시간이 많이 걸리는 작업입니다.


오늘의 실습: 불르투스 말고 다른 안드로이드 기능을 라이브러리에 추가해 보세요. 볼륨 컨트롤 같은거요.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

@etainclub You have received a 100% upvote from @intro.bot because this post did not use any bidbots and you have not used bidbots in the last 30 days!

Upvoting this comment will help keep this service running.