Professional Documents
Culture Documents
Giao Trinh Android Ung Dung Trong Cac He Thong IoTs 2020
Giao Trinh Android Ung Dung Trong Cac He Thong IoTs 2020
Ths. Nguyễn Văn Hiệp – Ts. Trần Đức Thiện – Ts. Trần Mạnh Sơn
Những năm 2000, điện thoại thông minh (smartphone) là một khái niệm rất xa xỉ. Người giàu thì
cũng chỉ có thể “sang chảnh” với các điện thoại “đập đá” đúng nghĩa. Nhưng chúng ta đã thật sự
chứng kiến sự thay đổi như vũ bão của thế giới smartphone. Rất nhiều hãng điện thoại mới nổi,
thành công mang đến cho người dùng sự tiếp cận dễ dàng hơn bao giờ hết. Điện thoại thông minh
giờ đã thông minh hơn, với sự tích hợp của AI (trí tuệ nhân tạo) người dùng như có thêm trợ lý
đắc lực hiểu mình hơn nữa. Smartphone ngày càng đa dạng, trãi dài trên các phân khúc từ cao cấp
đến bình dân. Giới trẻ hầu như ai cũng có smartphone, mọi thứ bắt đầu liên kết hầu hết với nhau
qua internet. Thế giới trở nên phẳng hơn, mọi tương tác liên quan đến đời sống tinh thần, vật chất
được số hóa và chia sẻ.
Với sự thống trị hiện tại của Android trong thế giới smartphone, cuộc cách mạng công nghiệp 4.0
đang tiến đến gõ cửa từng nhà, quyển sách mong muốn mang đến cho người đọc những nền tảng
cơ bản, cốt lốt của việc lập trình di động ứng dụng điều khiển, giám sát thiết bị.
Quyển sách này được viết dựa trên hai quyển Lập trình Android cơ bản, Lập trình Android trong
ứng dụng điều khiển của cùng tác giả, NXB Đại học Quốc Gia HCM. Chính vì vậy, một số khái
niệm cơ bản đã được bỏ bớt, hướng đến phần ứng dụng thực tế, các hệ thống giám sát điều khiển
thông qua thiết bị Android được cải tiến, chỉnh sửa, bổ sung.
Tác giả hi vọng sau khi đọc quyển sách này, người đọc có thể tự xây dựng một số hệ thống điều
khiển thông minh cơ bản phù hợp với nhu cầu cụ thể. Hoặc đơn giản là trang bị những kiến thức
để việc trải nghiệm các thiết bị smarthome (nhà thông minh) dễ dàng và thuận tiện hơn!
Chân thành cảm ơn sự ủng hộ của quý độc giả, mọi góp ý về nội dung vui lòng gởi về email:
hiepspkt@hmcute.edu.vn.
Thân ái!
Hình 1.2 Các phiên bản hệ điều hành Android và thời gian phát hành
Những yếu tố này đã giúp Android trở thành nền tảng điện thoại thông minh phổ biến nhất
thế giới, vượt qua Symbian OS vào quý 4 năm 2010, và được các công ty công nghệ lựa
chọn khi họ cần một hệ điều hành không nặng nề, có khả năng tinh chỉnh, và giá rẻ chạy
trên các thiết bị công nghệ cao thay vì xây dựng một hệ điều hành cho riêng mình. Do đó
Android đã được thiết kế để không chỉ chạy trên điện thoại và máy tính bảng mà còn xuất
hiện trên Tivi, máy chơi game và các thiết bị điện tử khác. Bản chất mở của Android cũng
Android không chỉ được thiết kế để chạy trên điện thoại, máy tính bảng (tablet), thiết bị di
động có kích thước lớn (phable). Android chạy trên các thiết bị có đủ hình dạng và kích
thước, mang đến cho bạn cơ hội lớn để tiếp tục tương tác với người dùng. Và Android được
thiết kế chuyên dùng và hướng đến các đối tượng khác nhau.
Android TV là hệ điều hành được tùy biến xây dựng chạy trên các tivi thông minh. Thị
trường tivi thông minh giờ rất sôi động. Nội dung xem trực tiếp phong phú, chất lượng cao.
Chính vì vậy Android TV mong muốn mang đến những trải nghiệm mới cho người dùng.
Android TV tạo ứng dụng cho phép người dùng trải nghiệm nội dung của ứng dụng trên
màn hình lớn.
Android Auto là hệ điều hành được tùy biến cho các xe hơi. Nhằm mang đến các trải
nghiệm giải trí, công việc tuyệt vời. Các trải nghiệm dẫn đường, nghe nhạc, xem phim, ...
sẽ được nâng lên một tầm mới. Việc viết ứng dụng của bạn với Android Auto, bạn sẽ không
phải lo lắng sự khác biệt về phần cứng của thiết bị như độ phân giải màn hình, giao diện
phần mềm, nút bấm và điều khiển cảm ứng. Người dùng có thể truy cập ứng dụng của bạn
thông qua ứng dụng Android Auto trên điện thoại của họ. Hoặc, khi được kết nối với các
loại xe tương thích, các ứng dụng trên thiết bị cầm tay chạy Android 5.0 trở lên có thể giao
tiếp với trải nghiệm Android Auto.
Wear OS (trước đây là Android Wear) là hệ điều hành Android được tùy biến xây dựng
cho các thiết bị đeo. Đồng hồ thông minh giúp bạn luôn kết nối với sức khỏe. Bạn có thể
theo dõi các hoạt động hằng ngày như thông tin thân nhiệt, nhịp tim, bước chân đi, giờ ngủ,
đọc nhanh các tin nhắn, email, …
1
(Một số thông tin được lấy từ trang Bách khoa toàn thư mở wikipedia.org).
Hình 2.1 Sơ đồ thừa kế giữa các thành phần giao diện trong Android
Layout Mô tả
Relative Layout RelativeLayout là một ViewGroup nó hiển thị các View con
của nó theo vị trí có tương đối với nhau. Việc sắp xếp các
view trên màn hình tốn ít tài nguyên hơn, tuy nhiên do có sự
quan hệ qua lại nên khi tùy chỉnh các thành phần sẽ chú ý
Grid Layout GridLayout sử dụng một mạng lưới các dòng mỏng và vô
hạn để tách khu vực bản vẽ của nó thành: các hàng, các cột,
và các ô (cell). Nó hỗ trợ cả việc bắc qua (span) các hàng và
các cột, mà cùng nhau cho phép một thành phần giao diện để
chiếm một phạm vi hình chữ nhật gồm nhiều ô.
Frame Layout FrameLayout là một đối tượng giữ chỗ ở trên màn hình mà
bạn có thể sử dụng để hiển thị một khung nhìn duy nhất.
Absolute Layout AbsoluteLayout làm bạn có thể chỉ định chính xác vị trí của
các view con trong nó. Sắp xếp các view con theo đúng tọa
độ x, y trong thành phần cha.
Constraint Layout Một constraint là phần mô tả làm thế nào để một View nên
được định vị trên màn hình tương đối với các phần tử khác
trong layout. Bạn có thể xác định một constraint cho một hay
nhiều mặt của một view bằng cách chế độ kết nối bất kỳ sau
đây : điểm neo nằm trên một View khác, một cạnh của
layout, một guideline vô hình. Đây là Layout mặc định trong
Android Studio hiện tại.
2.3 RELATIVE LAYOUT:
RelativeLayout là một ViewGroup có hiển thị các View con ở các vị trí tương đối. Vị trí
của mỗi View có thể được quy định liên quan đến các View anh em (như bên trái của hoặc
bên dưới một View khác) hoặc ở các vị trí tương đối với khu vực cha RelativeLayout
(chẳng hạn như sắp xếp ngay phía dưới, bên trái hoặc trung tâm).
RelativeLayout là một tiện ích rất mạnh mẽ cho thiết kế một giao diện người sử dụng vì nó
có thể loại bỏ các nhóm View lồng nhau và giữ cho hệ thống phân cấp bố trí của bạn bằng
phẳng, đồng thời cải thiện hiệu suất. Nếu bạn sử dụng một vài nhóm LinearLayout lồng
nhau, bạn có thể thay thế chúng bằng một RelativeLayout duy nhất.
Ví dụ 2.1 Thiết kế screen nhập ghi chú như sau:
Với hoạt động (activity) này thì chúng ta có thể sử dụng các layout như: TableLayout,
LinearLayout, RelativeLayout, ConstraintLayout để thiết kế. Ở phần này, tôi sử dụng
RelativeLayout để thiết kế ra giao diện như trên.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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:orientation="vertical"
android:id="@+id/linearlayoutmain"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="Ghi chú"
android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"/>
<EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/textView1"
android:hint="Nhập ghi chú vào đây"
android:inputType="textMultiLine">
<requestFocus/>
</EditText>
<Button
android:id="@+id/button1"
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_alignParentRight="true"
android:text="Hủy"/>
</RelativeLayout>
Thuộc tính Mô tả
Layout_width Quy định độ rộng của widget
Layout_height Quy định độ cao của widget
layout_margintop Quy định khoảng cách bên trên của
widget
layout_marginbottom Quy định khoảng cách bên dưới của
widget
layout_marginleft Quy định khoảng cách bên trái của
widget
layout_marginright Quy định khoảng cách bên phải của
widget
layout_gravity Quy định vị trí của widget
layout_weight Quy định khoảng không gian layout
phân chia cho widget
Để hiểu rõ hơn về LinearLayout chúng ta sẽ tìm hiểu các ví dụ dưới đây. Lưu ý: các ví dụ
chủ yếu phân tích các Layout do đó sẽ dùng phương pháp thiết kế giao diện từ file XML,
không hướng dẫn lập trình chức năng ở phần này.
Với yêu cầu như vậy, chúng ta có thể sử dụng các layout như: LinearLayout, TableLayout,
RelativeLayout để thiết kế. Ở đây, tôi sử dụng LinearLayout để giải quyết yêu cầu này.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="horizontal"
android:weightSum="100"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#aa0000"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#00aa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#0000aa"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#aaaa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
</LinearLayout>
Code trên ta thấy thuộc tính orientation được gán giá trị là horizontal để các widget trong
nó được sắp xếp theo chiều ngang.
Thuộc tính android:weightSum được sử dụng để tính tổng weight của các widget trong
LinearLayout. Thuộc tính android:layout_weight (của các widget nằm bên trong
LinearLayout) là độ rộng của widget này so với các widget khác, ta thấy ở code trên giá trị
của thuộc tính android:layout_weight ở cả bốn TextView là “25”, do đó độ rộng của mỗi
cột màu là như nhau và chiếm hết toàn bộ màn hình vì tổng của các android:layout_weight
bằng android:weightsum. Nếu bạn muốn các cột màu có độ rộng như nhau nhưng chỉ chiếm
một nửa màn hình, hãy tăng gấp đôi giá trị của android:weightsum = “200”. Hãy thử và
kiểm tra kết quá có đúng như bạn nghĩ không!
Ví dụ 2.3 Thiết kế screen có bốn hàng màu, với hàng màu đỏ và vàng có độ cao gấp đôi
so với hai hàng màu còn lại
Hình 2.6 Kết quả hiển thị trên màn hình khi thiết kế LinearLayout dọc
Ví dụ 2.4 Thiết kế screen là tổ hợp bốn hàng màu và bốn cột màu đã làm ở các ví dụ một
và ví dụ hai.
Horizontal
Vertical
Vertical
Như đã nói ở trên, các ViewGroup có thể chứa cả View và ViewGroup bên trong nó, ở ví
dụ ta sẽ lồng các LinearLayout vào nhau để đáp ứng yêu cầu thiết kế, hình dưới phác thảo
việc lồng các LinearLayout lại với nhau. Kết quả hiển thị trên máy ảo được trình bày trong
hình 2.8.
Trong thực tế, thiết kế giao diện cho các ứng dụng thường phức tạp, chỉ sử dụng một Layout
để chưa các widget không để đáp ứng yêu cầu thiết kế. Do đó, giải pháp lồng các Layout
vào nhau sẽ giúp bạn giải quyết hầu hết các bài toán giao diện. Ngoài LinearLayout còn
một số Layout khác hỗ trợ thiết kế giao diện mà chúng ta sẽ thảo luận dưới đây.
<TableRow android:layout_width="match_parent">
<TextView
android:layout_width="wrap_content"
android:text="Pass"/>
<EditText android:layout_width="match_parent"
android:inputType="textPassword"/>
</TableRow>
<TableRow android:layout_width="match_parent">
<Button
android:layout_width="wrap_content"
android:text="OK"/>
<Button
android:layout_width="wrap_content"
android:text="Thoát"/>
</TableRow>
</TableLayout>
Từ code trên ta thấy để tạo thêm các hàng cho bảng ta sẽ dùng thẻ
<TableRow></TableRow>, các widget ở mỗi hàng sẽ nằm trong cặp thẻ này. Số cột của
bảng phụ thuộc vào hàng có số widget lớn nhất. Bảng sử dụng trong ví dụ này có kích
thước là ba hàng và hai cột.
Thuộc tính android:StretchColumns=“*” cho phép căn đều độ rộng các cột trong bảng.
Ngoài ra, EditText để nhập mật khẩu sử dụng thuộc tính
android:inputType="textPassword" để hiển thị các dữ liệu được nhập vào ở dạng ký tự “*”.
Kết quả hiển thị trên máy ảo:
Theo thứ tự code trong file xml, Button1 ở đầu tiên nên trên giao diện nó sẽ nằm dưới cùng,
Button2 nằm ở giữa và Button1 nằm trên cùng.
Bài tập tổng hợp, dựa vào các Layout đã tìm hiểu ở trên, hãy thiết kế Screen như hình dưới.
Hình 2.12 Sử dụng các Layout đã trình bày thiết kế giao diện máy tính đơn giản
Để thiết kế được giao diện như trên thì ta có thể sử dụng các Layout ở trên và ở phần này
tôi sẽ sử dụng ConstraintLayout để thiết kế. Các bạn sẽ thấy đây là layout rất tiện lợi và
đang được Google khuyến khích dùng.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvyeucau"
android:text="Hãy nhập chiều cao và cân nặng của bạn"
android:textSize="25sp"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
app:layout_constraintTop_toTopOf="@id/constraintlayout"
app:layout_constraintLeft_toLeftOf="@id/constraintlayout"
app:layout_constraintRight_toRightOf="@id/constraintlayout"
tools:layout_editor_absoluteX="0dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvcao"
android:textSize="20sp"
android:text="Chiều cao"
app:layout_constraintTop_toBottomOf="@id/tvyeucau"
android:layout_marginTop="20sp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/edcao"
android:hint="Nhập chiều cao"
app:layout_constraintTop_toBottomOf="@id/tvyeucau"
app:layout_constraintLeft_toRightOf="@id/tvcao"
app:layout_constraintBaseline_toBaselineOf="@id/tvcao"
android:layout_marginLeft="30sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvnang"
android:text="Cân nặng"
android:textSize="20sp"
app:layout_constraintTop_toBottomOf="@id/tvcao"
app:layout_constraintLeft_toRightOf="@id/constraintlayout"
android:layout_marginTop="20dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ednang"
android:hint="Nhập cân nặng"
app:layout_constraintTop_toBottomOf="@id/edcao"
app:layout_constraintLeft_toRightOf="@id/tvnang"
app:layout_constraintBaseline_toBaselineOf="@id/tvnang"
android:layout_marginLeft="30sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Hình 2.14 Kết quả màn hình ứng dụng khi chạy máy ảo
Hình 3.2 Chọn kích thước màn hình thiết kế trong Android Studio
• dp, hay dip hay Density-independent Pixels, có khi được gọi là Device-
independent Pixels. Đây là một đơn vị đo chiều dài vật lý cũng giống như inch,
cm, mm… mà Google thường áp dụng để do kích thước màn hình của thiết bị. 160
dp = 1inch, điều này có nghĩa 1dp = 1/160 = 0.00625 inch. Một dp có thể chứa một
hay nhiều pixel. Ví dụ như màn hình có kích thước là 10 dp. Ở màn hình độ phân
giải thấp 1dp tương đương 1 pixel. Ở độ phân giải trung bình thì 1dp tương đương
4 pixels ….
• DPI (Dots per inch) hay PPI – (Pixels per inch) là số điểm ảnh (pixels) trên một inch của
màn hình thiết bị, con số này càng lớn thì màn hình thiết bị hiển thị hình ảnh càng mịn và
đẹp. Dựa vào dpi người ta chia làm loại màn hình như sau: small: ldpi (120 dpi), normal:
mdpi (160 dpi), large: hdpi (240 dpi), x-large: xhdpi (320 dpi). Với mỗi loại này thì một dp
tương ứng với số lượng pixels khác nhau, được tính theo công thức:
px = dp * (dpi / 160)
Ví dụ với thiết bị có dpi là 320 thì với 10 dp ta có: 10 * (320/160) = 20 px , một dp tương đương
2 px.
• PT (Point): khái niệm pt tương tự như dp là một đơn vị đo kích thước thực, nhưng khác với
dp: 1 pt = 1/72 inch, trong khi 1 dp = 1/160 inch pt thường được dùng trong lập trình iOS.
• SP (Scale-independent Pixels) Cũng tương tự như dp, nhưng sp thường được dùng cho
font size của văn bản.
Sau đây là ví dụ ở màn hình normal screen mdpi (160dpi) cho các đơn vị đo dp, sp, px,
pt, inch
Trên đây ta ví dụ cho trường hợp Control của ta là một TextView, các đối tượng khác
làm tương tự.
3.2.2 CÁC ĐỐI TƯỢNG UI CƠ BẢN:
Giải thích về yêu cầu: Người dùng nhập chiều cao và cân nặng vào hai EditText ở Activity
thứ nhất. Khi nhấn nút “Tiếp tục” thì sẽ chuyển qua Activity thứ hai. Sau đó tính toán chỉ
số BMI và hiển thị kết quả lên Activity thứ hai. Khi nhấn nút “OK” thì sẽ kết thúc và quay
lại Activity chính.
Các bước thực hiện như sau:
Bước 1: Tạo ứng dụng Android.
Bước 2: Thiết kế Layout cho Main Activity như yêu cầu gồm: ba TextView, hai
EditText, một Button. Chỉnh file res/layout/activity_main.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
tools:context="com.example.windows10.bmicalculator.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="@string/tvrequest"/>
<LinearLayout
android:layout_width="match_parent"
Bước 4: Định nghĩa các hằng số trong file res\value\string.xml như bên dưới. Để tiện
thiết kế layout thì ngay khi ta thiết kế layout cho MainActivity ta đã định nghĩa các hằng
số cho file String.xml rồi. Sau đó khi ta tạo Activity mới ta lại định nghĩa thêm. Việc này
sẽ giúp bạn thu gọn thuộc tính text trong các đối tượng.
<resources>
<string name="app_name">BMI Calculator</string>
<string name="tvrequest">Hãy nhập chiều cao và cân nặng của
bạn</string>
</resources>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity
android:name=".Main2Activity"
android:label="@string/app_name">
<intent-filter>
<action
android:name="android.intent.action.Main2Activity"/>
<category
android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
Bước 6: Viết code cho MainActivity. Chỉnh sửa file MainActivity.java như sau (Chú ý
các dòng chú thích).
package com.example.windows10.bmicalculator;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
@Override
protected void onResume() {
super.onResume();
//xử lý sự kiện nút nhấn
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(isEmpty(chieucao))
{
Toast.makeText(MainActivity.this,"Hãy nhập chiều
cao!",Toast.LENGTH_LONG).show();
a = false;
}
else
{
h =
Double.parseDouble(chieucao.getText().toString());
a = true;
}
if(isEmpty(cannang))
{
Toast.makeText(MainActivity.this,"Hãy nhập cân
nặng!",Toast.LENGTH_LONG).show();
b = false;
}
else
{
Bước 7: Viết code cho Main2Activity. Chỉnh sửa file Main2Activity.java như sau (Chú ý
các dòng chú thích).
package com.example.windows10.bmicalculator;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.w3c.dom.Text;
@Override
protected void onResume() {
super.onResume();
Intent caller = getIntent();
Bundle dulieunhan = caller.getBundleExtra("dulieu");
Double chieucao=dulieunhan.getDouble("chieucao");
Double cannang=dulieunhan.getDouble("cannang");
//Tính chỉ số BMI theo tiêu chuẩn tổ chức y tế thế giới
BMI=cannang/(chieucao*chieucao);
BMI= (double) Math.round(BMI*100)/100;
String kq;
if(BMI<16) {kq="Bạn thuộc dạng đại gia có nghĩa là da bọc xương
đấy";}
else if(BMI<17) {kq="Bạn suy dinh dưỡng cấp độ 2 rồi, ăn như hạm
vào!";}
else if(BMI<18.5) {kq="Bạn suy dinh dưỡng rồi á, đề nghị ăn bồi
bổ nhiều nhiều";}
else if(BMI<24.9) {kq="Chiều cao và cân nặng bình thường, đề nghị
duy trì";}
else if(BMI<29) {kq="Bạn hơi bị nhiều ký, bắt đầu béo phì, giảm
cân gấp";}
else if(BMI<35) {kq="Mức độ béo phì của bạn bắt đầu nghiêm trọng,
quyết liệt giảm cân hơn nữa";}
else kq="Bạn có thể lăn rồi, hết phương cứu chữa";
StringBuffer tket=new StringBuffer();
tket.append("Chỉ số BMI của bạn là:
").append(BMI).append("\n").append(kq);
//hiển thị kết quả lên textview
ketqua.setText(tket);
//xử lý sự kiện nút nhấn
btn_ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//kết thúc activity 2
finish();
}
});
}
public void anhxa()
{
ketqua = (TextView)findViewById(R.id.ketqua);
btn_ok = (Button)findViewById(R.id.ok);
}
}
Việc liên kết hai Activity với nhau đã được trình bày trong các quyển sách trước của cùng
tác giả. Có những cách thực hiện đơn giản hơn ứng cho những trường hợp cụ thể.
Tóm lại ta hãy yên tâm sử dụng Google FireBase vì các lợi ích cốt lỗi sau:
• Triển khai ứng dụng cực nhanh: Firebase cung cấp cho bạn khá nhiều các API, hỗ trợ
đa nền tảng giúp bạn tiết kiệm thời gian quản lý cũng như đồng bộ dữ liệu cung cấp
hosting, hỗ trợ xác thực người dùng thì việc triển khai ứng dụng sẽ giảm được rất nhiều
thời gian phát triển.
• Bảo mật: Với việc sử dụng các kết nối thông qua giao thức bảo mật SSL hoạt động
trên nền tảng cloud đồng thời cho phép phân quyền người dùng database bằng cú pháp
javascipt cũng nâng cao hơn nhiều độ bảo mật cho ứng dụng của bạn.
• Sự ổn định: Firebase hoạt động dựa trên nền tảng cloud cung cấp bởi Google do đó
hãy yên tập về việc một ngày đẹp trời nào đó server ngừng hoạt động hay như DDOS
hoặc là tốc độ kết nối như rùa bò. Một điều đáng lưu ý nữa đó là do hoạt động trên nền
tảng Cloud vì vậy việc nâng cấp hay bảo trì server cũng diễn ra rất đơn giản mà không
cần phải dừng server.
4.3 XÂY DỰNG MỘT DỰ ÁN ĐƠN GIẢN SỬ DỤNG GOOGLE FIREBASE
4.3.1 BÀI TOÁN 1:
Điều khiển một đèn từ xa qua mạng internet sử dụng cơ sở dữ liệu thời gian thực của
FireBase. Phần cứng kết nối sử dụng NODMCU8266 khá đơn giản trong Hình 4.2. Chi
tiết về NODMCU8266 sẽ trình bày bên dưới ở bài tập tiếp theo.
Tiếp theo ta viết một ứng dụng có tên là DieuKhienLedQuaFire với giao diện như sau:
Lưu ý: Ta phải có hai file hình ảnh lampon và lampoff được chép trong thư mục Drawable
của ứng dụng. Hai hình này thể hiện trạng thái led tắt và mở. Các bạn có thể tự vẽ lấy bằng
ứng dụng Paint hoặc có thể sưu tầm trên mạng.
Bước tiếp theo là ta sẽ liên kết ứng dụng vừa tạo với Firebase. Trước tiên bạn cần update
các phiên bản và thư viện cần thiết theo các chỉ dẫn bên dưới. Sau đó vào mục Setting:
Vào SDK Tools để cài đặt các Tool đánh dấu như hình bên dưới
Tiến hành liên kết ứng dụng với Firebase một cách tự động. Các bạn có thể làm thủ công,
tuy nhiên công việc phức tạp hơn và khá rắc rối. Các phiên bản Android Studio sau này,
việc liên kết với Firebase khá đơn giản, do Google tích hợp và hỗ trợ sẵn.
Đầu tiên bạn vào mục Tools\ Firebase từ ứng dụng của bạn đang mở
Cửa sổ Firebase mở ra bên phải ứng dụng của bạn. Từ đây bạn có thể làm theo các hướng
dẫn của Google. Các hàm mẫu và chỉ dẫn cũng khá chi tiết. Nếu bạn muốn dùng Firebase
tạo cơ sở dữ liệu thời gian thực, bạn nhấn vào mục Realtime Database
Sau đó bạn nhấn tiếp vào mục Save and retrieve data. Bạn lần lượt thực hiện các bước tiếp
theo
Lúc này nếu trên Firebase bạn có project sẵn rồi bạn có thể thực hiện liên kết, hoặc bạn có
thể tạo mới nếu chưa có. Ở đây tôi tạo một Project mới tên là DieuKhienLedquaFirebase,
sau đó tôi vào mục Database thêm một Child tên là LED và tôi cho trạng thái mặc định ban
đầu là 0.
Hình 4.9 Kết quả Database thêm một Child tên là LED
Lưu ý: Bạn có thể đọc ghi dữ liệu lên Database của Firebase bạn vào mục Rules đặt các
thuộc tính read/write thành true như hình bên dưới.
Sau khi thực hiện xong việc liên kế ứng dụng với Firebase thì bạn sẽ thấy thông báo như
sau tại cửa sổ Firebase trong ứng dụng
Trong đó, tham số trong hàm getReference là tên của Child mà mình muốn gởi và nhận
giá trị (đã tạo trên Realtime Database). Ở đây ta dùng “LED” bởi vì trên Firebase ta đã tạo
một Child tên là “LED”.
Sử dụng hàm setValue để gởi dữ liệu Firebase:
myRef.setValue(1);
Để đọc giá trị Child cụ thể từ Database của Firebase ta dùng hàm bên dưới:
// Read from the database
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
(b)
Hình 4.12 Kết quả chạy trên máy ảo a) khi Led sáng b) khi Led tắt
Bước tiếp theo là phần cứng NODMCU8266 sẽ nhận giá trị “LED” từ firebase và điều
khiển đèn Led sáng tắt tương ứng.
Đoạn code viết cho NodMCU 8266 trên Arduino như sau:
#include <ESP8266WiFi.h>
NodeMCU là một nền tảng IoTs mã nguồn mở. Nó bao gồm firmware chạy trên ESP8266
Wi-Fi SoC từ Espressif Systems, và phần cứng là dựa trên các mô-đun ESP-12. NodeMCU
8266 được sử dụng rất rộng rãi trong các ứng dụng IoTs bởi nó vừa đóng vai trò là một
chip xử lý bao gồm luôn việc kết nối nối mạng. Các chân IO được thiết kế hầu hết phù hợp
và đáp ứng được các nhu cầu người dùng cơ bản. Hơn nữa cùng với mã nguồn mở của
người dùng, kết hợp với cộng đồng Ardunio lớn mạnh, NodeMCU luôn là sự lựa chọn cho
sinh viên bắt đầu với các ứng dụng điều khiển, giám sát từ xa.
Khi Arduino.cc bắt đầu phát triển các bo mạch MCU mới dựa trên các bộ xử lý không phải
là AVR như ARM / SAM MCU và được sử dụng trong Arduino Due, họ cần phải chỉnh
sửa Arduino IDE. Mục đích để hỗ trợ các công cụ thay thế cho phép Arduino C / C ++ biên
dịch các bộ xử lý mới này. Một số người đam mê ESP8266 đã phát triển lõi Arduino cho
SoC WiFi ESP8266, thường được gọi là "Lõi ESP8266 cho Arduino IDE". Điều này đã trở
thành một nền tảng phát triển phần mềm hàng đầu cho các mô-đun và kit phát triển dựa
trên ESP8266 khác nhau, bao gồm cả NodeMCU.
Module cảm biến nhiệt độ, độ ẩm DHT11: Cảm biến nhiệt độ và độ ẩm DHT11 có đầu
ra tín hiệu số được hiệu chỉnh. Nó được tích hợp với một vi điều khiển 8 bit hiệu suất cao.
Công nghệ của nó đảm bảo độ tin cậy và ổn định lâu dài tuyệt vời. Nó có chất lượng tốt,
đáp ứng nhanh, khả năng chống nhiễu và hiệu suất cao.
Mỗi cảm biến DHT11 có tính năng hiệu chuẩn cực kỳ chính xác. Giao tiếp truyền dữ liệu
số trên một dây nên khá thuận tiện trong việc tích hợp và mở rộng. Kích thước nhỏ, công
suất thấp, khoảng cách truyền tín hiệu lên tới 20 mét, cho phép nhiều ứng dụng và thậm
chí là những ứng dụng đòi hỏi khắt khe nhất. Sản phẩm đóng gói đưa ra 4 chân, trong đó
có một chân giả (NC).
(a) (b)
Hình 4.15 Kết nối NodeMCU 8266 với DHT11 a) sơ đồ kết nối; b) kết nối trên hình thực tế
Theo các bước hướng dẫn, tạo một dự án Firebase lưu trữ dữ liệu thời gian thực với các
mục như sau:
Hình 4.19 Một dự án Firebase lưu trữ dữ liệu thời gian thực
Sau khi đã thiết kế xong phần giao diện ứng dụng Android, tạo cơ sở dữ liệu thời gian thực
trên Firebase và liên kết chúng xong ta tiến hành viết code cho ứng dụng tại File
Main_Activity.java như sau:
package com.example.giamsatdenquatnhietdoquafirebase;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
if(dulieuden.getValue().toString().equals("0")){
lamp_status=false;
img_lamp.setImageResource(R.drawable.lampoff);
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
//feedback fan
mdatafan=FirebaseDatabase.getInstance().getReference();
mdatafan.child("Quat").addValueEventListener(new ValueEventListener()
{
@Override
public void onDataChange(@NonNull DataSnapshot dulieuquat) {
if(dulieuquat.getValue().toString().equals("1")){
fan_status=true;
img_fan.setImageResource(R.drawable.fanon);
}
if(dulieuquat.getValue().toString().equals("0")){
fan_status=false;
img_fan.setImageResource(R.drawable.fanoff);
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
//feedback temp DHT1
mdata_T1=FirebaseDatabase.getInstance().getReference();
mdata_T1.child("Nhiet do DHT1").addValueEventListener(new
ValueEventListener() {
Nhấn vào các biểu tượng quạt/đèn để bật tắt, ta thấy trạng thái của nó trên Firebase cũng
thay đổi theo gần như tức thời. Và ngược lại khi bạn thay đổi từ Firebase thì trên ứng dụng
cũng có sự thay đổi tương ứng. Đặc biệt quan tâm khi thay đổi các giá trị nhiệt độ trên
Firebase thì bạn thấy nhiệt độ trên ứng dụng cũng thay đổi theo.
Sau khi xây dựng xong hệ thống cả phần cứng và phần mềm, bạn có thể kiểm tra hoạt động
của nó. Hiển nhiên ở đây chúng ta chỉ xây dựng ứng dụng ở mức độ đơn giản nhất. Rất
nhiều trường hợp trong thực tế được bỏ qua. Cốt lỗi của vấn đề là làm sao để bạn có thể
điều khiển, giám sát các thiết bị từ xa.
Giao diện khá đơn giản, bao gồm một TextView để hiển thị tiêu đề là “SMS DEVICE
CONTROLLER”, hai nút nhấn lần lượt là : “turn on the light” và “turn off the light”. Ta
thiết kế giao diện bằng cách hiệu chỉnh file app/res/layout/activity_main.xml bên phần
Design và phần Text. Phần Text hiệu chỉnh như sau:
Để hiệu chỉnh lại giá trị hiển thị của TextView ta vào file app/res/values/string.xml chỉnh
lại key liên kết với TextView.
Vì ứng dụng của chúng ta cho phép (permission) việc gởi và nhận tin nhắn SMS nên phải
thêm các cho phép trong file app/manifests/AndroidManifest.xml. Chú ý các dòng uses-
permission
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sony.sms_control" >
<uses-permission
android:name="android.permission.SEND_SMS">
</uses-permission>
<uses-permission
android:name="android.permission.RECEIVE_SMS">
</uses-permission>
<uses-permission
android:name="android.permission.READ_SMS">
</uses-permission>
<uses-permission
android:name="android.permission.broadcast_sms">
</uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Sau đó ta vào file MainActivity.java để viết code cho ứng dụng thực hiện yêu cầu. Ở đây
tác giả có sử dụng các dòng chú thích để giải thích các đoạn code trong chương trình. Tuy
nhiên phần bên dưới sẽ giải thích các vấn đề này một cách cụ thể hơn.
package com.example.sony.sms_control;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
Board TDGGSM_900 của công ty Futura Electtronica cũng dựa trên SIM900 với board
thiết kế sẵn khe sim, antenna, các chân giao tiếp, …
Nếu các bạn quen sử dụng các board Arduino thì GPRS Shield-EFCom cũng là một sự lựa
chọn. Board thiết kế có khả năng hoạt động trên bốn dãy tần và phù hợp với các board
Arduino.
Hình 5.7 Giao tiếp SIM900 qua hai chân khác chân Tx, Rx mặc định
Lưu ý: Chúng ta hoàn toàn có thể làm theo phần cứng như hình 3.6, khi đó code cho
Ardunio chỉ cần chỉnh lại cho phù hợp.
Đối tượng điều khiển của chúng ta trong mạch khá quen thuộc và đơn giản: một led đơn
3mm. Trên board Arduino có các chân I/O dùng cho mục đích xuất nhập. Ta chọn ngẫu
nhiên chân số 5 kết nối đến led đơn. Các bạn chú ý nên dùng một điện trở hạn dòng cho
Để nhận được tin nhắn từ module SIM ta xây dựng hàm receive_uart(), hàm này sẽ kiểm
tra xem có tin nhắn đến không? Nếu có tin nhắn đến thì nó sẽ lần lượt đọc nội dung tin
nhắn chứa vào mảng buff1. Lưu ý: trong khung dữ liệu tin nhắn đến chứa cả số thuê bao
và thông điệp tin nhắn. Và việc thực hiện giao tiếp giữa vi điều khiển và module SIM được
thực hiện theo chuẩn UART. Bạn hoàn toàn có thể viết chương trình ngắt cổng nối tiếp
UART để xử lý việc nhận tin nhắn đến cho đơn giản. Ở đây chúng ta xây dựng một hàm
con cho việc nhận tin nhắn vì không dùng các chân Tx, Rx chuẩn của Arduino, và khi
dùng hàm con thì buộc phải thực hiện phương thức hỏi vòng (polling) để kiểm tra xem có
tin nhắn đến hay không, nghĩa là hàm con này phải được lặp lại liên tục. Như vậy, điều này
chỉ phù hợp với các hệ thống xử lý đơn giản, khối lượng xử lý ít.
Chúng ta xem xét hàm con nhận dữ liệu bên dưới:
void receive_uart()//hàm nhận tin nhắn và lưu vào mảng //buff1
{
while ((SIM900.available() == 0)) {}
while (SIM900.available() > 0)
Sau khi lưu toàn bộ dữ liệu Module sim gửi về trong chuỗi buff1 ta bắt đầu xử lý tin nhắn,
trong hàm xử lý tin nhắn (hàm xu_ly_sms() ) gồm các công việc: tách số điện thoại bằng
cách tìm 12 ký tự bắt đầu bằng +84. Tìm trong nội dung tin nhắn có dãy ký tự là 1234 ON1
hay 1234 OFF1 không? Nếu có thì ta sẽ cho led sáng hoặc tắt tương ứng (tác động lên
chân I/O kết nối led đơn), và phản hồi về số điện thoại điều khiển bằng tin nhắn The light
is ON hay The light is OFF tương ứng với trạng thái led đơn đã điều khiển.
void xu_ly_sms() //xử lý chuỗi vừa nhận
{
//tìm vị trí số điện thoại bắt đầu bằng “+8”
for(i=0;i<k;i++)
{
if((buff1[i]=='+')&&(buff1[i+1]=='8')){
index=i;
break;
}
}
//tách số điện thoại từ mảng buff1 chứa vào mảng sdt
j=0;
for(i=index;i<(index+12);i++){
sdt[j]=buff1[i];
j++;
}
ktsdt=true;
for(i=0;i<13;i++)
{
if(sdt[i]!=number[i])
{
ktsdt=false;
break;
}
}
if(ktsdt==true){
if(strstr(buff1,"1234 ON1")!=NULL){
digitalWrite(13,HIGH);
sendSMS("the light is ON");
}
if(strstr(buff1,"1234 OFF1")!=NULL){
digitalWrite(13,LOW);
sendSMS("the light is OFF");
}
}
}
Hàm gửi tin nhắn: dựa vào các lệnh AT của Module SIM để ta viết hàm này. Bạn hoàn
toàn có thể dựa vào các lệnh bên dưới để gởi tin nhắn tới số điện thoại để bỏ qua công đoạn
tìm hiểu tập lệnh AT, nhưng nhớ phải thay thế số điện thoại của thiết bị phù hợp.
void sendSMS(String message)
{
SIM900.print(“AT+CSCS=\”GSM\”\r\n”); //lệnh AT để gửi tin
Như vậy tương ứng với ứng dụng Android trên, khi gửi tin nhắn điều khiển tắt mở thiết bị
thì Arduino sẽ thực hiện bật tắt đèn và trả lời lại bằng tin nhắn tương ứng để thông báo
trạng thái.
Trạm nhận
Tín hiệu GPS tín hiệu
GPS
Thông tin hỗ
Thiết bị cầm tay có trợ
GPS và GSM Trung tâm Dịch vụ hỗ
Trạm gốc dịch vụ di trợ GPS
động
Trạm gốc
Hình 6.2 Xác định vị trí bằng công thức lượng giác
Hình 6.3 Hình biểu tượng của ứng dụng Google Maps mới cập nhật
Hình 6.15 Giao diện chính của ứng dụng xác định vị trí
Với ứng dụng này ta xây dựng giao diện gồm một <fragment> chứa Google Maps. Ta thêm
hai editText để nhập cũng như hiển thị kinh độ, vĩ độ. Người dùng có thể nhập kinh độ, vĩ
độ vào hai ô này và nhấn nút “Tìm”, khi đó vị trí tương ứng sẽ hiển thị trên Maps. Khi bạn
nhấn vào nút “Yêu cầu” thì thiết bị sẽ gởi một tin nhắn yêu cầu đến thiết bị cần định vị.
Trong bài này thực ra là ta gởi tin nhắn yêu cầu đến số điện thoại của thiết bị ta cần giám
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("home"));
}
else
{
kinhdo=Float.parseFloat(chuoi1);
vido=Float.parseFloat(chuoi2);
LatLng sydney = new LatLng(vido, kinhdo);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("vị trí hiện tại"));
}
}
});
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String sms_extra= "pdus";
Bundle bundle= intent.getExtras();
Object[] smsArr = (Object[]) bundle.get(sms_extra);
for ( int i= 0; i< smsArr.length; i++){
SmsMessage smsMessage= SmsMessage.createFromPdu((byte[])
smsArr[i]);
body = smsMessage.getMessageBody();
address= smsMessage.getDisplayOriginatingAddress();
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
Xây dựng hàm cho trình xử lý sự kiện nhấn nút “Yêu cầu”.
Khi nhấn nút “Yêu cầu”, ứng dụng sẽ lập tức gửi tin nhắn đến thiết bị cần xác định vị trí,
yêu cầu thiết bị đó gửi lại tin nhắn có chưa kinh độ và vĩ độ. Lưu ý số điện thoại của thiết
bị ta sẽ gởi tin nhắn đến, cú pháp tin nhắn ta có thể tự quy ước và làm theo. Lệnh Toast
hiển thị trạng thái của việc yêu cầu lên giao diện chính.
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
String messageToSend = "SEND"; //nội dung tin nhắn yêu cầu
String number = "+84xxxxxxxxx"; //số điện thoại của thiết bị.
SmsManager.getDefault().sendTextMessage(number, null,
messageToSend, null,null);
}
catch(Exception e){
Toast.makeText(getApplicationContext(), " yêu cầu thất
bại.",Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(), "Yêu cầu vị
trí.",Toast.LENGTH_LONG).show();
}
});
Trong đó, number là số điện thoại của thiết bị cần xác định vị trí, messageToSend là nội
dung của tin nhắn yêu cầu. Câu lệnh SmsManager.getDefault().sendTextMessage là câu
lệnh gửi tin nhắn một cách tự động mà người dùng không cần phải thao tác.
Hàm nút nhấn tìm kiếm :
btn_search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"Tìm vị trí theo kinh độ, vĩ
đô.",Toast.LENGTH_LONG).show();
String chuoi1 = text_kinhdo.getText().toString();
String chuoi2 = text_vido.getText().toString();
String qtr_trong= "";
final double kinhdo;
final double vido;
if ((chuoi1.equals(qtr_trong))&&(chuoi2.equals(qtr_trong)))
{
vido= 10.851214;
kinhdo=106.772012;
LatLng sydney = new LatLng(vido, kinhdo);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("home"));
}
else
{
address
Noi_dung_la:
body”
Trên thiết bị android của ta sẽ nhận được và tiến hành tách làm 2 biến phân biệt bởi dấu
cách (khoảng trắng). Biến thứ nhất là words [3] tương tứng với kinh độ và biến 2 là words
[4] là vĩ độ. Sau đó ta tiến hành chuyển đổi kiểu dữ liệu cho 2 biến và cập nhật lại bản đồ
bằng cách nhấn nút tìm kiếm ngay lập tức thì nó sẽ thực hiện lại hàm nút nhấn và bản đồ
sẽ được cập nhật lại vị trí. Dòng lệnh registerReceiver được dùng để kiểm tra mỗi khi có
tin nhắn tới.
Toàn bộ file MapsActivity.java
package com.example.map;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MapsActivity extends FragmentActivity implements
OnMapReadyCallback {
private GoogleMap mMap;
private Button btn_search, btn_send;
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("home"));
}
else
{
kinhdo=Float.parseFloat(chuoi1);
vido=Float.parseFloat(chuoi2);
LatLng sydney = new LatLng(vido, kinhdo);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("vị trí hiện tại"));
}
}
});
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String sms_extra= "pdus";
Bundle bundle= intent.getExtras();
Object[] smsArr = (Object[]) bundle.get(sms_extra);
for ( int i= 0; i< smsArr.length; i++){
SmsMessage smsMessage= SmsMessage.createFromPdu((byte[])
smsArr[i]);
body = smsMessage.getMessageBody();
address= smsMessage.getDisplayOriginatingAddress();
sms= "ban_co_tin_nhan_tu:\n"+ address +
"\nNoi_dung_la:\n"+ body;
}
if (address.equals("+84966870820"))
{
String[] words =sms.split("\\s");
for (String w:words){
System.out.println(w);
};
sms_kinhdo=words[3];
sms_vido=words[4];
text_kinhdo.setText(sms_kinhdo);
text_vido.setText(sms_vido);
}
};
6.3 VIẾT CHƯƠNG TRÌNH CHO THIẾT BỊ NHẬN TIN NHẮN YÊU CẦU
VÀ GỬI LẠI VỊ TRÍ
Thực tế ta xây dựng một thiết bị có thể nhận, gởi tin nhắn SMS và có thể định vị GPS.
Thông thường ta sẽ dùng Module Sim để làm việc này. Khi đó nếu nó nhận được tin nhắn
từ số điện thoại yêu cầu, nội dung đúng cú pháp quy định nó sẽ gởi ngược lại tin nhắn chứa
thông tin vị trí. Từ đó ứng dụng yêu cầu sẽ định vị và hiển thị trên google Maps như phần
đã nói ở trên. Nhưng vì chương tin nhắn SMS tác giả đã trình bày riêng, các bạn có thể
tham khảo và kết hợp với bài này. Ở đây, tác giả xây dựng một ứng dụng tiếp theo, ứng
dụng này sẽ cài trên một điện thoại cần định vị. Khi có tin nhắn yêu cầu xác định vị trí, nó
sẽ lấy thông tin vị trí và gởi ngược lại số điện thoại yêu cầu. Vì ứng dụng này chỉ cần lấy
dữ liệu trong máy là GPS và phần nào đó là công cụ Network hỗ trợ việc xác định vị trí
nên chúng ta không cần phải xin API mà tới thẳng phần viết ứng dụng.
Hình 6.17 Các yêu cầu quyền cơ bản cho ứng dụng
6.4 MÔ PHỎNG
Phía ứng dụng của thiết bị cần theo dõi:
Sau khi nhận được tin nhắn yêu cầu, ứng dụng sẽ tự động gửi tin nhắn kèm vị trí phản hồi
Hình 6.19 Tin nhắn yêu cầu gửi tới và phản hồi bằng tin nhắn vị trí
Sau khi gửi yêu cầu ứng dụng sẽ nhận được tin nhắn phản hồi kèm với vị trí gồm 2 thành
phần là kinh độ và vĩ độ sẽ được hiện thị ra trên giao diện. Người dùng sẽ nhấn nút “Tìm”
để Google Maps dẫn tới vị trí đó trên bản đồ
Hình 6.21 Giá trị được biểu thị và vị trí thiết bị được hiển thị trên bản đồ
Bluetooth
Phần mềm điều HC05/06 Thiết bị 1
khiển chạy trên
thiết bị Android
Bluetooth Cảm biến
(Điều khiển và
Vi xử lý
giám sát thông
qua Bluetooth)
Trong chương này, chúng ta sẽ thiết kế một hệ thống đơn giản gồm một phần mềm chạy
trên điện thoại Android có các nút nhấn điều khiển đóng mở thiết bị, khi người dùng tác
động trên phần mềm, các lệnh điều khiển sẽ gởi đến bộ điều khiển thông qua giao thức
Bluetooth. Bộ điều khiển được thiết kế có khả năng kết nối Bluetooth với điện thoại. Khi
nhận được lệnh điều khiển từ điện thoại nó sẽ điều khiển thiết bị đóng mở. Thiết bị điều
khiển ở đây chính là các led cho trực quan và đơn giản. Khi điều khiển xong các thiết bị
thì nó phản hồi trở lại điện thoại để cập nhật trạng thái thiết bị. Tương tự như khi điều khiển
Khi khởi động ứng dụng, giao diện sẽ như trên, ấn vào Scan để quét các thiết bị
Bluetooth xung quanh. Danh sách các thiết bị được hiển thị ra, sau đó nhấn vào thiết bị
muốn ghép nối, đánh mật khẩu để kết nối với thiết bị đó. Khi kết nối hoàn tất thì việc điều
khiển đơn giản chỉ là thao tác chạm vào ImageView bóng đèn trên màn hình. Ở đây chúng
ta dùng một ImageView là hình một bóng đèn sáng và một bóng tắt. Nếu bóng đèn đang
Giao diện được thiết kế khá đơn giản với LinearLayout, một ImageView hình bóng đèn
cho biết trạng thái của thiết bị; ListView để hiện các thiết bị Bluetooth mà ứng dụng tìm
được; Button Scan để dò tìm Bluetooth của các thiết bị khác.
Giao diện được thiết kế có nội dung như sau:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:orientation="vertical"
android:weightSum="100"
android:id="@+id/layout_me">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
Để hiệu chỉnh lại giá trị hiển thị của TextView ta vào file app/res/values/string.xml chỉnh
lại key liên kết với TextView
<resources>
<string name="app_name">Bluetooth_Control</string>
<string name="img_light">img_light</string>
<string name="btn_scan">Scan</string>
</resources>
Vì ứng dụng cho phép (permission) việc gởi và nhận qua Bluetooth nên ta phải thêm các
cho phép trong file app/manifests/AndroidManifest.xml. Chú ý các dòng uses-permission
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ngb.bluetooth_control" >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Sau đó vào file MainActivity.java để viết code cho ứng dụng thực hiện yêu cầu. Ở đây tác
giả cũng có sử dụng các dòng chú thích để giải thích các đoạn code trong chương trình.
Phần giải thích chi tiết hơn sẽ được trình bày phía dưới.
Toast.makeText(getApplicationContext(),"Bluetooth is already
on",Toast.LENGTH_LONG).show();
}
}
final BroadcastReceiver bReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BTArrayAdapter.add(device.getName());
BTArrayAdapter.notifyDataSetChanged();
Adress.add(device.getAddress());
}
else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
Toast.makeText(MainActivity.this, "kết nối thành công",
Toast.LENGTH_SHORT).show();
chophepgui=true;
}}};
public void find() {
if (myBluetoothAdapter.isDiscovering()) {
// the button is pressed when it discovers, so cancel the //discovery
Nếu thiết bị chưa bật Bluetooth thì bật Bluetooth bằng Intent
Intent turnOnIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
StartActivityForResult(turnOnIntent, 1);
Nếu thiết bị đã bật Bluetooth thì xuất 1 thông báo ra Toast "Bluetooth is already on".
Tiếp đến là việc xử lý nút Scan để quét các thiết bị đang hoạt động:
btn_scan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
find();
}
});
Tạo một lắng nghe cho nút Scan, khi nhấn vào thì gọi hàm find();
Đây là hàm dùng để quét các thiết bị đang hoạt động trong phạm vi có thể kết nối của
Bluetooth
public void find() {
if (myBluetoothAdapter.isDiscovering()) {
myBluetoothAdapter.cancelDiscovery();
}
else {
BTArrayAdapter.clear();
myBluetoothAdapter.startDiscovery();
registerReceiver(bReceiver, new
IntentFilter(BluetoothDevice.ACTION_FOUND));
}
}
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(bReceiver);
}
Dùng Array adapter để hiển thị tên của các thiết bị trong Arraylist ID.
BTArrayAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, ID);
list_device.setAdapter(BTArrayAdapter);
Khi nhấn vào tên của một trong những thiết bị quét được thì kết nối với thiết bị đó thông
qua một lắng nghe được đăng ký.
list_device.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long
id) {
BtAddress = Adress.get(position);
Connect();
} });
Trong lắng nghe này tôi gọi một hàm Connect để kết nối tới thiết bị mong muốn.
public void Connect() {
Log.d(TAG, BtAddress);
BluetoothDevice device = myBluetoothAdapter.getRemoteDevice(BtAddress);
Log.d(TAG, "Connecting to ... " + device);
myBluetoothAdapter.cancelDiscovery();
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
btSocket.connect();
Log.d(TAG, "Connection made.");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log.d(TAG, "Unable to end the connection");
}
Log.d(TAG, "Socket creation failed");
}}
Vậy là hoàn thành các bước cơ bản để kết nối với một thiết bị Bluetooth quét được.
Khi muốn điều khiển thiết bị chỉ cần chạm vào ImageView thì ứng dụng sẽ gửi lệnh điều
khiển nhờ vào một lắng nghe đã được đăng ký.
img_light.setOnClickListener(new View.OnClickListener() {
@Override
else writeData("1");
beginListenForData();
}
});
Trong lắng nghe này tôi gọi hai hàm con khác. Hàm writeData() để gửi ký tự đến bộ điều
khiển. Tùy thuộc vào trạng thái đèn mà dữ liệu tôi gửi sẽ khác nhau. Nếu đèn đang sáng
(light_status=true) thì tôi sẽ gởi số “0” để ra lệnh tắt đèn và ngược lại. Hàm
beginListenForData() để nhận dữ liệu phản hồi từ bộ điều khiển.
private void writeData(String data) {
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.d(TAG, "Bug BEFORE Sending stuff", e);
}
String message = data;
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.d(TAG, "Bug while sending stuff", e);
String msg = "Sự cố! Vui lòng thực hiện kết nối lại";
}}
Trong hàm beginListenForData thì tôi dùng một biến dạng String là Data để lưu ký tự nhận
về, nếu bộ điều khiển trả về là “ON” thì thay đổi ImageView là biểu tượng bóng đèn sáng
và đặt biến trạng thái đèn light_status là true. Ngược lại nếu dữ liệu trả về là “OFF” thì ta
thay đổi ImageView là biểu tượng đèn tắt và đặt biến trạng thái đèn light_status là false.
Tất cả những hàm trên đã được xây dựng sẵn và các bạn có thể lấy để làm tài nguyên cho
những ứng dụng tương tự.
7.3 BỘ ĐIỀU KHIỂN
Chức năng của bộ điều khiển là nhận tín hiệu điều khiển từ ứng dụng sau khi điều khiển
thiết bị xong sẽ gửi lại phản hồi về điện thoại, tất cả đều thông qua Bluetooth.
Đề tài này tôi chọn Module Bluetooth HC05/HC06 là thiết bị để giao tiếp Bluetooth giữa
điện thoại và bộ điều khiển.
Chương trình viết cho Ardunio khá đơn giản. Ở chương trình giao tiếp với Module SIM ta
còn cần phải tìm hiểu tập lệnh AT mới điều khiển Module SIM được. Còn với board HC-
05 mọi thứ trở nên đơn giản hơn vì việc nhận dữ liệu và gởi dữ liệu hoàn toàn qua cổng
nối tiếp chuẩn. Ardunio hỗ trợ việc giao tiếp này khá nhiều qua các hàm Serial. Sau khi
nhận dữ liệu qua cổng UART ta nên sử dụng ngắt cho các ứng dụng phức tạp để giảm thời
gian hỏi vòng và thực hiện quá trình xử lý ngay khi có tín hiệu từ Bluetooth gởi đến. Ứng
dụng ở đây khá đơn giản nên ta chỉ sử dụng chương trình con cho việc nhận dữ liệu từ cổng
nối tiếp.
Yêu cầu của chương trình là khi nhận dữ liệu điều khiển từ điện thoại gởi xuống thông qua
giao thức Bluetooth truyền đến HC-05, dữ liệu được chuyển đến cổng nối tiếp của vi điều
khiển, vi điều khiển sẽ phân tích lệnh điều khiển để tắt/mở đèn led. Sau khi điều khiển led
xong, vi điều khiển sẽ phản hồi trạng thái led về phần mềm chạy trên điện thoại bằng cách
gởi một dữ liệu theo quy định đến HC-05 thông qua cổng nối tiếp. Hoạt động giao tiếp vi
điều khiển với thiết bị thông qua Bluetooth sử dụng module HC-05/06 được dùng khá phổ
biến bởi mức độ tiện dụng và đơn giản của nó. Các bạn nên nắm vững các hàm điều khiển
cơ bản để phục vụ cho việc nghiên cứu các chương tiếp theo.
Lưu ý: Nhiều bạn không chuyên về lãnh vực điện tử có thể thắc mắc là tại sao tác giả hay
chọn đối tượng điều khiển là led đơn mà không phải là một đèn dây tốc 220VAC, động cơ,
quạt…? Xin nhắc lại: chúng ta chọn led đơn là để đơn giản tối đa phần cứng, các bạn có
thể kiểm nghiệm ngay hoạt động một cách trực quan, và với board Ardunio mà bạn mua
về thì việc kết nối thêm led đơn là điều cực kỳ đơn giản, gọn nhẹ. Việc giao tiếp với các
thiết bị điện 220VAC, 24VDC,… chỉ khác ở phần công suất, đối với các sinh viên chuyên
ngành điện tử thì việc này khá dễ dàng. Còn đối với các bạn không chuyên ngành điện tử,
các bạn cũng có thể tìm hiểu cách giao tiếp thông qua transistor, relay, triac,.. bằng cách
tìm kiếm tài liệu trên “Google”. Các bạn có thể tìm được những kiến thức rất bổ ích từ
cộng đồng và yên tâm rằng quá trình thực hiện rất đơn giản. Như vậy, chúng ta đã giải
Hình 8.2 Sơ đồ hệ thống ứng dụng công nghệ OCR trong điều khiển led màu
Việc nhận dạng ký tự từ hình ảnh chụp được sau đó gởi lệnh điều khiển hoặc thông tin
xuống cho NodeMCU có thể thực hiện qua kết nối Bluetooth ở phạm vi dưới 10-20m. Ở
đây tác giả dùng Firebase làm cơ sở dữ liệu trực tuyến để kết nối với NodeMCU vì bản
thân ESP8266 có sẵn kết nối wifi, thuận tiện trong việc mở rộng điều khiển từ xa qua mạng
internet.
8.3.3 THIẾT KẾ ỨNG DỤNG TRÊN ANDROID
Ứng dụng Android thực hiện công việc khá đơn giản là có nút nhấn để mở Camera của
điện thoại lên. Sau đó nhấn nút chụp ảnh. Khi ảnh được chụp thì nó sẽ dùng công cụ nhận
dạng của Google để tách các ký tự từ hình ảnh chụp được. Nếu các ký tự nhận dạng nằm
trong hệ thống các từ điều khiển do ta quy ước bao gồm: red, blue, green, cyan, purple,...thì
nó sẽ thay đổi giá trị của một biến trên Firebase (thông qua mạng internet) mà ứng dụng
đã liên kết đến. Đồng thời nó sẽ hiển thị ký tự nhận dạng được trên giao diện ứng dụng.
Đầu tiên ta xây dựng ứng dụng với giao diện đơn giản gồm một imageview chứa hình ảnh
camera chụp được, một textview hiển thị thông báo chụp ảnh và kết quả ký tự nhận dạng,
một nút nhấn để mở camera chụp ảnh.
<Button
android:id="@+id/button"
android:layout_width="match_parent"
Hình 8.10 Giá trị biến LED trên firebase thay đổi khi nhận dạng chữ blue
Khi ứng dụng nhận dạng được ký tự là green thì biến LED trên Firebase thay đổi thành
“010”
Tiến hành viết code cho NodeMCU xong và chạy thực nghiệm kiểm tra kết quả cùng với
ứng dụng Android trên điện thoại. Ta thấy việc nhận nhận ký tự từ ứng dụng OCR và điều
khiển led màu khá đơn giản. Hi vọng với nền tảng kiến thức cơ bản này độc giả tiếp tục
phát triển thêm cho các nghiên cứu, hoặc ứng dụng thực tế hơn, phức tạp hơn.
Trong chương này, chúng tôi muốn giới thiệu với các bạn về cảm biến gia tốc trong điện
thoại Android và các sử dụng dụng nó thông qua một ứng dụng cơ bản. Mục tiêu của chúng
ta là mang những tương tác giữa con người và điện thoại qua cảm biến gia tốc chuyển thành
những tương tác điều khiển đến các thiết bị điện bên ngoài. Hãy làm một ứng dụng đơn
giản, giả sử có bốn led đơn, chúng ta sẽ viết một ứng dụng Android trên điện thoại để điều
khiển bốn led đơn này sáng tắt dựa vào cảm biến gia tốc. Hay nói cách khác, chúng ta cầm
điện thoại nghiêng tới, lui, trái, phải để đóng/mở các đèn led này. Trong một ứng dụng xa
hơn, nhưng khá gần gũi là các bạn có thể viết ứng dụng điều khiển một chiếc xe chạy tới
lui, qua trái qua phải bằng cách nghiêng điện thoại. Trở lại ứng dụng của chúng ta, nó hoạt
động theo nguyên tắc sau: sử dụng cảm biến Accelerometer tích hợp trong điện thoại
Android và đọc về các giá trị gán vào ba thông số tương ứng cho ba trục là ax, ay, az. Tạo
ngưỡng để so sánh với các giá trị cảm biến. Khi giá trị ax đọc về bé hơn -2 thì đang nghiêng
điện thoại về phía trước. Lớn hơn 2 thì đang nghiêng về phía sau. Nếu giá trị trong khoảng
-1 đến 1 ta xem như cân bằng. Tương tự cho trục y cũng vậy. Hiển nhiên các ngưỡng này
tùy theo ứng dụng cụ thể bạn có thể thay đổi để phù hợp với độ nhạy hệ thống, sau đó
truyền tín hiệu qua Bluetooth điều khiển bốn bóng đèn tương ứng cho bốn góc nghiêng.
Thao tác quét và kết nối Bluetooth tương tự chương trước, sau khi kết nối hoàn tất ứng
dụng mới cho phép gửi dữ liệu điều khiển đi.
Với bốn ImageView ta hiển thị cho bốn hướng của góc nghiêng, một Listview để lưu các
địa chỉ Bluetooth quét được, một Button để dò tìm các thiết bị có thể kết nối Bluetooth và
một Textview để hiển thị giá trị cảm biến đọc được.
File String.xml hiệu chỉnh với các key như sau:
<resources>
<string name="app_name">Accelerometer</string>
<string name="img_top">img_top</string>
<string name="btn_scan">scan</string>
</resources>
Do giao diện màn hình thiết kế nằm ngang nên phải hiệu chỉnh file manifest như sau:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ngb.accelerometer" >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.sensor.accelerometer"
android:required="true" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="keyboardHidden|orientation"
android:name=".MainActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Chú ý: Các dòng bên dưới activity. Chúng ta sử dụng thuộc tính screenOrientation để thiết
lập hướng màn hình. Với giá trị Landcape là màn hình ngang và Potrait là màn hình dọc.
Trong ứng dụng chúng ta sử dụng cảm biến gia tốc nên phải thiết lập Permission cho phép
sử dụng phần cứng cảm biến gia tốc accelerometer. Và vì chúng ta giao tiếp với thiết bị
bên ngoài qua bluetooth nên bluetooth cũng được cho phép sử dụng. Cuối cùng trong ứng
dụng chúng ta có thể viết chương trình cho phép điện thoại rung lên phản hồi trong một vài
trường hợp nên cũng cần phải thiết lập cho phép rung (vibrate).
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.sensor.accelerometer"
android:required="true" />
Sau đó, chúng ta vào file MainActivity.java để viết code cho ứng dụng thực hiện yêu cầu.
Phần này có sử dụng dòng chú thích để giải nghĩa các đoạn code trong chương trình. Tuy
nhiên, giải thích cụ thể hơn sẽ được trình bày ở phần bên dưới.
package com.example.ngb.accelerometer;
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.UUID;
public class MainActivity extends Activity implements SensorEventListener{
//chú ý đây là class để lắng nghe các cảm biến
TextView txtv_view;
ImageView top,bot,left,right;
registerReceiver(bReceiver, new
IntentFilter(BluetoothDevice.ACTION_FOUND));
}
}
//hàm kết nối Bluetooth
public void Connect() {
Log.d(TAG, BtAddress);
BluetoothDevice device = myBluetoothAdapter.getRemoteDevice(BtAddress);
Log.d(TAG, "Connecting to ... " + device);
myBluetoothAdapter.cancelDiscovery();
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
btSocket.connect();
Log.d(TAG, "Connection made.");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log.d(TAG, "Unable to end the connection");
}
Log.d(TAG, "Socket creation failed");
}}
//hàm gửi dữ liệu qua bluetooth
private void writeData(String data) {
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.d(TAG, "Bug BEFORE Sending stuff", e);
}
String message = data;
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.d(TAG, "Bug while sending stuff", e);
String msg = "Sự cố! Vui lòng thực hiện kết nối lại";
}}
//hủy đăng ký khi ứng dụng thoát
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (chophepgui) {
unregisterReceiver(bReceiver);
}
myBluetoothAdapter.disable();
}}
Sau đó đăng ký lắng nghe cho nó khi BluetoothSocket đã kết nối
this.registerReceiver(bReceiver, filter1);
Trong hàm BroadcastReceiver thêm điều kiện ràng buộc như sau:
else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
Toast.makeText(MainActivity.this, "kết nối thành công", Toast.LENGTH_SHORT).show();
chophepgui=true;
}
Khi kết nối hoàn tất sẽ thông báo ra Toast và biến chophepgui đổi thành true. Khi
chophepgui bằng true thì dữ liệu sẽ được gửi.
Tiếp đến là vấn đề đọc cảm biến gia tốc.
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType()==Sensor.TYPE_ACCELEROMETER){
ax=Math.round(event.values[0]); //đọc giá trị trục x
ay=Math.round(event.values[1]); //đọc giá trị trục y
az=Math.round(event.values[2]); //đọc giá trị trục z
txtv_view.setText("x:"+ax+"y"+ay+"z"+az);}
if (chophepgui==true) {
if (ax <= -2) {
top.setImageResource(R.drawable.top);
writeData("1"); }
else if (ax >= 2) {
bot.setImageResource(R.drawable.bot);
writeData("2");
} else {
top.setImageResource(R.drawable.none1);
bot.setImageResource(R.drawable.none1);
writeData("3");
}
if (ay <= -2) {
left.setImageResource(R.drawable.left);
writeData("4");
} else if (ay >= 2) {
right.setImageResource(R.drawable.right);
writeData("5");
} else {
left.setImageResource(R.drawable.none1);
right.setImageResource(R.drawable.none1);
writeData("6");
}
}
}
Giá trị của cảm biến được gán vào ba biến ax, ay, az và làm tròn bằng hàm round.
Sau khi biến chophepgui là true thì bắt đầu so sánh ngưỡng để biết hướng nghiêng của điện
thoại. Nếu 𝑎𝑥 ≤ −2 thì đang nghiêng phía trước, 𝑎𝑥 ≥ 2 đang nghiêng phía sau, tương tự
Giao diện được thiết kế đơn giản, với nội dung file XML như sau:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
android:orientation="vertical" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="60dp"
android:gravity="center"
android:orientation="vertical"
android:id="@+id/linearLayout">
</LinearLayout>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/textView_ledred"
list_data.setAdapter(V2TArrayAdapter);
for (int i = 0; i <text.size(); i++)
{
String buff = text.get(i);
//đọc nội dung từng gợi ý chuyển đổi và lấy từ khóa
if (buff.contains("bật") || buff.contains("Bật"))
{
if (buff.contains("đỏ") || buff.contains("Đỏ"))
{
writeData("1");
}
if (buff.contains("lục") || buff.contains("Lục"))
{
writeData("2");
}
if (buff.contains("dương") || buff.contains("Dương"))
{
writeData("3");
}
}
if (buff.contains("tắt")|| buff.contains("Tắt"))
{
if (buff.contains("đỏ") || buff.contains("Đỏ"))
{
writeData("4");
}
if (buff.contains("lục") || buff.contains("Lục"))
{
writeData("5");
}
if (buff.contains("dương") || buff.contains("Dương"))
{
writeData("6");
}
}}}}}
public void on(){
if (!myBluetoothAdapter.isEnabled()) {
Intent turnOnIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
workerThread.start();
}
}
Việc giao tiếp, truyền và nhận Bluetooth tương tự như các chương trước, ở đây tôi chỉ trình
bày về phần chuyển giọng nói thành Text.
Quá trình chuyển đổi giọng nói sang text có hai phần chính.
• Hàm con chuyển đổi dữ liệu giọng nói thành text. Hàm này chỉ thực thi khi điện thoại
đã kết nối với thiết bị điều khiển. Sau khi có được dữ liệu giọng nói (đã chuyển thành
text) thì kết quả hiển thị lên listview list_data, sau đó dữ liệu sẽ được chuyển lần lượt
thành các chuỗi dạng string và chứa trong buff. Bây giờ, nhiệm vụ của chúng ta là tìm
trong các chuỗi dữ liệu nhận dạng này (buff) các từ khóa điều khiển để ra lệnh điều
khiển phù hợp, gởi đến bộ điều khiển qua Bluetooth.
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (chophepgui) {
//khi kết nối với một thiết bị nào đó rồi mới thực hiện gửi //được
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_SPEECH && resultCode == RESULT_OK) {
text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
//lưu chuỗi chuyển hóa từ giọng nói vào listview list_data
V2TArrayAdapter = new ArrayAdapter<>(this,
android.R.layout.select_dialog_item, text);
list_data.setAdapter(V2TArrayAdapter);
//đọc và chuyển nội dung lần lượt lưu vào buff
for (int i = 0; i <text.size(); i++)
{
String buff = text.get(i);
//đọc nội dung từng gợi ý chuyển đổi và tìm từ khóa
//Tìm từ khóa Bật/bật để mở đèn led
if (buff.contains("bật") || buff.contains("Bật"))
{
//tìm từ khóa đèn đỏ
if (buff.contains("đỏ") || buff.contains("Đỏ"))
{//nếu đúng từ khóa bật đèn đỏ thì gởi lệnh “1”
writeData("1");
Khi kết nối bluetooth với thiết bị thành công thì chophepgui=true. Việc chuyển đổi chuỗi
và so sánh mới được thực hiện.
if (requestCode == RESULT_SPEECH && resultCode == RESULT_OK)
{text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
Khi nhận được phản hồi cho phép chuyển đổi, lưu chuỗi chuyển đổi vào chuỗi text.
text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
Việc so sánh như xử lý chuỗi thông thường, dùng lớp contains để tìm nội dung từ khóa
trong chuỗi text.
• Bắt đầu việc nhập giọng nói bằng cách nhấn vào imageView micro.
btn_speak.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//tạo intent thực hiện việc nhập giọng nói và ghi lại
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
//chọn ngôn ngữ đầu ra, ở đây tôi chọn FREE_FORM, ngôn ngữ sẽ phụ //thuộc vào thiết
lập của thiết bị
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
//khởi động activityForResult để gửi phản hồi
startActivityForResult(intent, RESULT_SPEECH);
}
});
Hình 10.2 Kết nối Ardunio với HC05 và ba led đơn
Chương trình điều khiển viết cho Arduino gần như không có khác biệt với các chương
trước, vì chỉ bao gồm quá trình nhận dữ liệu từ Bluetooth, tác động các led tương ứng và
phản hồi. Việc giao tiếp giữa Arduino và HC05 dựa theo chuẩn truyền nối tiếp bất đồng bộ
UART quen thuộc với tốc độ baud được thiết lập là 9600. Chân số 7 kết nối led màu xanh
dương, chân số 8 kết nối led màu đỏ, chân số 9 kết nối led màu xnah lục. Các chân này các
bạn chú ý thêm điện trở hạn dòng thích hợp cho nó. Các mã lệnh trên Android gởi xuống
cần phải chú ý để thực thi cho đúng. Mã lệnh điều khiển sáng đèn đỏ, xanh lục xanh dương
lần lượt là “1”, “2”, “3”. Tương tự mã lệnh điều khiển tắt đèn đỏ, xanh lục và xanh dương
lần lượt là “4”, “5”, “6”. Lưu ý các mã là mã Ascii nên trong chương trình vi điều khiển
cũng phải dùng mã Ascii để kiểm tra, hoặc theo cách khác là chuyển mã Ascii thành số nhị
phân bình thường. Đối với các con số từ mã nhị phần chuyển thành Ascii thì cộng thêm
0x30, còn từ Ascii chuyển thành nhị phân thường thì trừ 0x30.
Chú ý: Các ghi chú kế bên các dòng lệnh để hiểu rõ chương trình.
char buff1;
int k;
void receive_uart();
void setup()
{
Serial.begin(9600); //thiết lập tốc độ baud 9600
//thiết lập chân số 7 kết nối led xanh dương là ngõ ra
pinMode(7,OUTPUT);
//thiết lập chân số 8 kết nối led đỏ là ngõ ra
pinMode(8,OUTPUT);
//thiết lập chân số 9 kết nối led xanh lục là ngõ ra
voidloop()
{
//gọi hàm nhận dữ liệu từ port nối tiếp: board HC05
receive_uart();
delay(20);
//điều khiển trạng thái các led tương ứng lệnh điều khiển
//sau khi gọi hàm receive_uart()nếu có lệnh điều khiển //gởi từ phần mềm Android thì
nó sẽ được chứa trong buff1
switch (buff1)
{
case 1: {
digitalWrite(8,HIGH); //sáng led đỏ
//gởi dữ liệu phản hồi cho biết đèn đỏ sáng
Serial.println("ds");
break;
}
case 4: {
digitalWrite(8,LOW); //tắt led đỏ
//gởi dữ liệu phản hồi cho biết đèn đỏ tắt
Serial.println("dt");
break;
}
case 2: {
digitalWrite(9,HIGH); //sáng led xanh lục
//Gởi phản hồi về phần mềm Android cho biết đèn
//xanh lục đã sáng
Serial.println("ls");
break;
}
case 5: {
digitalWrite(9,LOW); //tắt led xanh lục
Serial.println("lt");
break;
}
case 3: {
digitalWrite(7,HIGH); //sáng led xanh dương
Serial.println("xs");
break;
}
case 6: {
digitalWrite(7,LOW); //tắt led xanh dương
Serial.println("xt");
break;
}
default: break;
}}
void receive_uart() //nhận dữ liệu từ port nối tiếp
{
while ((Serial.available() == 0)) {}
while (Serial.available() > 0)
{
buff1 =Serial.read() - 0x30;
//nhận dữ liệu và chuyển dữ liệu từ mã Ascii thành số nhị //phân thông thường để
tiện việc so sánh
}}
- Sau khi đăng nhập xong, ta tiến hành tạo mới một project trên Blynk. Nếu bạn có một
ứng dụng sẵn rồi thì có thể mở nó ra và hiệu chỉnh phù hợp.
- Sau đó lựa chọn phần cứng phần cứng mà chúng ta mà chúng ta sử dụng và đặt tên
cho project. Ở đây chúng ta chọn phần cứng là “ESP8266” tên project là “điều khiển
led” và sau đó nhấn Create.
- Hoàn thành tạo mới project ứng dụng sẽ gửi mã Auth Token vào gmail mà chúng ta
đã dùng để đăng ký tài khoản và dùng mã này để làm việc với project vừa khởi tạo.
Hình 11.5 Thông báo gởi mã Auth Token đến tài khoản liên kết
- Bây giờ chúng ta sẽ tiến hành tạo hai nút nhấn để điều khiển bật tắt hai led đơn bằng
cách chạm vào “Button” và kéo đến màn hình thiết kế.
Hình 11.7 Giao diện xây dựng các Button điều khiển
Hình 11.8 Thiết lập các thuộc tính cho nút Button trong Blynk
- Đặt tên cho nút nhấn thứ nhất là “điều khiển led 1”
- Mode có 2 chế độ là PUSH (nút nhấn thả nút nhấn trở về trạng thái ban đầu) và
SWITCH (nút nhấn thả sẽ chuyển trạng thái, trạng thái đó được giữ nguyên cho đến
khi bạn nhấn lần tiếp theo), trong phần này chúng ta chọn là SWITCH.
- Chạm vào mã PIN để chọn chân, ở đây chúng kết nối led đơn ở chân GPIO16
NodeMCU nên chọn chân là “gp16”. Nếu phần cứng bạn có sự khác biệt, hãy điều
chỉnh cho phù hợp.
Như vậy việc thiết lập đặc tính cho nút nhấn thứ nhất thì đã xong, nút nhấn thứ 2 chúng ta
cũng thiết lập tương tự nhưng tại mã PIN chúng ta chọn chân theo sự kết nối của led đơn
với chân GPIO của NodeMCU.
Sau khi hoàn thành thiết lập cho 2 nút nhấn chúng ta nhấn vào nút Run (Hình tam giác gốc
phải) để khởi chạy Blynk.
Đầu tiên chúng ta phải đăng nhập vào trang Web IFTTT (ifttt.com). Tài khoản đăng nhập
IFTTT phải là tài khoản của Google Assistant. Sau đó, chúng ta sẽ thiết lập câu lệnh để
điều khiển bật tắt led 1 còn led 2 chúng ta cũng làm tương tự. Sau khi đăng nhập xong
IFTTT thì ta nhấn vào nút Create để tạo một Applet mới như hình bên dưới
Như chúng ta đã nói phía trên, mục This này sẽ xác định điều kiện xảy ra để thực thi mục
Then That. Ở đây ta sử dụng Google Assistant cho việc điều khiển nên ta gắn nó với Google
Assistant bằng cách gõ chữ “Google Assistant” vào mục chọn một dịch vụ.
Sau đó ta chọn loại Trigger (kích hoạt) là loại nói một câu đơn “Say a simple Phrase”. Tất
nhiên trong một số điều khiển khác, mục đích khác bạn có thể chọn loại trigger khác.
Ở mục “What do you want to say” (Bạn muốn nói gì?) chúng ta đặt câu lệnh cần nói ở
đây. Các câu lệnh điều khiển bạn có thể đặt theo ý các bạn với sự hỗ trợ ngôn ngữ của
Google Assistant trên thiết bị. Ở đây tôi chọn là “turn on led 1”. Nếu thiết bị các bạn đã
cập nhật Google Assistant phiên bản hỗ trợ tiếng Việt thì bạn có thể đặt câu lệnh tiếng Việt
cho thuận tiện trong việc điều khiển. Trong phần “What do you want to say” có đến ba
tùy chọn cho chúng ta. Điều đó có nghĩa là chỉ cần bạn nói đúng một trong ba câu lệnh thì
nó đều có thể đáp ứng. Bạn cũng có thể tổ hợp các câu lệnh này vừa bằng tiếng Anh và
tiếng Việt kết hợp như hình 11.15.
“ What do you want the Assistant to say in response?” Đây là câu phản hồi của Google
Asistant sau khi mình thực hiện câu lệnh vừa nói. Bạn thích nghe “Chị Gồ” trả lời những
lời ngọt tai thì cứ đưa mẫu câu vào đây!
Sau khi cài đặt câu lệnh xong thì nhấn vào “Create trigger”. Như vậy ta đã thực hiện xong
mục “This”
Sau khi thực hiện xong mục “This” thì ta tiến hành thực hiện mục “That”
Chúng ta điền đầy đủ các thông tin sau đây, lưu ý phải điền đủ đủ và đúng
Sau khi điền đầy đủ thông tin thì ta nhấn “Create action” để hoàn thành.
Như vậy thông qua IFTTT ta liên kết Google Assistant cho sự kiện trigger “This” và
Webhooks cho hành động phản ứng “That”. Hoàn thànhbằng việc nhấn Finish
Ta làm điều tương tự để điều khiển sáng/tắt Led 2. Kết quả sau khi chúng ta thiết lập xong như
hình 11.22.
Hình 11.22 Kết quả sau khi hoàn thành thiết lập cho hai led
Bây giờ chúng ta mở phần mềm Adruino IDE để tiến hành viết chương trình cho Nodemcu.
Chúng ta thấy chương trình rất đơn giản vì chúng ta sử dụng các thư viện và hàm hỗ trợ
gần như hoàn toàn.
#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
char auth[] = " Mã Auth Token ";
char ssid[] = "TÊN WIFI";
char pass[] = "PASS WIFI";
void setup()
{
Serial.begin(9600);
Blynk.begin(auth, ssid, pass);
}
void loop()
{
Blynk.run();
}
Kết thúc chương 11, chúng ta thấy việc điều khiển các led thông qua mạng Internet, sử
dụng luôn cả Google Assistant chạy rất trơn tru. Một ứng dụng IoTs rất “xịn xò” nhưng có
thể thực hiện đơn giản thông qua các thao tác. Mục này rất phù hợp hướng dẫn các bạn
sinh viên ngoài ngành, các bạn sinh viên mới bắt đầu khám phá lãnh vực IoTs. Đặc biệt
Sau khi CREATE PROJECT thì cửa sổ nhảy sang bước 2 như hình dưới, các bạn tải file
google-services.json về và bỏ vào trong thư mục app/src của project mà bạn tạo →
CONTINUE
Sau bước 2 thì đến bước 3, bạn thêm các dòng code được hướng dẫn từ Firebase như hình
dưới, phần này vào phần Android Studio tôi sẽ nói chi tiết hơn các bạn đừng lo.
Vậy là xong 3 bước trên. Tiếp theo, trong hình dưới chọn “Add Firebase to your Android
app”
Bây giờ mới đến phần quan trọng, các bạn sẽ vào MainActivity.java trong Android Studio
để lấy “Android package name” để đăng ký cho Firebase → Sau đó chọn “REGISTER
APP” . Nhớ là phải lấy cho đúng đừng sót chữ nào của package name.
Tiếp sau các bạn vào thiết lập cho phép đọc ghi dữ liệu. Các bạn vào Database → Rule →
chuyển thành “true” chỗ read write. Sau đó nhấn PUBLISH.
Hình 12.7 Thiết lập cho phép đọc ghi dữ liệu lên DataFirebase
Sau đó nhấn vào DATA và bạn sẽ thấy cơ sở dữ liệu dạng cây JSON như hình dưới. Bạn
có thể tạo các biến dữ liệu tại đây.
Và như vậy là Database đã sẵn sàng để đọc ghi dữ liệu lên rồi, giờ chúng ta sẽ xây dựng
một ứng dụng trên Smartphone để gửi dữ liệu lên Database Firebase thôi.
12.3 TẠO MỘT ỨNG DỤNG TRÊN SMARTPHONE
Việc tạo ứng dụng Android đơn giản như các ứng dụng trước. Không có gì khác biệt. Sau
khi khởi tạo xong project ta sẽ chép File Google-services.json vào thư mục.
Ở phần Firebase, yêu cầu ta chép các thư viện cho file Gradle. Ta tiến hành chép các dòng
code như hướng dẫn bên Firebase lúc nãy vào file build.gradle (project). Lưu ý là các phiên
bản Android Studio mới thì Gradle có sự sắp xếp khác biệt một vài điểm. Nhắc lại, nếu bạn
liên kết Firebase vào project một cách tự động như đã từng làm ở các chương trước thì các
thư viện này được thêm vào tự động và ta không phải làm thủ công.
Tiếp theo vào phần layout cho phần mềm, thiết kế một switch (nút gạt điều khiển)
Hình 12.11 Thiết kế giao diện cho ứng dụng trên điện thoại
Đặt ID của Widget này là “switch1”. Sau đó ta qua MainActivity.java để lập trình hoạt
động của ứng dụng. Việc việc code cho ứng dụng điều khiển này các bạn xem lại chương
Xây dựng các ứng dụng IOTs sử dụng Firebase.
Sau khi viết đoạn code và chạy mô phỏng hoặc chạy trên thiết bị thật ta sẽ thấy các biến
trên Firebase được tạo và thay đổi giá trị
Hình 12.13 Kết quả quan sát Firebase khi chạy ứng dụng
Tùy thuộc vào người lập trình có muốn chọn các giao diện có chứa sẵn các công cụ hỗ trợ
hay không, nếu người lập trình muốn giao diện trống thì người lập trình có thể lựa chọn
Blank Activity.
Tiếp theo sau đó là đặt tên cho ứng dụng, chọn phiên bản hệ điều hành, ngôn ngữ lập trình.
Về cơ bản, ứng dụng viết trên đồng hồ thông minh cũng được viết tương tự như trên điện
thoại thông minh nên tác giả sẽ không thực hiện lại. Vì thế xem như ta đã viết một chương
trình cho đồng hồ thông minh. Và bây giờ là cách để nạp chương trình đó cho đồng hồ
“ASUS ZENWATCH 2”
Mở đồng hồ Android, vào cài đặt → tùy chọn nhà phát triển → chọn “gỡ lỗi qua Wi-Fi”
và ta có thể thấy địa chỉ IP của đồng hồ trong hình dưới là 192.168.1.104 cũng như PORT
để kết nối với laptop là 5555:
Vào thư mục ADB (Android Debugging Bridge - ta có thể tải ADB trên rất nhiều nguồn
trên google) → Nhấn Shift và click chuột trái chọn “Open command window here”:
Hình 12.19 Dòng lệnh để kết nối đồng hồ thông minh với máy tính
Hình 12.20 Debugging thông qua wifi giữa máy tính và đồng hồ
Như vậy đến đây ta cơ bản đã xây dựng được một ứng dụng IoTs với một chiếc đồng hồ
thông minh, việc điều khiển trở nên tiện lợi và đa dạng hơn. Chúc các bạn tiếp tục phát
triển những dự án hay phù hợp khác.
Đến đây các bạn cơ bản đã nắm được các kiến thức nền tảng với lập trình di động
trong ứng dụng điều khiển. Các bạn có thể dễ dàng xây dựng một ứng dụng IoTs phù hợp
cho riêng mình. Hệ điều hành Android là một hệ điều hành mở, ngày càng có nhiều hỗ trợ
người dùng hơn bao giờ hết. Ngày trước việc điều khiển bằng giọng nói, khuôn mặt, vân
tay,...có vẻ rất khó khăn, nhưng hiện tại Google cung cấp rất nhiều hàm API để ta dễ dàng
thực hiện điều đó.
Tác giả cố gắng cập nhật, thêm mới nhiều nội dung. Tuy nhiên kiến thức về lập trình
Android trong điều khiển và ứng dụng là rất rộng. Vì thế tác giả cũng chọn lọc một số phần
cốt lõi, phổ biến để đưa đến độc giả. Hi vọng ở lần tái bản tiếp theo sẽ có nhiều cải tiến
phù hợp hơn.
Rất cám ơn quý độc giả, các bạn sinh viên với lòng đam mê khoa học đã đọc hết
quyển sách này. Chúc các bạn luôn giữ nhiệt huyết sáng tạo, vận dụng các kiến thức vào
thực tế để giúp đất nước ngày càng tươi đẹp và phát triển.
Trân trọng.
[1] Nguyễn Văn Hiệp, Lập trình Android trong ứng dụng điều khiển, NXB Đại học Quốc gia
TPHCM – 2016
[2] Trang hỗ trợ các nhà lập trình Android http://developer.android.com , 01-09/2020
[3] Diễn đàn lập trình Android http://stackoverflow.com/questions , 25/03/2020
[4] Cộng đồng Arduino Việt Nam http://arduino.vn/, 28/05/2020