تطبيق Android لـ Raspberry Pi
في دروسنا التعليمية السابقة حول إنترنت الأشياء، قمنا بتغطية كيفية إعداد Raspberry Pi وكيفية توصيله بخادم Parse Server باستخدام واجهة برمجة تطبيقات Back4App لحفظ الكائنات على الخادم وإجراء الاستعلامات والاستعلامات المباشرة.
الآن نغطي كيفية إعادة إنتاج كل ما تم إنجازه على جانب Raspberry إلى جانب التطبيق. في هذا البرنامج التعليمي، نصف تطبيق أندرويد للتفاعل مع جهاز إنترنت الأشياء الذي تم تكوينه مسبقًا. من جانب خادم Parse Server، يقوم التطبيق بنفس المهام التي يقوم بها جهاز Raspberry: يكتب الكائنات وينفذ الاستعلامات والاستعلامات المباشرة. يرجى ملاحظة أن هذه الوظائف قد تكون مفيدة حتى لو كنت لا تخطط لتطوير تطبيق إنترنت الأشياء!
أثبت Parse أنه إطار عمل رائع لصناعة تطبيقات إنترنت الأشياء. في عام 2020، مع إضافة بروتوكول GraphQL API، فإنه يوفر طريقة أفضل لاسترجاع البيانات.
نحن نقدم أكوادنا كخطوة أولى لك لتطوير التطبيقات التي تريدها.
Contents
المتطلبات الأساسية
الشرط المسبق الوحيد هو إكمال البرنامج التعليمي QuickStart لنظام 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”. إليك الكود الأساسي الذي سيساعدك على البدء.
فئة عامة MainActivityفئة MainActivity تمتد AppCompatActivity {
@Overrride باطلة محمية onCreate(Bundle savedInstanceState) {. super.onCreate(تم الحفظ في الحالة المحفوظة); تعيينContentView(R.layout.activity_main);
// إدراج شريط أدوات في التطبيق. مزيد من الوصف لهذا الشريط في ملف menu_main_activity.xml شريط الأدوات شريط الأدوات = (شريط الأدوات) findViewById(R.id.toolbar); setSupportSupportActionBar(شريط الأدوات); }
@Overrride العمومية منطقية منطقية onCreateOptionsMenu(قائمة القائمة القائمة) { // تضخيم القائمة؛ هذا يضيف عناصر إلى شريط الإجراءات إن كان موجودًا. getMenuMenuInflater().inflate(R.menu.menu_main_activity, menu); إرجاع صحيح; }
@Overrride عمومي منطقية منطقية onOptionsItemSelected(MenuItem item) { // تعامل مع نقرات عنصر شريط الإجراءات هنا. سيتعامل شريط الإجراءات // التعامل تلقائيًا مع النقرات على زر الصفحة الرئيسية/الأعلى، طالما // طالما أنك تحدد نشاطًا أصليًا في AndroidManifest.xml. int id = item.getItemId();
//لا يوجد فحص SimplififableIfStatement إذا (المعرف = = R.id.action_settings) { إرجاع صحيح; }
إرجاع super.onOptionsItemSelected(العنصر); } }
قد تحتاج إلى إضافة الواردات التالية:
استيراد android.os.Bundle; استيراد android.support.design.widget.FloatingActionButton.com العائم استيراد android.support.support.design.widget.widget.Snackbar; استيراد android.support.support.v7.app.App.AppCompatActivity; استيراد android.support.support.v7.widget.widget.Toolbar; استيراد android.view.View.View; استيراد android.view.Menu; استيراد android.view.MenuItem; استيراد android.widget.EditText; استيراد android.widget.Button; استيراد android.widget.ToggleButton; استيراد android.view.View.View.OnClickListener;
في ملف XML التخطيط الخاص بك، تأكد من إضافة الشيفرة التالية:
<?xml version="1.0" encoding="utf-8"؟ <android.support.support.design.widget.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">
<إدراجتخطيط="@layout/content_main_main_activity" />
<android.support.support.design.widget.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="?attr/colorPrimary" التطبيق:popupTheme="@style/AppTheme.PopupOverlay" />>
</android.support.support.design.widget.widget.AppBarLayout>>
</android.support.support.design.widget.widget.CoordinatorLayout>
لتوصيل تطبيقك بـ Back4App، أضف الشيفرة التالية أدناه داخل دالة onCreate() في فصلك.
// تهيئة خادم Parse Parse.initialize(جديد Parse.Configuration.Builder(هذا) .applicationId("Your_APP_ID") // من الإعدادات الأساسية، على "الميزات" .clientKey("YOUR_CLIENT_KEY") // من الإعدادات الأساسية، على "الميزات" .الخادم("https://parseapi.back4app.com/") .build() );
تذكر أن تتبع الإرشادات خطوة بخطوة في QuickStart لمنح الوصول إلى الإنترنت لتطبيقك.
القسم 2: حفظ واسترجاع الكائنات في Parse والعرض على التطبيق
ابدأ بإضافة الواردات أدناه
استيراد com.parse.FindCallback; استيراد com.parse.Parse استيراد com.parse.ParseException; استيراد com.parse.ParseObject; استيراد com.parse.ParseQuery; استيراد com.parse.SaveCallback; استيراد org.json.JSONException; استيراد org.json.JSONObject; استيراد java.util.List;
في هذا القسم، نحفظ كائنات فئة “CommandGPIO1” بنقرة زر. يحتوي كل كائن Parse Object على ثلاث سمات افتراضية: objectId، و createdAt، و updatedAt. ستحتوي فئتنا على سمتين إضافيتين: المحتوى والوجهة. ستكون القيم الخاصة بالمحتوى إما “تشغيل” أو “إيقاف”، وتمثل الحالة التي سيتم إرسالها إلى الصمام. بالنسبة للوجهة، ستحتوي جميع الكائنات على “أمر”، ولكن يمكننا تحديد سلاسل مختلفة لمصابيح LED مختلفة.
على سبيل المثال:
ننشئ زرين، ويكتب كل منهما قيمة مختلفة على المحتوى. لإنشاء زر، لإضافة الشيفرة التالية إلى ملف XML للتخطيط الخاص بك:
<زر android:id="@+id/buttonSendOn" android:layout_width="التفاف_المحتوى" android:layout_height="wrap_content" android:layout_gravity="top||start" android:layout_margin_marginBottom="0dp" android:layout_marginTop="120dp" android:layout_marginEnd="0dp" android:layout_margin_marginRight="0dp" android:layout_marginLeft="16dp" android:layout_marginStart="16dp" android:text="Send ON"/>
في ملف الصف الخاص بك، يكون إنشاء الزر سهلاً مثل كتابة
زر زر تشغيل = (زر) findViewById(R.id.button.id.buttonSendOn);
إذا كنت مبتدئًا في برمجة الأندرويد، يرجى ملاحظة أن وسيطة “findViewById” تحتوي على “buttonSendOn”، وهذا هو معرف الأندرويد الذي حددناه في XML.
نريد أن يحفظ هذا الزر كائنًا على Parse Server عند النقر عليه. للقيام بذلك، أضف الشيفرة التالية:
buttonOn.setOnClickListener( جديد OnClickListener() { @Overrride عام باطل onClick( طريقة عرضنهائية ) {
// إنشاء كائن جديد وتعيين السمات المناسبة أمر ParseObject = جديد ParseObject("CommandGPIO1");
الأمر.put("المحتوى"،"على"); command.put("الوجهة"، "الأمر"); command.saveInBackground(جديد SaveCallback(){ @Overrride عمومي باطل تم(ParseException e){ { .Snackbar.make(view, "تم الإرسال إلى الإخراج"، Snackbar.LENGTH_LONG ) .setAction("إجراء"، فارغ).show(); } }); });
لاحظ أنه يمكننا إضافة أي دالة نريد تنفيذها ضمن دالة رد النداء onClick. هنا، ننشئ هنا كائن فئة “CommandGPIO1″، ونضبط المحتوى على أنه “تشغيل”، والوجهة على أنها “أمر”.
من المفيد أن نعرف أنه ليس علينا تعريف هذه الفئة مسبقًا في لوحة معلومات Parse! إذا اخترت لاحقًا إضافة سمة جديدة، أو تغيير اسم صنفك، فما عليك سوى العمل على الشيفرة الخاصة بك، وسيتم تحديث التغييرات تلقائيًا على لوحة التحكم.
في هذه المرحلة، من الأفضل أن تختبر تطبيقك وتتحقق من إنشاء الكائن!
يُظهِر SaveCallback شريط سناكبار، وهو عبارة عن ملاحظات خفيفة الوزن في أسفل الشاشة، كما هو موضح في الشكل أعلاه.
انسخ هذه الشيفرة لإنشاء زر يكتب الكائنات بـ “إيقاف”. قم بتغيير الأسطر التالية في XML التخطيط
android:id="@+id/buttonSendOff" android:layout_gravity="أعلى |وسط" android:layout_marginLeft="0dp" android:layout_marginStart="0dp" android:text="Send OFF"/>
إنشاء زر جديد وتغيير السطر حيث تتم كتابة المحتوى:
زر زر إيقاف = (زر) findViewById(R.id.button.id.buttonSendOff);
الأمر.put("المحتوى"،"إيقاف");
الآن بعد أن أصبحت الأمور تعمل بشكل صحيح على Parse Server، نريد تقديم ملاحظات دائمة لمستخدم التطبيق. لذلك، سنستخدم عنصر 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="خطأ" التطبيق:layout_behavior="@string/appbar_scrolling_scrolling_view_behavior" />>
ضمن نطاق onCreate() من فصلك، أضف الشيفرة التالية:
// تحديد عنصر نصي لإظهار حالة دبوس الإخراج الذي يتلقى الأوامر من التطبيق // يتم إرسال الأوامر عن طريق الأزرار، والتي سيتم وصفها لاحقًا في هذه الشيفرة. نص نص تحرير نصينهائي textOutPin = (نص تحرير) findViewById(R.id.status1Text); textOutPin.setFocusable(false); textOutPin.setClickable(صحيح); textOutPin.setText("تحميل حالة دبوس الإخراج...");
يُنشئ السطر الأول الكائن. السطر الثاني يجعله غير قابل للتحرير للمستخدمين. السطر الثالث يجعله قابلاً للنقر، بحيث يمكن نسخه ولصقه. يقوم السطر الرابع بتعيين النص الأولي.
نريد كتابة حقل محتوى آخر كائن “CommandGPIO1” المحفوظ في Parse على كائن EditText هذا. على الرغم من أنه يمكننا القيام بذلك باستخدام بعض المنطق داخل الشيفرة، إلا أننا سنسترجع الكائنات من Parse عن طريق إجراء ParseQuery لأن هذا يوفر نتائج أكثر واقعية وقوة.
تقوم الشيفرة أدناه بتعريف وتعيين معلمات وطباعة نتائج استعلام ParseQuery.
ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1"); queryOut.whereEqualTo("الوجهة"، "الأمر"); queryOut.addDescendingOrderder("createdAt"); queryOut.setLimit(1);
استعلامOut.findInBackground(جديد FindCallback<ParseObject>() { @Overrride عام باطل تم(List<ParseObject> كائنات، ParseException e) { { إذا (e = = لاغية){ { textOutPin.setText("الإخراج هو " + objects.get(0).getString("المحتوى")); } غير ذلك{{ textOutPin.setText("خطأ: " + e.getMessage()); } } });
ينشئ السطر الأول الاستعلام. يضيف السطر الثاني قيدًا لتحديد فقط تلك الكائنات التي يحتوي حقل وجهتها على “أمر”. السطر الثالث يحدد الكائنات بدءًا بالأحدث منها. يحصر السطر الرابع النتائج في واحد فقط، مما يضمن أننا سنسترجع الكائن الأحدث.
يقوم السطر الخامس بتفعيل الاستعلام، ويستدعي دالة رد النداء عند الانتهاء. هنا، نكتب هنا محتوى الكائن المسترجع على كائن EditText المُعرَّف مسبقًا.
سنضيف هذا الجزء من الشيفرة مباشرةً بعد الإعلان عن كائن EditText، بحيث يتم تنفيذ الاستعلام عند فتح التطبيق، وأيضًا في SaveCallback عند حفظ كائن جديد على Parse، لتحديث النص تلقائيًا عند إنشاء كائنات جديدة.
في هذه المرحلة، يجب أن يعمل تطبيقك كما هو موضح في لقطات الشاشة التالية.
أخيرًا، نضيف زر تحديث للسماح للمستخدمين بإجراء الاستعلام أعلاه متى ما أرادوا. سيتم ذلك بنمط مختلف من الأزرار، أي زر إجراء عائم.
أضف هذا الرمز إلى ملف XML للتخطيط:
<android.support.design.widget.widget.FloatingActionButton android:id="@+id/fab" android:layout_width="wrap_content" android: layout_height="wrap_content" android:layout_gravity="top||end" android:layout_margin_marginBottom="0dp" android:layout_marginTop="120dp" android:layout_marginEnd="16dp" android:layout_margin_marginRight="16dp" android:layout_marginLeft="0dp" android:layout_marginStart="0dp" التطبيق:srcCompat="@android:drawable/ic_popup_sync" />>
والآن، ضمن نطاق onCreate() في الصف، أضف الشيفرة التالية:
// زر التحديث للحصول على حالة الإخراج عند الطلب. // يتم تنفيذ نفس الاستعلام مثل الأول هنا زر الإجراء العائم fab= (زر الإجراء العائم) findViewById(R.id.fab); fab.setOnClickListener(جديد View.OnClickListener(){ @Overrride عام باطل onClick(طريقة العرض النهائية ){ { ParseQuery<ParseObject> queryOut = ParseQuery.getQuery("CommandGPIO1"); queryOut.whereEqualTo("الوجهة"، "الأمر"); queryOut.addDescendingOrderder("createdAt")؛ queryOut.setLimit(1); استعلامOut.findInBackground(جديد FindCallback<ParseObject>() { @Overrride عام باطل تم(List<ParseObject> كائنات، ParseException e) { { إذا (e = = لاغية){ { textOutPin.setText("الإخراج هو " + objects.get(0).getString("المحتوى")); } غير ذلك{{ textOutPin.setText("خطأ: " + e.getMessage()); } Snackbar.make(view, "تحديث حالة الإخراج"، Snackbar.LENGTH_LONG ).setAction("إجراء"، فارغ).show(); } }); } });
في هذه المرحلة يمكننا الآن حفظ الكائنات على Parse Server واسترداد المعلومات منها وعرضها في تطبيقك! يجب أن يعمل تطبيقك كما هو موضح في الشكل أدناه:
القسم 3: الاستماع إلى الأحداث في الوقت الحقيقي باستخدام الاستعلام المباشر والعرض على التطبيق
في هذا القسم، نراقب في الوقت الحقيقي فئة “InputGPIO” في لوحة معلومات Parse Dashboard، ونعرض “محتوى” الكائنات للمستخدم.
ابدأ بتعريف نص تحرير جديد في XML التخطيط:
<تعديل النص android:id="@+id/status2Text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center||start" android:layout_margin_marginBottom="0dp" android:maxLines ="2" android:ems="10" android:singleLine="خطأ" app:layout_behavior="@string/appbar_scrolling_scrolling_view_behavior" />>
في نطاق onCreate()، أنشئ كائن EditText جديدًا وقم بإجراء استعلام تهيئة (ليس مباشرًا بعد) بعد ذلك مباشرةً.
نص تحرير نصنهائي textInInPin = (نص تحرير) findViewById(R.id.status2Text); textInPin.setFocusable(false); textInPin.setClickable(صحيح); textInPin.setText("تحميل حالة دبوس الإدخال...");
// استعلام أولي (غير مباشر) للحصول على آخر حالة مخزنة للدبوس ParseQuery<ParseObject> queryIn = ParseQuery.getQuery("InputGPIO"); queryIn.whereEqualTo("النوع"، "مقاطعة"); queryIn.addDescendingOrderder("createdAt"); الاستعلام في.setLimit(1);
استعلامIn.findIn.findInBackground(جديد FindCallback<ParseObject>() { @Overrride العامة باطل تم(List<ParseObject> كائنات، ParseException e) { { إذا (e = = لاغية){ { textInPin.setText("الإدخال هو " + objects.get(0).getString("المحتوى")); } غير ذلك{{ 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.com; استيراد tgio.parselivequery.interfaces.OnListener;
أضف التعليمة البرمجية التالية لتهيئة LiveQuery وتعريف معلماته.
// تهيئة الاستعلام المباشر LiveQueryClient.init("wss:YOUR_SUBDOMAIN_NAME.back4app.io", "YOUR_APP_ID"، صحيح ); LiveQueryClient.connect();
// تحديد سمات LiveQuery اشتراك فرعي في = جديد BaseQuery.Builder("InputGPIO") حيث("النوع"،"مقاطعة") .addField("المحتوى") .بناء() .subscribe();
عند تحديد الاشتراك، نأخذ فقط تلك العناصر التي تكون سمة نوعها “مقاطعة” ونسترجع محتوى الحقل.
والآن، أضف الشيفرة التالية للاستجابة كلما تم إنشاء كائن محدد بواسطة الاشتراك في Parse Server.
// البدء بالاستماع إلى أحداث LiveQuery CREATE، والحصول على محتواها وكتابة subIn.on(LiveQueryEvent.CREATE, جديد OnListener() { @Overrride عمومي باطل على(كائن JSONObject) { { محاولة { سلسلة نهائية SubInContent = (سلسلة) ((JSONObject) object.get("كائن")).get("محتوى"); تشغيلOnUnUiThread(جديد قابل للتشغيل() { @Overrride تشغيل عام باطل () { { textInPin.setText("الإدخال هو " + subInContent); Snackbar.make(findViewById(R.id.myCoordinatorLayout)، "تم تغيير دبوس الإدخال إلى " + subInContent.toUpperCase()، Snackbar.LENGTH_LONG ).setAction("إجراء"، فارغ).show(); } }); } إمساك (JSONException e){ { e.printStackTrace(); } } });
سيتم عرض حقل محتوى الكائن في عنصر EditText الجديد الخاص بنا.
ما قمنا به في هذه المرحلة كافٍ لعرض أي مدخلات يرسلها جهاز إنترنت الأشياء. ومع ذلك، وللتحقق من كيفية عمل التطبيق، سنقوم بتنفيذ نوع آخر من الأزرار، أي زر تبديل الذي سينشئ كائن فئة “InputGPIO” وسيحفظه على Parse Server.
أضف الشيفرة التالية إلى ملف XML للتخطيط:
<زر التبديل android:id="@+id/toggleTest" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center||end" android:layout_margin_marginBottom="0dp" android:layout_marginTop="100dp" android:layout_marginEnd="0dp" android:layout_margin_marginRight="0dp" android:layout_marginLeft="0dp" android:layout_marginStart="0dp" android:textOn = "تشغيل الإدخال" android:textOff = "إيقاف الإدخال"/>
أضف الرمز المذكور أدناه في نطاق الدالة onCreate()، في فئة MainActivity.
// زر التبديل موجود هنا فقط لمحاكاة الكائنات التي ستنشئها الأجهزة زر تبديل الزر تبديل الاختبار = (زر تبديل الزر) findViewById(R.id.toggleTest); toggleTest.setOnClickListener( جديد OnClickListener() { @Overrride عام باطل onClick( طريقة عرضنهائية ) { { أمر ParseObject = جديد ParseObject("InputGPIO"); إذا(gpioStatusTest.equals("off")) gpioStatusTest = "تشغيل"; غير ذلك gpioStatusTest = "إيقاف التشغيل";
command.put("type","interrupt"); command.put("المحتوى"، "من Toggle: " + gpioStatusTest); command.saveInBackground(جديد SaveCallback(){ @Overrride عمومي باطل تم(ParseException e){ { Snackbar.make(عرض، "دبوس الإدخال المتغير"، Snackbar.LENGTH_LONG ).setAction("إجراء"، فارغ).show(); } }); } });
أيضًا، أعلن السلسلة في نطاق MainActivity.
String gpioStatusTest = "off";
انتهينا من التطبيق! في نهاية هذا البرنامج التعليمي، يجب أن يبدو تطبيقك مشابهًا تمامًا للشكل الموضح أدناه.
إذا كنت ترغب في مواصلة تطوير تطبيق إنترنت الأشياء، يُرجى قراءة سلسلة إنترنت الأشياء، وهو دليل خطوة بخطوة، والذي يعلمك أساسيات إعداد Raspberry Pi وتطبيقات محددة، مثل استرداد وحفظ الكائنات من Parse Server باستخدام JavaScript.
أكوادنا متاحة في الرابط التالي:
https://github.com/back4app/iot-raspberry-node
المراجع
توصيل Raspberry Pi بخادم Parse Server
كيفية التفاعل بين تطبيق Android وجهاز إنترنت الأشياء؟
القسم 1: أساسيات إنشاء التطبيق الخاص بك والاتصال بجانب الخادم.
القسم 2: حفظ واسترجاع الكائنات على Parse وعرضها على التطبيق.
القسم 3: الاستماع إلى الأحداث في الوقت الفعلي باستخدام الاستعلام المباشر والعرض على التطبيق