2016년 5월 11일 수요일

Arduino GPS, Altimeter (고도, 기압계) 로 놀기 - 2편 ('어떻게' 를 만들어 보기)

1편 에서 정리한 '어떻게' 는 이랬었다.
" google glass 와 arduino (GPS, Altimeter) 를 BLE 로 연결하고 걸어다니면서 '위/경/고도, 대기압, 온도, 시간, 방향, 속도' 정보를 google glass 에 표시한다. "
그래서 google glass app 을 하나 만들어 봤다.

app 을 만들기 전에 빵판에 GPS, Altimeter, HM-10, Arduino pro mini 를 동작하게 만들었다.


library example 에서 copy & paste 하여 간단하게 sketch 를 만들고,
#include <SoftwareSerial.h>
#include <TinyGPS.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BMP085_U.h>
TinyGPS gps;
SoftwareSerial ss(4, 3);
SoftwareSerial bb(8, 7);
Adafruit_BMP085_Unified bmp = Adafruit_BMP085_Unified(10085);

void setup()
{
ss.begin(9600);
bmp.begin();
}

void loop()
{
bool newData = false;
unsigned long chars;
unsigned short sentences, failed;

for (unsigned long start = millis(); millis() - start < 1000;) {
while (ss.available()) {
char c = ss.read();
if (gps.encode(c))
newData = true;
}
}

if (newData) {
float flat, flon;
unsigned long age;
gps.f_get_position(&flat, &flon, &age);

ss.end();
bb.begin(9600);
bb.print("0 ");
bb.print(flat == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flat, 5);
bb.print(" ");
bb.print(flon == TinyGPS::GPS_INVALID_F_ANGLE ? 0.0 : flon, 5);

sensors_event_t event;
bmp.getEvent(&event);
float temperature;
float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
if (event.pressure) {
bmp.getTemperature(&temperature);
bb.print("1 ");
bb.print(temperature);
bb.print(" ");
bb.print(bmp.pressureToAltitude(seaLevelPressure, event.pressure));
}
bb.end();
ss.begin(9600);
}

gps.stats(&chars, &sentences, &failed);
}
android-studio 를 실행하여 google glass app 을 하나 만든다.

Tizen 이나 Android 나 Application 을 만드는 것은 정말 익숙치 않은 작업이다.
이번 기회에 BLE 동작을 Tizen app 으로도 만들어 보고 Android app 도 만들어 보면서 개발 초기 진입 장벽을 비교해 보는 재미도 쏠쏠 했던것 같다.

개인적인 두 platform 의 장/단점을 적어 보자면,
"개발환경 세팅, guide 문서 찾기, sample code 확보" 에서는 역시 Android 가 단연 우월했고, (이정도면 당연히 Android '승' 이지만) C/C++ 에 좀 더 친화적인 Tizen 이 Code 작성에는 조금 유리 했다. 역시 초보에겐 sample code 와 google 검색량이 중요한 요소인것 같다.

각설하고 google glass app 코드를 설명하면, BLE 동작 방식은 동일하기 때문에 Gear S2 로 Car Door 열기 - 5편 (Gear S2 app 을 BLE 로 변경) 에서 작성한 순서 그대로 작성했다.
1. bluetooth 를 초기화 한다.
2. le device 를 scan 한다.
3. HM-10 에 gatt connect 를 한다.
4. gatt client 를 만든다.
5. 제공하는 service 를 검색한다.
6. 원하는 service 의 characteristic 을 찾는다.
7. 값을 read 해 본다.
1. bluetooth 를 초기화 한다.
public void onCreate(Bundle savedInstanceState) {
...
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();

...
}
2. le device 를 scan 한다. && 3. HM-10 에 gatt connect 를 한다.
...
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {

@Override public void onLeScan(final BluetoothDevice device,
                                    int rssi, byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override public void run() {
if (device.getName().equals("SWHOME_GPS")) {
mScanning = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
mBluetoothGatt = device.connectGatt(LocationActivity.this, true,
mGattCallback);
}
}
});
}
};
protected void onStart() {
...
mScanning = true;
mBluetoothAdapter.startLeScan(mLeScanCallback);
   ...
}
4. gatt client 를 만든다. (이건 타이젠만) && 5. 제공하는 service 를 검색한다. && 6. 원하는 service 의 characteristic 을 찾는다. && 7. 값을 read 해 본다.
>> android 에서는 BluetoothGattCallback 하나면 충분한다.
private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
@Override public void onConnectionStateChange(BluetoothGatt gatt, int status,
                                                   int newState) {
String intentAction;

if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
mConnectionState = STATE_CONNECTED;

} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
}
}

@Override public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
getSupportedGattServices();
setCharacteristicNotification(mNotifyCharacteristic, true);
}
}

@Override public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
Scanner scanner = new Scanner(characteristic.getStringValue(0).toString());

int idx= scanner.nextInt();

if (idx == 0) {
mLat = scanner.nextDouble();
mLon = scanner.nextDouble();
} else if (idx == 1) {
mTemp = scanner.nextDouble();
mAlt = scanner.nextDouble();
}

scanner.close();
}
};
여기까지 하면 HM-10 을 통해 위/경/고도 와 온도 정보를 받을 수 있다.
이제 이 정보를 사용해서 지도를 표시해 주면 모든 것이 끝이다.

layout 을 잡아 줄 때 아래처럼 ImageView 와 TextView 를 반반 갈라서 정해 줬기 때문에,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@color/black" >

<LinearLayout
      android:layout_width="fill_parent"
      android:layout_height="fill_parent"
      android:gravity="center_vertical|center_horizontal"
      android:orientation="horizontal">

<ImageView
        android:id="@+id/ivMap"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:gravity="left"
      android:layout_weight="1"
      android:text="Left Aligned"
      android:visibility="visible" />

<TextView
        android:id="@+id/tvLocation"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      android:visibility="visible"
      android:gravity="left" />

</LinearLayout>
</LinearLayout>
ImageView 에 지도 image 를 불러만 와 주면 된다.

2가지 option 이 있는데, google map 과 open street map 을 사용하는 것이다.
각각의 장단점은,

[google map]
장점 : 상세하다. 키왕짱.
단점 : 횟수 제한이 있다. 좀 쓰다보면 지도가 안나온다. key 를 받으면 되지만...
          국내에서는 길찾기가 되지 않는다. 

[open street map]
장점 : 무제한 무료다. 길찾기도 가능한다.
단점 : 조악하다.

일단, 아직은 정확히 무엇을 해 볼지 결정을 하지 않았기 때문에, 수시로 지도를 찍어 보는 open street map 을 구현을 해 두고, 정확한 지도가 필요할 때는 google map 을 사용하기로 한다.

각각의 사용법은 이렇다. 원할 때 바꿔 치기만 하면 된다.

[google map]
new ImageLoadingTask().execute("http://maps.googleapis.com/maps/api/staticmap?zoom="
 + mZoom + "&size=320x320&markers=color:red%7C"+ mLat + "," + mLon + "&sensor=false");
[open street map]
new ImageLoadingTask().execute("http://staticmap.openstreetmap.de/staticmap.php?center="
 + mLat + "," + mLon + "&zoom=" + mZoom + "&size=320x320&markers=" + mLat + "," + mLon 
+ ",ol-marker");
자!! 이제 실행 해 보자.
google glass 에서 capture
app 실행 첫 화면
google map 으로 실행한 모습
open street map 으로 실행한 모습
 멋지게 돌아간다~~

흠.. 여기서 오해를 하지 않도록 설명을 해야 하는 부분이 있는데, google glass 에는 기본적으로 GPS 및 여러가지 센서들이 장착되어 있고 쉽게 예제 코드로 구현할 수 있다.
The following Android sensors are supported on Glass:
The following Android sensors are not supported:
지금 여기서 하는 것은 이런 google glass 의 기본 센서를 사용하고자 함이 아니고, 순수하게 google glass 는 display 로만 사용하고 arduino 센서를 사용하고자 함이다. 

괜히 이쯤에서 '그냥 gogole glass 만 쓰면 안돼??' 라고 묻는 사람이 있을 수도 있다는 생각이 불현듯 들어서 정리를 했다. ㅎㅎ

위에 만든 코드에 marker 추가하면 이동간의 history 도 볼 수 있을 것이고, file logging 도 추가 해서 history 관리를 할 수 있다. 이 부분은 그리 어려운 것이 아니라 '뭘 할지' 가 결정이 되면 만들어 보기로 한다.

그런데.. 여기까지 했는데도 '뭘 할지' 가 잘 떠오르지 않는다. 가족들의 위치를 map 에 같이 표시해 주거나, 등산용 GPS logging 을 만들거나, 주위 POI (Point of Interest) 를 보여주는 것 같은 일상정인 Location base 의 응용을 생각해 봤는데 arduino 만의 특색을 살리기에는 뭔가 부족하다.

참 어렵네.... '뭘 할지' 에 대한 결정은 3편으로 넘겨야 겠다. 블로그를 시작한 후 주제를 정하지 못한 전대미문의 사건이지만 안되는 건 안되는 거니까.

일단 만들어 놓은 걸 열심히 쓰다보면 좋은 생각이 떠오르겠지....

댓글 없음: