Raspberry Pi용 안드로이드 앱

이전 IoT 튜토리얼에서는 Raspberry Pi를 설정하는 방법과 Back4App API를 사용하여 서버에 객체를 저장하고 쿼리 및 라이브 쿼리를 수행하기 위해 Parse 서버에 연결하는 방법을 다루었습니다.

이제 Raspberry 쪽에서 수행한 모든 작업을 앱 쪽에서 재현하는 방법을 알아보겠습니다. 이 튜토리얼에서는 앞서 구성한 IoT 디바이스와 상호작용하는 안드로이드 앱에 대해 설명합니다. Parse 서버 측에서 앱은 객체를 작성하고 쿼리 및 라이브 쿼리를 수행하는 등 Raspberry와 동일한 작업을 수행합니다. 이러한 기능은 IoT 애플리케이션을 개발할 계획이 없더라도 유용하게 사용할 수 있습니다!

Parse는 IoT 애플리케이션을 만들 수 있는 놀라운 프레임워크임이 입증되었습니다. 2020년에는 GraphQL API 프로토콜이 추가되어 데이터를 검색하는 더 나은 방법을 제공합니다.

원하는 앱을 개발하기 위한 첫 단계로 코드를 제공합니다.

전제 조건

유일한 전제 조건은 안드로이드 퀵스타트 튜토리얼을 완료하는 것입니다. 이를 위해서는 아래 링크를 클릭해야 합니다.

https://www.back4app.com/docs/pages/android/how-to-build-an-android-app-on-back4app

다음은 이 IoT 시리즈 튜토리얼의 섹션 3에서 필요한 LiveQuery에 대한 튜토리얼입니다. 하지만 이 튜토리얼을 미리 완료할 필요는 없습니다.

https://docs.back4app.com/docs/android/live-query/

튜토리얼을 진행하는 동안 궁금한 점이 있으면 아래 링크의 Android용 공식 Parse 가이드를 참조하여 문제를 해결할 수 있습니다.

http://docs.parseplatform.org/android/guide/

섹션 1: 앱 생성 및 Back4App 연결의 기본 사항

이 프로젝트에서는 클래스 이름을 “MainActivity.java”로 지정합니다. 다음은 시작하는 데 도움이 되는 기본 코드입니다.

public class MainActivity extends AppCompatActivity {
   오버라이드
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
     // 앱에 툴바를 삽입합니다. menu_main_activity.xml 파일에서 이 바 인에 대한 추가 설명 
      툴바 툴바 = (툴바) findViewById(R.id.toolbar);
      setSupportActionBar(toolbar); 
   }
   오버라이드
   public boolean onCreateOptionsMenu(Menu 메뉴) {
     // 메뉴가 있는 경우 액션 바에 항목을 추가합니다. 
      getMenuInflater().inflate(R.menu.menu_main_activity, menu);
     참을 반환합니다;
   }
   오버라이드
   public boolean onOptionsItemSelected(MenuItem item) {
     // 여기에서 액션 바 항목 클릭을 처리합니다. 액션 바는
      // 홈/업 버튼 클릭을 자동으로 처리합니다.
      // 안드로이드 매니페스트.xml에서 상위 활동을 지정하기만 하면 됩니다. 
     int id = item.getItemId();
     //검사 SimplifiableIfStatement 
     if (id == R.id.action_settings) {
        return true;
      }
      return super.onOptionsItemSelected(item);
   }
}

다음 임포트를 추가해야 할 수도 있습니다:

import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
android.support.v7.app.AppCompatActivity를가져옵니다 ;
import android.support.v7.widget.Toolbar;
import android.view.View;
android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;
import android.widget.Button;
import android.widget.ToggleButton;
android.view.View.OnClickListener를가져옵니다 ;

레이아웃 XML 파일에 다음 코드를 추가하세요:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:id="@+id/myCoordinatorLayout"
   tools:context="com.example.guilherme.myiotapplication.MainActivity">
   <includelayout="@layout/content_main_activity" />
   <android.support.design.widget.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay">
      <android.support.v7.widget.Toolbar
          android:id="@+id/toolbar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:background="?attr/colorPrimary"
          app:popupTheme="@style/AppTheme.PopupOverlay" />
   </android.support.design.widget.AppBarLayout>
</android.support.design.widget.CoordinatorLayout>

앱을 Back4App에 연결하려면 클래스의 onCreate() 함수 내에 아래 코드를 추가하세요.

// Parse 서버 초기화
Parse.initialize(new Parse.Configuration.Builder(this)
    .applicationId("YOUR_APP_ID") // 핵심 설정의 "기능"에서 
    .clientKey("YOUR_CLIENT_KEY") // 핵심 설정의 "기능" 항목에서 
    .server("https://parseapi.back4app.com/")
    .build()
);

빠른 시작의 단계별 지침에 따라 앱에 인터넷 액세스 권한을 부여하는 것을 잊지 마세요.

섹션 2: Parse 및 앱에 표시할 개체 저장 및 검색하기

아래에 임포트를 추가하여 시작하세요.

com.parse.FindCallback을가져옵니다;
import com.parse.Parse;
import com.parse.ParseException;
import com.parse.ParseObject;
com.parse.ParseQuery를가져옵니다 ;
com.parse.SaveCallback을가져옵니다 ;
org.json.JSONException을가져옵니다 ;
import org.json.JSONObject;
java.util.List를가져옵니다 ;

이 섹션에서는 버튼 클릭으로 “CommandGPIO1” 클래스의 객체를 저장합니다. 모든 Parse 객체에는 objectId, createdAt , updatedAt의 세 가지 기본 속성이 있습니다. 우리 클래스에는 콘텐츠와 대상이라는 두 가지 추가 속성이 더 있습니다. 콘텐츠의 값은 “켜짐” 또는 “꺼짐”이며 LED로 전송할 상태를 나타냅니다. 대상의 경우 모든 객체에는 “command”가 포함되지만 LED마다 다른 문자열을 정의할 수 있습니다.

예를 들어

두 개의 버튼을 만들고 각각 다른 값을 콘텐츠에 기록합니다. 버튼을 만들려면 레이아웃 XML 파일에 다음 코드를 추가합니다:

<버튼
   android:id="@+id/buttonSendOn"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   안드로이드:레이아웃_중력="상단|시작"
   android:layout_marginBottom="0dp"
   android:layout_marginTop="120dp"
   android:layout_marginEnd="0dp"
   android:layout_marginRight="0dp"
   android:layout_marginLeft="16dp"
   android:layout_marginStart="16dp"
   android:text="Send ON"/>

클래스 파일에서 버튼을 만드는 것은 작성하는 것만큼이나 쉽습니다:

버튼 buttonOn = (버튼) findViewById(R.id.buttonSendOn);

안드로이드 프로그래밍 초보자라면 “findViewById” 인수에 “buttonSendOn”이 포함되어 있으며, 이것이 XML에서 정의한 Android:Id라는 점에 유의하세요.

이 버튼이 클릭될 때 객체를 Parse 서버에 저장하기를 원합니다. 이를 위해 다음 코드를 추가합니다:

buttonOn.setOnClickListener( new OnClickListener(){{
   @Override
  public void onClick(final View view) {
     // 새 객체를 생성하고 적절한 속성을 할당합니다. 
      ParseObject command = new ParseObject("CommandGPIO1");
      command.put("content","on");
      command.put("destination", "command");
      command.saveInBackground(new SaveCallback(){{
      @Override
     public void done(ParseException e){{
         Snackbar.make(view, "Sent ON to Output", Snackbar.LENGTH_LONG )
         .setAction("Action", null).show();
      }
   }); 
});

onClick 콜백 함수 내에 원하는 함수를 추가할 수 있습니다. 여기서는 “CommandGPIO1” 클래스 객체를 생성하고 콘텐츠를 “on”으로, 대상을 “command”로 설정합니다.

이 클래스를 Parse 대시보드에서 미리 정의할 필요가 없다는 점을 알아두면 유용합니다! 나중에 새 속성을 추가하거나 클래스 이름을 변경하는 경우 코드 작업만 하면 변경 사항이 대시보드에 자동으로 업데이트됩니다.

이 시점에서 앱을 테스트하고 객체가 생성되는지 확인해야 합니다!

위의 그림과 같이 저장 콜백은 화면 하단에 가벼운 피드백인 스낵바를 표시합니다.

이 코드를 복사하여 “off”로 개체를 쓰는 버튼을 만듭니다. 레이아웃 XML에서 다음 줄을 변경합니다.

android:id="@+id/buttonSendOff"
android:layout_gravity="top|center"
android:layout_marginLeft="0dp"
android:layout_marginStart="0dp"
android:text="보내기"/>

새 버튼을 만들고 콘텐츠가 작성되는 줄을 변경합니다:

버튼 buttonOff = (버튼) findViewById(R.id.buttonSendOff);
command.put("content","off");

이제 Parse 서버에서 제대로 작동하고 있으므로 앱 사용자에게 영구적인 피드백을 제공하고자 합니다. 이를 위해 아래와 같이 편집 텍스트 요소를 사용하겠습니다.

레이아웃 XML에서 이 요소를 정의합니다:

<EditText
    android:id="@+id/status1Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="top|start"
    android:maxLines="2"
    android:ems="10"
    android:singleLine="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

클래스의 onCreate() 범위 내에 다음 코드를 추가합니다:

// 앱에서 명령을 수신하는 출력 핀의 상태를 표시하기 위한 텍스트 요소를 정의합니다.
// 명령은 나중에 이 코드에서 설명하는 버튼으로 전송됩니다.
final EditText textOutPin = (EditText) findViewById(R.id.status1Text);
textOutPin.setFocusable(false);
textOutPin.setClickable(true);
textOutPin.setText("출력 핀의 로딩 상태...");

첫 번째 줄은 객체를 생성합니다. 두 번째 줄은 사용자가 편집할 수 없도록 만듭니다. 세 번째 줄은 클릭할 수 있도록 하여 복사하여 붙여넣을 수 있도록 합니다. 네 번째 줄은 초기 텍스트를 설정합니다.

Parse에 저장된 마지막 “CommandGPIO1” 객체의 콘텐츠 필드를 이 EditText 객체에 쓰고 싶습니다. 코드 내의 일부 로직을 사용하여 이 작업을 수행할 수도 있지만, 실제로는 ParseQuery를 수행하여 개체를 검색하는 것이 더 현실적이고 강력한 결과를 제공하므로 Parse에서 개체를 검색하겠습니다.

아래 코드는 Parse 쿼리를 선언하고, 매개변수를 설정하고, 결과를 출력합니다.

ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
queryOut.whereEqualTo("destination", "command");
queryOut.addDescendingOrder("createdAt");
queryOut.setLimit(1);
queryOut.findInBackground(new FindCallback<ParseObject>() { {
   @Override
  public void done(List<ParseObject> objects, ParseException e) {
     if (e == null){
         textOutPin.setText("출력은 " + objects.get(0).getString("content"));
      }
     else{
         textOutPin.setText("Error: " + e.getMessage());
      }
   }
});

첫 번째 줄은 쿼리를 생성합니다. 두 번째 줄은 대상 필드에 “command”가 포함된 개체만 선택하도록 제약 조건을 추가합니다. 세 번째 줄은 최신 개체부터 정렬합니다. 네 번째 줄은 결과를 하나로 제한하여 최신 개체를 검색하도록 합니다.

다섯 번째 줄은 쿼리를 활성화하고 쿼리가 완료되면 콜백 함수를 호출합니다. 여기서는 검색된 객체의 내용을 이전에 정의한 EditText 객체에 씁니다.

편집 텍스트 객체를 선언한 직후에 이 코드를 추가하여 앱이 열릴 때 쿼리가 수행되고, 새 객체를 생성할 때 자동으로 텍스트를 업데이트하기 위해 Parse에서 새 객체를 저장할 때 SaveCallback에서도 쿼리가 수행되도록 합니다.

이 시점에서 앱은 다음 화면 캡처와 같이 작동해야 합니다.

마지막으로 사용자가 원할 때마다 위의 쿼리를 수행할 수 있도록 새로 고침 버튼을 추가합니다. 다른 스타일의 버튼, 즉 플로팅 액션 버튼을 사용하여 수행합니다.

레이아웃 XML 파일에 이 코드를 추가합니다:

<android.support.design.widget.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="top|end"
    android:layout_marginBottom="0dp"
    android:layout_marginTop="120dp"
    android:layout_marginEnd="16dp"
    android:layout_marginRight="16dp"
    android:layout_marginLeft="0dp"
    android:layout_marginStart="0dp"
    app:srcCompat="@android:drawable/ic_popup_sync" />

이제 클래스의 onCreate() 범위 내에 다음 코드를 추가합니다:

// 새로고침 버튼을 눌러 요청 시 출력 상태를 가져옵니다.
// 여기에서도 첫 번째와 동일한 쿼리가 수행됩니다.
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener(){{
    @Override
   public void onClick(final View view){
        ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1");
        queryOut.whereEqualTo("destination", "command");
        queryOut.addDescendingOrder("createdAt");
        queryOut.setLimit(1);
        queryOut.findInBackground(new FindCallback<ParseObject>() { {
            @Override
           public void done(List<ParseObject> objects, ParseException e) {
              if (e == null){
                   textOutPin.setText("출력은 " + objects.get(0).getString("content"));
               }
              else{
                   textOutPin.setText("Error: " + e.getMessage());
               }
               Snackbar.make(view, "업데이트된 출력 상태", Snackbar.LENGTH_LONG ).setAction("Action", null).show();
            }
        });
     }
 });

이제 Parse Server에 객체를 저장하고, 객체에서 정보를 검색하여 앱에 표시할 수 있습니다! 앱이 아래 그림과 같이 작동해야 합니다:

섹션 3: 라이브 쿼리를 사용하여 실시간 이벤트 수신 및 앱에 표시하기

이 섹션에서는 Parse 대시보드에서 “InputGPIO” 클래스를 실시간으로 모니터링하고 사용자에게 객체의 “콘텐츠”를 표시합니다.

레이아웃 XML에서 새 EditText를 정의하는 것으로 시작합니다:

<EditText
    android:id="@+id/status2Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="가운데|시작"
    android:layout_marginBottom="0dp"
    android:maxLines="2"
    android:ems="10"
    android:singleLine="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

onCreate() 범위에서 새 EditText 객체를 생성하고 그 직후에 초기화 쿼리(아직 라이브가 아님)를 수행합니다.

최종 편집 텍스트 textInPin = (편집 텍스트) findViewById(R.id.status2Text);
textInPin.setFocusable(false);
textInPin.setClickable(true);
textInPin.setText("입력 핀의 로딩 상태...");
// 핀의 마지막 저장 상태를 얻기 위한 초기 (비 라이브) 쿼리
ParseQuery<ParseObject> queryIn = ParseQuery.getQuery("InputGPIO");
queryIn.whereEqualTo("type", "interrupt");
queryIn.addDescendingOrder("createdAt");
queryIn.setLimit(1);
queryIn.findInBackground(new FindCallback<ParseObject>() { {
    @Override
   public void done(List<ParseObject> objects, ParseException e) {
       if (e == null){
            textInPin.setText("입력은 " + objects.get(0).getString("content"));
        }
       else{
            textInPin.setText("오류입니다: " + e.getMessage());
        }
    }
});

“InputGPIO”의 속성은 콘텐츠와 유형이며, 후자는 “CommandGPIO” 클래스에서 대상 역할을 한다는 점에 유의하세요.

이 시점에서 앱은 아래 그림과 같은 모습이어야 합니다.

이제 라이브 쿼리를 구현하겠습니다. 여기에는 앞서 언급한 튜토리얼이 필요합니다.

https://docs.back4app.com/docs/android/live-query/

라이브 쿼리 튜토리얼의 1단계에 따라 라이브 쿼리를 활성화할 클래스를 선택하고 하위 도메인 이름을 정의하세요. 이 기능을 사용 설정하려면 “InputGPIO” 클래스를 만들어야 할 수도 있습니다(아직 없는 경우). 튜토리얼의 2단계에 따라 Android에서 실시간 쿼리 클라이언트를 설정합니다.

다음 임포트를 추가하는 것을 잊지 마세요:

tgio.parselivequery.BaseQuery를가져옵니다;
tgio.parselivequery.LiveQueryClient를가져옵니다 ;
tgio.parselivequery.LiveQueryEvent를가져옵니다 ;
tgio.parselivequery.Subscription을가져옵니다 ;
tgio.parselivequery.interfaces.OnListener를가져옵니다 ;

다음 코드를 추가하여 LiveQuery를 초기화하고 매개변수를 정의합니다.

// 라이브 쿼리 초기화
LiveQueryClient.init("wss:YOUR_SUBDOMAIN_NAME.back4app.io", "YOUR_APP_ID", true );
LiveQueryClient.connect();
// LiveQuery의 속성 정의 
 구독 subIn = 새로운 BaseQuery.Builder("InputGPIO")
    .where("type","interrupt")
    .addField("content")
    .build()
    .subscribe();

구독을 정의할 때 유형 속성이 “interrupt”인 요소만 가져와서 필드 콘텐츠를 검색합니다.

이제 다음 코드를 추가하여 구독으로 정의된 객체가 Parse 서버에서 생성될 때마다 응답하도록 합니다.

// LiveQuery CREATE 이벤트를 수신하기 시작하여 해당 콘텐츠를 가져와서 작성합니다.
subIn.on(LiveQueryEvent.CREATE, new OnListener() {
    @Override
   public void on(JSONObject object) {
       try {
           final String subInContent = (String) ((JSONObject) object.get("object")).get("content");
            runOnUiThread(new Runnable() {
                @Override
               public void run() {
                   textInPin.setText("입력은 " + subInContent);
                   Snackbar.make(findViewById(R.id.myCoordinatorLayout), "입력 핀이 " + subInContent.toUpperCase(), Snackbar.LENGTH_LONG ).setAction("Action", null).show();
                }
            });
        } catch (JSONException e){
            e.printStackTrace();
        }
    }
});

객체의 콘텐츠 필드가 새로운 EditText 요소에 표시됩니다.

여기까지는 IoT 디바이스에서 전송한 모든 입력을 표시하기에 충분합니다. 그러나 앱이 어떻게 작동하는지 확인하기 위해 다른 종류의 버튼, 즉 “InputGPIO” 클래스 객체를 생성하고 이를 Parse Server에 저장하는 토글 버튼을 구현하겠습니다.

레이아웃 XML 파일에 다음 코드를 추가합니다:

<ToggleButton
    android:id="@+id/toggleTest"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center|end"
    android:layout_marginBottom="0dp"
    android:layout_marginTop="100dp"
    android:layout_marginEnd="0dp"
    android:layout_marginRight="0dp"
    android:layout_marginLeft="0dp"
    android:layout_marginStart="0dp"
    android:textOn = "입력 켜짐"
    android:textOff = "입력 꺼짐"/>

MainActivity 클래스의 onCreate() 함수 범위에서 아래 코드를 추가합니다.

// 토글 버튼은 하드웨어가 생성할 객체를 에뮬레이트하기 위해 여기에 있습니다.
ToggleButton toggleTest = (ToggleButton) findViewById(R.id.toggleTest);
toggleTest.setOnClickListener( new OnClickListener(){{
    @Override
   public void onClick(final View view) {
        ParseObject command = new ParseObject("InputGPIO");
       if(gpioStatusTest.equals("off"))
           gpioStatusTest = "on";
       else
            gpioStatusTest = "off";
        command.put("type","interrupt");
        command.put("content", "From Toggle: " + gpioStatusTest);
        command.saveInBackground(new SaveCallback(){{
            @Override
           public void done(ParseException e){{
                Snackbar.make(view, "변경된 입력 핀", Snackbar.LENGTH_LONG ).setAction("Action", null).show();
            }
        });
    }
});

또한 메인 액티비티 범위에서 문자열을 선언하세요.

String gpioStatusTest = "off";

앱이 완성되었습니다! 이 튜토리얼이 끝나면 앱이 아래 그림과 매우 유사하게 보일 것입니다.

IoT 애플리케이션을 계속 개발하고 싶으시다면, 단계별 가이드인 IoT 시리즈를 읽어보시고 Raspberry Pi를 설정하는 기본 사항과 JavaScript를 사용하여 Parse 서버에서 객체를 검색하고 저장하는 등 특정 애플리케이션을 설정하는 방법을 알려주세요.

코드는 다음 링크에서 확인할 수 있습니다:

https://github.com/back4app/iot-raspberry-node

참고자료

Raspberry Pi를 Parse 서버에 연결하기

Raspberry Pi 설정하기

Android 앱과 IoT 기기의 상호작용은 어떻게 하나요?

섹션 1: 앱 생성 및 서버 측 연결의 기본 사항
섹션 2: Parse에서 객체 저장 및 검색, 앱에 표시
섹션 3: 라이브 쿼리를 사용하여 실시간 이벤트 수신 및 앱에 표시


Leave a reply

Your email address will not be published.