이번에 버전업 중에 사용한 안드로이드 스튜디오 버전.
최신 버전은 아니지만, 일단 회사 데스크톱에 설치되어 있는 에디터 버전이 이렇기 때문에 얘 기준으로 세팅을 맞췄습니다.
Android beacon library 를 사용한 유니티 플러그인을 만든건 2년이지만, 그나마 최근이라고 해야하나?
당시 기기의 OS 레벨에 맞춰야 하는 상황과 스토어에서 요구하는 앱의 sdk 최저 레벨에 관련하여 대응해야하는 상황이 있어서 어느정도 손을 본 버전입니다.

올바르게 사용했다고는 생각들지는 않지만, 동작시켜서 원하는 기능 수행에 주목적을 두고 만들었습니다.

앞선 글들을 통해서 간단하게 AAR 파일을 만들어서 유니티에 동작을 시켜보셨기 때문에, 그 기준으로 진행을 해보겠습니다.

아래 스샷들은 해당 프로젝트를 하면서 추가해줘야하는 권한 및 필요한 gradle 작성 관련입니다.
없어져도 되는 부분도 있긴 할텐데, 쓸데없는 노파심에 예전에 넣어둔걸 그대로 냅둔 부분도 있긴 합니다.

 

- 코드 -

package com.example.beaconplugin;

import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Debug;
import android.util.Log;

import com.unity3d.player.UnityPlayer;

import org.altbeacon.beacon.Beacon;
import org.altbeacon.beacon.BeaconManager;
import org.altbeacon.beacon.BeaconParser;
import org.altbeacon.beacon.InternalBeaconConsumer;
import org.altbeacon.beacon.MonitorNotifier;
import org.altbeacon.beacon.RangeNotifier;
import org.altbeacon.beacon.Region;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AndroidNativeBeacon implements InternalBeaconConsumer
{
    private static final String LOG_TAG = "Android";

    private BeaconManager beaconManager;
    private Beacon beacon;
    private  Map<String, Double> bDistances = new HashMap<String, Double>();
    private float catchBeaconDistance = 1.2f;

    private String gameObject;
    private String nitfyCallback;
    private String errorCallback;
    //    private String enterCallback;
    //    private String exitCallback;
    public AndroidNativeBeacon()
    {
        Log.d(LOG_TAG, "AndroidNativeBeacon");
        beaconManager = null;
    }

    public void Init(Context context, String gameObject, String nitfyCallback, String errorCallback)//, String enterCallback, String exitCallback)
    {
        Log.d(LOG_TAG, "AndroidNativeBeacon Init");
        // beaconManager
        beaconManager = BeaconManager.getInstanceForApplication(context);
        beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("m:2-3=0215,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));

        this.gameObject = gameObject;
        this.nitfyCallback = nitfyCallback;
        this.errorCallback = errorCallback;
//        this.enterCallback = enterCallback;
//        this.exitCallback = exitCallback;
    }

    public void SetCatchDistance(float _value)
    {
        catchBeaconDistance = _value;
        Log.d(LOG_TAG, "AndroidNativeBeacon.SetCatchDistance : " + catchBeaconDistance);
    }

    public void OnNotify(String data)
    {
        Log.i(LOG_TAG, "AndroidNativeBeacon.OnNotify to gameObject=" + gameObject);
        UnityPlayer.UnitySendMessage(gameObject, nitfyCallback, data);
    }

    public void OnError(String errorMessage)
    {
        Log.i(LOG_TAG, "AndroidNativeBeacon.OnError message:" + errorMessage);
        UnityPlayer.UnitySendMessage(gameObject, errorCallback, errorMessage);
    }

//    public void OnEnterBeacon()
//    {
//        Log.i(LOG_TAG, "AltBeaconClient.OnEnterBeacon to gameObject=" + gameObject);
//        UnityPlayer.UnitySendMessage(gameObject, enterCallback, "");
//    }
//
//    public void OnExitBeacon()
//    {
//        Log.i(LOG_TAG, "AltBeaconClient.OnExitBeacon to gameObject=" + gameObject);
//        UnityPlayer.UnitySendMessage(gameObject, exitCallback, "");
//    }

    @Override
    public void onBeaconServiceConnect()
    {
        Log.d(LOG_TAG, "AndroidNativeBeacon.onBeaconServiceConnect");

        beaconManager.addMonitorNotifier(new MonitorNotifier() {
            @Override
            public void didEnterRegion(Region region) {
                Log.i(LOG_TAG, "didEnterRegion - 비콘 연결됨");
                beacon = null;
                //EnterIBeacon();
            }

            @Override
            public void didExitRegion(Region region) {
                Log.i(LOG_TAG, "didExitRegion - 비콘 끊김");
                beacon = null;
                NotiyIBeacon(null);
                //ExitIBeacon();
            }

            @Override
            public void didDetermineStateForRegion(int state, Region region) {
                Log.i(LOG_TAG,  "I have just switched from seeing/not seeing beacons: "+state);
            }
        });

        beaconManager.addRangeNotifier(new RangeNotifier() {
            @Override
            public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
                // 검출한 비콘의 정보를 전부 로그에 써내다
                if(beacons.size() > 0)
                {
                    Log.i(LOG_TAG, "didRangeBeaconsInRegion - 비콘들 검출함." + beacons.size());
                    NotiyIBeacon(beacons);
                }
            }
        });

        try {
            beaconManager.startMonitoring(new Region("unique-id-001", null, null, null));
            // 비콘 정보 감시를 개시
            beaconManager.startRangingBeacons(new Region("unique-id-001", null, null, null));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void Bind()
    {
        Log.d(LOG_TAG, "AndroidNativeBeacon.Bind");
        AltBeaconManagerBind(BindType.Bind);
    }

    public void Unbind()
    {
        Log.d(LOG_TAG, "AndroidNativeBeacon.UnBind");
        AltBeaconManagerBind(BindType.Unbind);
    }

//    private boolean AltBeaconBindStatus = false;
    private void AltBeaconManagerBind(BindType type)
    {
        Log.d(LOG_TAG, "AndroidNativeBeacon.AltBeaconManagerBind type:" + type.toString());

        switch (type)
        {
            case Bind:
                Log.d(LOG_TAG, "AndroidNativeBeacon.AltBeaconManagerBind bind");
                beaconManager.bindInternal(this);
                break;
//                AltBeaconBindStatus = true;
//            case Auto:
//                if(AltBeaconBindStatus)
//                {
//                    if (clients.size() == 0) {
//                        Log.d(LOG_TAG, "AndroidNativeBeacon.AltBeaconManagerBind unbind");
//                        beaconManager.unbindInternal(this);
//                    }
//                    else {
//                        Log.d(LOG_TAG, "AndroidNativeBeacon.AltBeaconManagerBind bind");
//                        beaconManager.bindInternal(this);
//                    }
//                }
//                break;
            case Unbind:
                Log.d(LOG_TAG, "AndroidNativeBeacon.AltBeaconManagerBind unbind");
                beaconManager.unbindInternal(this);
//                AltBeaconBindStatus = false;
                break;
        }
    }

    private void NotiyIBeacon(Collection<Beacon> beacons)
    {
        if(beacons != null)
        {
            //발열이 심해서 거리 계산 하는 반복문을 따로 해보자.
            for(Beacon b : beacons)
            {
                Double _Value = new Double(calculateDistance_double(b));

                bDistances.put(b.getId1().toString(), _Value);
            }
            //발열이 심해서 거리 계산 하는 반복문을 따로 해보자.
            Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon beacons is null.");
            Log.d(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon catchBeaconDistance : " + catchBeaconDistance);
            for (Beacon b : beacons)
            {
                Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon inside forech");

                if (beacon == null)
                {
                    Double beaconDistance = null;

                    if(bDistances.get(b.getId1().toString()) != null)
                    {
                        beaconDistance = (Double) bDistances.get(b.getId1().toString());//calculateDistance(b);
                    }

                    if(beaconDistance != null)
                    {
                        if(beaconDistance.doubleValue() <= catchBeaconDistance)
                        {
                            Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon beacon is null. put in b data");
                            beacon = b;
                        }
                        else
                        {
                            beacon = null;
                        }
                    }
                    else if(beaconDistance == null)
                    {
                        Log.i(LOG_TAG, "beaconDistance is NULL");
                    }
                }
                else if (beacon != null)
                {
                    Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon beacon is not null.");

                    if (!beacon.getId1().toString().equals(b.getId1().toString()))
                    {
                        Double beaconDistance = null;
                        Double bDistance = null;

                        if(bDistances.get(beacon.getId1().toString()) != null)
                        {
                            beaconDistance = (Double) bDistances.get(beacon.getId1().toString());//calculateDistance(b);
                        }

                        if(bDistances.get(b.getId1().toString()) != null)
                        {
                            bDistance = (Double) bDistances.get(b.getId1().toString());//calculateDistance(b);
                        }

                        Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon UUID is not same");
                        Log.i(LOG_TAG, "beacon.getId1() : " + beacon.getId1() + " , b.getId1() : " + b.getId1());

                        if(beaconDistance != null && bDistance != null)
                        {
                            Log.i(LOG_TAG, "beacon.getId1().Dis : " + beaconDistance.doubleValue() + " , b.getId1().Dis : " + bDistance.doubleValue());

                            if(beaconDistance.doubleValue() > bDistance.doubleValue())//if (beacon.getDistance() > b.getDistance())
                            {
                                if(bDistance.doubleValue() <= catchBeaconDistance)
                                {
                                    Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon b.Distance is short. put in b data");
                                    beacon = b;
                                }
                                else
                                {
                                    beacon = null;
                                }
                            }
                        }
                        else if(beaconDistance != null && bDistance == null)
                        {
                            Log.i(LOG_TAG, "beacon.getId1().Dis : " + beaconDistance.doubleValue());

                            if(beaconDistance.doubleValue() <= catchBeaconDistance)
                            {
                                Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon beacon.Distance is short.");
                                Log.i(LOG_TAG, "bDistance is NULL");
                            }
                            else
                            {
                                beacon = null;
                            }
                        }
                        else if(beaconDistance == null && bDistance != null)
                        {
                            Log.i(LOG_TAG, "b.getId1().Dis : " + bDistance.doubleValue());

                            if(bDistance.doubleValue() <= catchBeaconDistance)
                            {
                                Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon b.Distance is short. put in b data");
                                Log.i(LOG_TAG, "beaconDistance is NULL");
                                beacon = b;
                            }
                            else
                            {
                                beacon = null;
                            }
                        }
                    }
                    else if (beacon.getId1().toString().equals(b.getId1().toString()))
                    {
                        Double beaconDistance = (Double) bDistances.get(beacon.getId1().toString());//calculateDistance_double(beacon);

                        if(beaconDistance.doubleValue() <= catchBeaconDistance)
                        {
                            Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyIBeacon UUID is same");
                            beacon = b;
                        }
                        else
                        {
                            beacon = null;
                        }
                    }
                }
            }
        }

        bDistances.clear();
        NotiyInform(beacon);
    }

    protected static double calculateDistance_double(Beacon b)
    {
        int measuredPower = b.getTxPower();
        double rssi = b.getRssi();

        if (rssi == 0)
        {
            Log.i(LOG_TAG, "beacon.getId1() : " + b.getId1().toString() + " , rssi : 0");

            return -1.0;
            // if we cannot determine distance, return -1.
        }
        double ratio = rssi*1.0/measuredPower;

        if (ratio < 1.0)
        {
            double other = Math.pow(ratio,10);

            Log.i(LOG_TAG, "beacon.getId1() : " + b.getId1().toString() + " , other : " + other);

            return Math.pow(ratio,10);
        }
        else
        {
            double distance = (0.89976) * Math.pow(ratio, 7.7095) + 0.111;

            Log.i(LOG_TAG, "beacon.getId1() : " + b.getId1().toString() + " , distance : " + distance);

            return distance;
        }
    }

    protected static Double calculateDistance_Double(Beacon b)
    {
        int measuredPower = b.getTxPower();
        double rssi = b.getRssi();

        if (rssi == 0)
        {
            return -1.0;
            // if we cannot determine distance, return -1.
        }
        double ratio = rssi*1.0/measuredPower;

        if (ratio < 1.0)
        {
            return Math.pow(ratio,10);
        }
        else
        {
            Double distance = (0.89976) * Math.pow(ratio, 7.7095) + 0.111;

            return distance;
        }
    }

    private void NotiyInform(Beacon b)
    {
        String data = "";
        // "UUID:" + b.getId1()
        // "major:" + b.getId2()
        // "minor:" + b.getId3()
        // "RSSI" + b.getRssi()
        // "Name" + b.getBluetoothName();
        // "Address" + b.getBluetoothAddress();

        if(b != null)
        {
            data = String.format("%s,%s,%s,%s,%s,%s,%s,%s", System.currentTimeMillis(), b.getId1(), b.getId2(), b.getId3(), b.getTxPower(), b.getRssi(), b.getBluetoothName(), b.getBluetoothAddress());
        }

        // 로그 출력
        Log.i(LOG_TAG, "AndroidNativeBeacon.NotiyInform " + data);

        // 각 클라이언트에 진찰한 비콘 정보를 브로드캐스트
//        for (int i = 0; i < clients.size(); i++)
//            clients.get(i).
        OnNotify(data);
    }

    private enum BindType
    {
        Auto,
        Bind,
        Unbind,
    };

    @Override
    public Context getApplicationContext() {
        return null;
    }

    @Override
    public void unbindService(ServiceConnection connection) {

    }

    @Override
    public boolean bindService(Intent intent, ServiceConnection connection, int mode) {
        return false;
    }
}

 

맨위의 package com.example.beaconplugin; 의 example 은 다들 아시다싶이, 회사명이 들어가는 자리 혹은 관련 명칭이 들어가는 곳이라서 example 으로 대체해놓았습니다.

 

코드는 예전에 참고로 찾아서 본 투케이2K 님의 https://kkh0977.tistory.com/3314 글을 보고 작업한 것을 이리저리 다른 글도 참고하면서 만든겁니다.

 

구문을 보시다보면, 본래는 없던 메서드들이 자리하고 있는 것으로 보이는게 있을겁니다.

제가 관련 프로젝트에서 필요한 기능을 추가하려고 구글링을 통해서 이리저리 찾은 메서드들을 집어넣은거라 가볍게 넘어가주시면 됩니다. 그리고 사용하지 않는 메서드들은 왜 남아있는지 의문이 드실수도 있는데, 제가 순수한 안드로이드 프로그래머가 아니라서 노파심에 사용한 예시 코드의 모습이 그대로 남아 있는 상태입니다.

 

준비가 다되었다면, 빌드탭에서 저 망치를 한번 눌러주시면 빌드를 진행하게 됩니다.

 

빌드를 진행하면 아래와 같은 진행상태가 나올겁니다.

 

 

빌드가 정상적으로 된다면 이전글에서 확인한 해당 경로에 aar 파일이 존재할텐데, 디버그 빌드로 나온 것을 확인하셨을 겁니다.

 

이것을 릴리즈로 빌드하려면 아래와 같이 우측의 탭을 눌러서 창을 나오게 한 다음에 재생 버튼 같은 것을 눌러주시면 됩니다.

 

 

그러면 아래와 같은 창이 하나가 나올텐데, 스샷에 나온것처럼 해당 부분을 더블클릭을 해주면 릴리즈 빌드가 실시 됩니다.

 

 

빌드가 끝나고 경로를 가보면 아래와 같이 aar 파일들이 나온 것을 확인 할 수 있습니다.

 

다음글은 이 aar 파일을 이용해서 유니티에서 비콘을 동작시켜보는 코드들과 사용법을 알려드리겠습니다.

 

아래 링크는 최근에 발견한 안드로이드 비콘 라이브러리 사용관련 깃허브 글입니다.
제 글보다는 이 글이 더 나은 것 같아서, 첨부했습니다.

https://github.com/happymoment-s/TIL/blob/master/Android/%5BAndroid%5D%5BBeacon%5D%20Beacon%20Library%20%EC%82%AC%EC%9A%A9%EB%B2%95(AltBeacon%2C%20Nordic%2C%20Estimote).md

올해도 너무 일에 치여서 살다보니까, 올 1월에 첫글 썼다가 말이 되어서야 두번째 글을 쓰게 되었습니다.

 

이게 직장인의 비애인가 보다 하네요.

 

이번 글은 딱히 복잡할 일이 없습니다.

 

이전에 만든 AAR을 이용해서, 토스 메시지를 띄워보는 것이 끝이죠.

 

이번의 목표는 만든 AAR을 유니티에서 어떻게 쓸 수 있는지 방법은 터득하는 것입니다.

 

유니티 에디터에 어느정도 익숙하다는 전제로 설명하도록 하겠습니다.

 

일단 유니티에 안드로이드 플렛폼 프로젝트 하나를 만들도록 합니다.

 

그리고 원하는 이름의 스크립트를 하나 만들고 아래와 같이 작성하도록 합니다.

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

public class EmptyClass : MonoBehaviour
{
    private AndroidJavaObject aar;
    private AndroidJavaClass unityPlayer;
    private AndroidJavaObject context;

    private void Awake()
    {
        // Context(Activity) 객체를 취득
        unityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        context = unityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        aar = new AndroidJavaObject("com.test.myappliaction.플러그인 만들 당시에 쓴 자바 클래스명");
        
        //aar에 Context 전달.
        aar.Call("setContext", context);
    }
    
    public void ShowToast(string message) 
    {
    	//aar에 만든 해당 토스트 메시지 창 호출 함수에 문자열 값을 전달.
    	aar.Call("ShowToast", message);
    }
}

 

Canvas에 버튼 하나를 생성해서 버튼 클릭시, 해당 함수가 호출되도록 작업을 해줍니다.

 

그리고 테스트용 APK를 하나 빌드하여 테스트 기기에 설치하여, 테스트를 해봅니다.

 

버튼 클릭을 하면, 아래와 같이 팝업 메시지가 뜨면 성공입니다.

 

 

 

다음은 Android beacon library 를 이용해서 비콘 신호를 받는 앱을 만드는 것을 진행해보려고 합니다.

 

유니티에서 쉽게 비콘 통신을 할려면, 유니티 에셋 스토어에서 돈 주고 에셋 하나 사서 하는게 가장 좋습니다.

 

거기서 파는 에셋 중에 좋은건 IOS 에서도 쓸 수 있기 때문에 범용성 아주 좋습니다.

 

정말 추후에 IOS 비콘 플러그인도 만들었으면 하긴 하는데... 제가 IOS는 단 1도 몰라서 어떻게 만들어야 할지 걱정입니다.

 

심지어 맥(MAC)도 있어야 가능하니..... 아무튼 다음 글에서 뵙겠습니다.

일에 정신없이 치여서 살다보니, 블로그가 있었다는 것도 잊고 살았네요.

 

그동안 했던 프로젝트의 가짓 수가 많았던 것은 아니지만, 블로그에 개재할만한 내용 혹은 할 수 없던 것들이 꽤나 있어서 정리를 차일피일 미루고 있었는데, 그나마 하나를 정리해서 올릴 수 있을 것 같아서 파트별로 정리해서 올릴 것 같습니다.

 

이제는 그동안 했던거 포트폴리오 정도로 정리는 아니겠지만, 잊어먹더라도 다음에 할 수 있게 참고용으로 남기도록 작업을 해야할 것 같습니다.

+ Recent posts