適用於 Raspberry Pi 的 Android 應用程式

在之前的物联网教程中,我们介绍了如何设置Raspberry Pi,以及如何使用 Back4AppAPI 将其连接到 Parse 服务器,从而将对象保存到服务器并执行查询和实时查询。

现在,我们将介绍如何将Raspberry上的所有操作复制到应用程序上。在本教程中,我们将介绍一个与之前配置的物联网设备进行交互的 Android 应用程序。在 Parse 服务器端,应用程序执行与Raspberry相同的任务:写入对象、执行查询和实时查询。请注意,即使你不打算开发物联网应用程序,这些功能也可能有用!

事实证明,Parse 是开发物联网应用的绝佳框架。2020 年,随着 GraphQL API 协议的加入,它将提供更好的数据检索方式。

我们提供的代码是您开发所需应用程序的第一步。

前提条件

唯一的先决条件是完成我们的 Android 快速入门教程。为此,您需要点击下面提到的链接。

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

下面是关于 LiveQuery 的教程,在本物联网系列教程的第 3 节中需要使用。不过您不必提前完成。

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

如果你在整个教程中遇到任何问题,可以通过下面链接中的官方 Parse Android 指南来解决。

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

第 1 节:创建应用程序和连接 Back4App 的基础知识

在本项目中,我们将类命名为 “MainActivity.java”。以下是基本代码,可以帮助您开始使用。

public class MainActivityextends AppCompatActivity {
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
     // 在应用程序中插入工具栏。关于该工具栏的更多描述请参见 menu_main_activity.xml 文件 
      工具栏 toolbar = (Toolbar) findViewById(R.id.toolbar);
      setSupportActionBar(toolbar); 
   }
   @Override
   public boolean onCreateOptionsMenu(Menu 菜单) { // Inflate the menu; this is the function of the menu.
     // 膨胀菜单;如果存在操作栏,则将项目添加到操作栏。 
      getMenuInflater().inflate(R.menu.menu_main_activity, menu);
     返回 true;
   }
   @Override
   public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks.
     // 在此处理操作栏项的点击。操作栏将
      // 自动处理主页/向上按钮的点击,只要
      // 只要您在 AndroidManifest.xml 中指定了父活动。 
     int id = item.getItemId();
     //noinspection SimplifiableIfStatement 
     if (id == R.id.action_settings) {
        返回 true;
      }
      return super.onOptionsItemSelected(item);
   }
}

您可能需要添加以下导入:

importandroid.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
导入 android.support.v7.widget.Toolbar;
import android.view.View
import 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" </p
      android:layout_height="wrap_content" /> <android.support.design.widget.AppBarLayout
      android:theme="@style/AppTheme.AppBarOverlay">
      <android.support.v7.widget.Toolbar
          android:id="@+id/toolbar"
          android:layout_width="match_parent" > <android.support.v7.widget.Toolbar
          android:layout_height="wrap_content"
          android:background="?
          app:popupTheme="@style/AppTheme.PopupOverlay"/>应用程序主题 
   </android.support.design.widget.AppBarLayout> </android.support.design.widget.AppBarLayout
</android.support.design.widget.CoordinatorLayout>

要将应用程序连接到 Back4App,请在类的 onCreate() 函数中添加以下代码。

// 初始化 Parse 服务器
Parse.initialize(new Parse.Configuration.Builder(this)
    .applicationId("YOUR_APP_ID")//核心设置,"功能 "部分 
    .客户端密钥("YOUR_CLIENT_KEY"//来自核心设置,关于 "功能" 
    .server("https://parseapi.back4app.com/")
    .build()
);

请记住,请按照 QuickStart 上的步骤说明为您的应用程序授予 Internet 访问权限。

第 2 部分:在 Parse 上保存和检索对象并在应用程序上显示

首先添加以下导入

importcom.parse.FindCallback;
import com.parse.Parse.FindCallback
import com.parse.ParseException;
import com.parse.ParseObject;
import com.parse.ParseQuery
import com.parse.SaveCallback;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;

在本节中,我们将通过点击按钮保存 “CommandGPIO1 “类的对象。每个 Parse Object 都有三个默认属性:objectId createdAt updatedAt。我们的类还有两个附加属性:contentdestinationcontent的值要么是 “on”(打开),要么是 “off”(关闭),代表要发送到 LED 的状态。对于目的地,所有对象都将包含 “command”,但我们可以为不同的 LED 定义不同的字符串。

举个例子:

我们创建了两个按钮,每个按钮都会在内容上写入不同的值。要创建按钮,请在布局 XML 文件中添加以下代码:

<按钮
   android:id="@+id/buttonSendOn"
   android:layout_width="wrap_content"
   android:layout_height="wrap_content"
   android:layout_gravity="顶部|开始"
   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"/>类文件中创建按钮。

在类文件中,创建按钮就像写代码一样简单:

Button buttonOn = (Button) findViewById(R.id.buttonSendOn);

如果您是 Android 编程初学者,请注意 “findViewById “参数中包含 “buttonSendOn”,而这正是我们在 XML 中定义的Android:Id

我们希望这个按钮在被点击时能在 Parse Server 上保存一个对象。为此,请添加以下代码:

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 面板上定义这个类!如果你后来选择添加新属性或更改类名,那么你只需修改代码,更改内容就会在仪表板上自动更新。

此时,你最好测试一下你的应用程序,检查对象是否正在创建!

如上图所示,SaveCallback 会在屏幕底部显示一个轻量级的反馈小条。

复制这段代码,创建一个写入 “关闭 “对象的按钮。更改布局 XML 中的以下几行

android:id="@+id/buttonSendOff"
android:layout_gravity="top|center"
android:layout_marginLeft="0dp"
android:layout_marginStart="0dp"
android:text="发送关闭"/>。

创建一个新按钮,并更改写入内容的行:

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

现在 Parse 服务器已经正常工作了,我们希望向应用程序用户提供永久性反馈。为此,我们将使用 EditText 元素,如下所示。

在布局 XML 中定义该元素:

< 编辑文本
    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("Loading status of output pin...");

第一行创建了对象。第二行使用户无法编辑该对象。第三行使其可点击,以便复制和粘贴。第四行设置初始文本。

我们希望将 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("Output is " + objects.get(0).getString("content"));
      }
     else{
         textOutPin.setText("Error:" + e.getMessage());
      }
   }
});

第一行创建查询。第二行添加了一个约束,只选择目标字段包含 “command “的对象。第三行从最新的对象开始。第四行将结果限制为一个,确保我们将检索到最新的对象。

第五行激活查询,并在查询完成后调用回调函数。在这里,我们将检索到的对象内容写入先前定义的 EditText 对象。

我们将在声明 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("Output is " + objects.get(0).getString("content"));
               }
              else{
                   textOutPin.setText("Error:" + e.getMessage());
               }
               Snackbar.make(view,"Updated status of Output", Snackbar.LENGTH_LONG ).setAction("Action",null).show();
            }
        });
     }
 });

至此,我们就可以在 Parse 服务器上保存对象、从中获取信息并在应用程序中显示它们了!你的应用程序应该可以如下图所示运行:

第 3 节:使用实时查询监听实时事件并在应用程序中显示

在本节中,我们将实时监控 Parse Dashboard 中的 “InputGPIO “类,并为用户显示对象的 “内容”。

首先在布局 XML 中定义一个新的 EditText:

< 编辑文本
    android:id="@+id/status2Text"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center|start"
    android:layout_marginBottom="0dp"
    android:maxLines="2"
    android:ems="10"
    android:singleLine="false"
    app:layout_behavior="@string/appbar_scrolling_view_behavior" />

在 onCreate() 的作用域中,创建一个新的 EditText 对象,然后立即执行初始化查询(尚未启用)。

final EditText textInPin = (EditText) findViewById(R.id.status2Text);
textInPin.setFocusable(false);
textInPin.setClickable(true);
textInPin.setText("Loading status of input pin...");
// 初始(非实时)查询,获取上次存储的引脚状态
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("Input is " + objects.get(0).getString("content"));
        }
       else{
            textInPin.setText("Error:" + e.getMessage());
        }
    }
});

请注意,”InputGPIO “的属性是内容类型,后者在 “CommandGPIO “类中扮演目的地的角色。

至此,您的应用程序应该如下图所示。

现在,我们将实现实时查询。这里需要参考前面提到的教程。

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

请务必按照 Live Query 教程中的第 1 步操作,选择在哪些类中启用 Live Query 并定义子域名称。您可能需要创建 “InputGPIO “类(如果还不存在),以便在该类上启用该功能。按照教程中的第 2 步在 Android 上设置 Live Query 客户端。

记住添加以下导入:

importtgio.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 的属性 
 Subscription subIn =new BaseQuery.Builder("InputGPIO")
    .where("type","interrupt")
    .addField("content")
    .build()
    .subscribe();

在定义订阅时,我们只选取type属性为 “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("Input is " + subInContent);
                   Snackbar.make(findViewById(R.id.myCoordinatorLayout),"Input pin was changed  to" + subInContent.toUpperCase(), Snackbar.LENGTH_LONG ).setAction("Action",null).show();
                }
            });
        }catch (JSONException e){
            e.printStackTrace();
        }
    }
});

对象的内容字段将显示在新的 EditText 元素中。

至此,我们所做的工作足以显示物联网设备发送的任何输入。不过,为了验证应用程序的运行情况,我们将实现另一种按钮,即切换按钮(Toggle Button),它将创建一个 “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";
       否则
            gpioStatusTest ="关闭";
        command.put("type","interrupt");
        command.put("content","From Toggle: " +gpioStatusTest);
        command.saveInBackground(new SaveCallback(){
            @Override
           public void done(ParseException e){
                Snackbar.make(view,"Changed input pin", Snackbar.LENGTH_LONG ).setAction("Action",null).show();
            }
        });
    }
});

另外,在 MainActivity 作用域中声明字符串。

StringgpioStatusTest ="off";

应用程序就完成了!本教程结束时,您的应用程序应该与下图非常相似。

如果您想继续开发我们的物联网应用程序,请阅读我们的物联网系列,这是一个循序渐进的指南,教您设置 Raspberry Pi 的基础知识和具体的应用程序,例如使用 JavaScript 从 Parse 服务器检索和保存对象。

我们的代码可从以下链接获取:

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

参考资料

将 Raspberry Pi 连接到 Parse 服务器

设置Raspberry Pi

如何讓 Android 應用與物聯網裝置互動?

第1部分:建立應用程式並連接伺服器端的基本知識
第2部分:在 Parse 上儲存與擷取物件,並顯示於應用程式中
第3部分:使用 Live Query 監聽即時事件並顯示於應用程式中


Leave a reply

Your email address will not be published.