You are on page 1of 188

2016

Đại học Sư Phạm Kỹ Thuật TPHCM

Ths. Nguyễn Văn Hiệp

Marshmallow
Lillipop
Kitkat
Jelly Bean
Ice cream sandwich
Gingerbread

LẬP TRÌNH

ANDROID
TRONG ỨNG DỤNG ĐIỀU KHIỂN
DÙNG CHO SINH VIÊN CHUYÊN NGÀNH ĐIỆN, ĐIỆN TỬ, TỰ ĐỘNG
BỘ GIÁO DỤC VÀ ĐÀO TẠO
TRƯỜNG ĐẠI HỌC SƯ PHẠM KỸ THUẬT
THÀNH PHỐ HỒ CHÍ MINH
*******************
ThS. NGUYỄN VĂN HIỆP

Giáo trình
LẬP TRÌNH ANDROID
TRONG ỨNG DỤNG
ĐIỀU KHIỂN

NHÀ XUẤT BẢN ĐẠI HỌC QUỐC GIA TP.HCM

1
LỜI NÓI ĐẦU

Chào mừng các bạn đến với quyển sách “Lập trình Android trong
ứng dụng điều khiển”! Đây là quyển sách tiếp nối quyển “Lập trình
Android cơ bản” của cùng tác giả mà tôi muốn hướng đến đối tượng là các
sinh viên chuyên ngành Công nghệ kỹ thuật Điện tử, truyền thông, Công
nghệ kỹ thuật Tự động hóa. Quyển sách trình bày các vấn đề rất “sát sườn”
về lãnh vực điều khiển thiết bị qua điện thoại thông minh bao gồm vấn đề
xây dựng phần mềm trên điện thoại Android và thiết kế phần cứng điện tử,
lập trình cho vi điều khiển.
Như chúng ta đã biết ngày nay điện thoại thông minh đã trở nên “gần
gũi” hơn bao giờ hết. Việc sở hữu một điện thoại thông minh thật sự là một
việc không quá khó khăn. Điện thoại thông minh không chỉ là phương tiện
thông tin liên lạc và giải trí. Các ứng dụng giám sát, thu thập và điều khiển
qua điện thoại thông minh ngày càng nhiều. Việt Nam chúng ta đang trong
giai đoạn hội nhập mạnh mẽ, người làm khoa học kỹ thuật phải nhạy bén
với thời đại. Và hiện nay rất nhiều công ty tên tuổi của nước nhà đã mạnh
dạn đầu tư và có những thành công nhất định về các lãnh vực smarthome,
IoT (Internet of Things),…Các sản phẩm được đưa ra thị trường và được
đón nhận. Chúng ta hằng ngày đọc báo vẫn hay thấy đâu đó những mẩu tin
như: anh nông dân A xây dựng được hệ thống tưới nước bằng điện thoại
di động, anh nông dân B xây dựng được hệ thống đo nhiệt độ, độ ẩm nhà
kính từ xa,...Điều đó chứng tỏ rằng lãnh vực IoT đã và đang rất được quan
tâm, không chỉ trong giới khoa học kỹ thuật.
Quyển sách trình bày các vấn đề điều khiển phổ biến qua điện thoại
di động Android như điều khiển bằng tin nhắn SMS, điều khiển, giám sát
thiết bị qua Bluetooth, điều khiển thiết bị bằng giọng nói tiếng Việt, điều
khiển thiết bị sử dụng cảm biến gia tốc, điều khiển thiết bị qua wifi, điều
khiển thiết bị qua mạng internet. Về phần cứng để xây dựng bộ điều khiển,
tác giả chọn một kit vi điều khiển rất phổ biến hiện nay là Arduino UNO
R3 để tận dụng các thư viện, module có sẵn nhằm phát triển nhanh và dễ
hiểu.
Tác giả xin gởi lời cảm ơn đến gia đình, bộ môn Điện tử công nghiệp
và khoa Điện-Điện tử đã tạo điều kiện thời gian, hỗ trợ chuyên môn. Đặc
biệt xin cảm ơn sự đóng góp rất lớn của em Nguyễn Gia Bảo, khoa Điện-
Điện tử đã giúp tôi hoàn thành quyển sách này.
Với mong muốn có một quyển sách thật cơ bản, qua đó bạn đọc làm
ngay những ứng dụng đầu tay nhanh chóng. Tác giả hi vọng nhem nhuốm

2
được ngọn lửa đam mê để bùng cháy tình yêu khoa học kỹ thuật và sáng
tạo của các bạn sinh viên chuyên ngành điều khiển. Mặc dù vậy, với thời
gian hạn hẹp, quyển sách có lẽ còn những sai sót nhất định. Mọi đóng góp
xây dựng vui lòng liên hệ tác giả Nguyễn Văn Hiệp, Khoa Điện-Điện Tử,
Trường Đại học Sư Phạm Kỹ Thuật TPHCM, hoặc qua email:
thewind030282@gmail.com.
Trân trọng.

3
MỤC LỤC
Trang
Lời nói đầu 3

Chương 1
Giới thiệu về android studio và vấn đề xử lý đa 7
luồng
7
1.1 Tổng quan về Android Studio
11
1.2 Cài đặt Android Studio
17
1.3 Hướng dẫn tạo một ứng dụng mới từ Android
Studio và debug ứng dụng
1.4 Làm thế nào để thực thi đa luồng (multi- 29
threading) trong Android với Handler class
Chương 2 53
Giới thiệu arduino và phần mềm Arduino IDE
Chương 3 59
Điều khiển thiết bị qua tin nhắn SMS
Chương 4 81
Điều khiển và giám sát thiết bị qua Bluetooth
Chương 5 101
Điều khiển thiết bị dùng cảm biến gia tốc
Chương 6 117
Điều khiển thiết bị bằng giọng nói
Chương 7 135
Điều khiển thiết bị qua WIFI
Chương 8 165
Điều khiển thiết bị qua internet
Tổng kết 182
Tài liệu tham khảo 183

4
Chương 1
GIỚI THIỆU VỀ ANDROID STUDIO
VÀ VẤN ĐỀ XỬ LÝ ĐA LUỒNG

Nếu bạn là người lập trình Android đã lâu, chắc hẳn bạn đã quen với
môi trường của Eclipse. Tuy nhiên với sự phát triển theo hướng riêng của
mình, giờ đây Google đã giới thiệu Android Studio để thay thế cho Eclipse.
Có thể có những bước khác biệt ban đầu, tuy nhiên với sự cam kết hoàn
thiện và cải tiến mỗi ngày để mang đến sự tiện dụng và nhiều hỗ trợ từ
Google thì Android Studio hoàn toàn là vùng đất mới đầy tiềm năng. Ở
quyển Android cơ bản trước đây của tôi đã xuất bản, tất cả các bài ví dụ,
các chương trình được lập trình với Eclipse. Giờ chúng ta cùng làm quen
với Android Studio để có một bước chuyển qua môi trường mới.
1.1 Tổng quan về Android Studio
Để tải Android Studio, các bạn vào link sau sau:
http://developer.android.com/sdk/index.html

Hình 1.1. Giao diện trang tải Android Studio


Bạn bấm vào nút Download Android Studio để tải ứng dụng về.
Dung lượng của phiên bản ngày 23/03/2014 là 816MB. Bản Android
Studio này chứa đựng các thành phần sau:
- Android Studio IDE
- Android SDK tools
- Android 5.0 (Lollipop) Platform
5
- Android 5.5 emulator system image with Google APIs
Khi quyển sách được xem xét để xuất bản thì Android Studio đã hỗ trợ
Android 6.0 Platform. Điều đó chứng tỏ sự cải thiện và phát triển không
ngừng nghỉ của tập đoàn công nghệ Google.
Phần mềm yêu cầu cấu hình máy tính cài đặt như sau (tôi chỉ nói về
cấu hình máy chạy hệ điều hành Windows)
 Microsoft® Windows® 8/7/Vista/2003 (32 or 64-bit)
 Tối thiểu 2 GB RAM, khuyến khích 4 GB RAM
 Ổ cứng dư 400 MB và ít nhất 1 GB cho Android SDK, emulator
system images, và caches
 Độ phân giải màn hình tối thiểu 1280 x 800
 Cài đặt Java Development Kit (JDK) 7 trở lên
 Một số tùy chọn thêm để tăng tốc trình mô phỏng giả lập: Intel®
processor with support for Intel® VT-x, Intel® EM64T (Intel®
64), và Execute Disable (XD) Bit
Một số ưu điểm của Android Studio được Google giới thiệu như sau:
- Trình soạn thảo code thông minh
Cốt lõi của Android Studio là trình soạn thảo mã (code) thông minh
có khả năng phân tích code, tái cấu trúc và hoàn thành mã nâng cao.
Trình soạn thảo mã mạnh mẽ giúp bạn trở thành một nhà lập trình
ứng dụng Android hiệu quả hơn.

Hình 1.2. Giao diện trình soạn thảo thông minh


6
- Các đoạn code mẫu và tích hợp GitHub
Các hướng dẫn để tạo một dự án mới thì dễ dàng hơn bao giờ hết.
Có nghĩa là bạn không phải làm tất cả, Android Studio đã hỗ trợ bạn một
số bước đầu cần thiết.
Bắt đầu các dự án với các code mẫu, thậm chí nhập các code mẫu
của Google từ GitHub.
Github http://github.com , còn được gọi là mạng xã hội dành cho các
nhà phát triển. Github hoạt động tháng 2 năm 2008, là một dịch vụ sử
dụng hệ thống quản lý phân tán GIT giúp người dùng lưu trữ source code
(mã nguồn) cho các dự án.

Hình 1.3. Giao diện Import code mẫu vào project


- Hỗ trợ nhiều màn hình ứng dụng
Ta có thể dễ dàng xây dựng các ứng dụng cho điện thoại Android,
tablet Android, các thiết bị đeo chạy Android wear, Tivi Android, kính
Google.
Với sự hỗ trợ module và màn hình giao diện mới này ta có thể dễ
dàng để quản lý các project và ta các tài nguyên.

7
Hình 1.4. Một số giao diện màn hình mẫu trong Android Studio
- Có tất cả các thiết bị ảo cho các loại màn hình với hình dáng và
kích thước khác nhau
Android Studio đi kèm cấu hình trước hình ảnh thiết bị mô phỏng
được tối ưu.
Trình quản lý thiết bị ảo được cập nhật và sắp xếp hợp lý cung cấp
các hồ sơ thiết bị được định nghĩa trước cho hầu hết các thiết bị Android
thông thường.

Hình 1.5. Một số thiết bị ảo trong Android Studio


8
- Android được xây dựng tiến hóa với Gradle
Tạo ra nhiều file APK cho ứng dụng Android của bạn với các thuộc
tính khác nhau bằng việc sử dụng một project giống nhau.
Quản lý ứng dụng phụ thuộc với Maven.
Xây dựng APK từ Android Studio hoặc dòng lệnh.
Maven là một công cụ lĩnh hội và quản lý dự án. Nó cung cấp cho
các nhà phát triển ứng dụng một framework vòng đời xây dựng hoàn
chỉnh. Maven có thể thiết lập các cách để làm việc theo tiêu chuẩn trong
một thời gian rất ngắn. Khi đó hầu hết các ứng dụng là đơn giản và có thể
tái sử dụng, Maven giúp các nhà phát triển thoải mái hơn, dễ dàng hơn
khi tạo ra các báo cáo, kiểm tra, xây dựng và thử nghiệm các thiết lập tự
động.

Hình 1.6. Sử dụng Gradle

1.2 Cài đặt Android Studio


Vào thư mục download ứng dụng về và tiến hành cài đặt theo trình
tự sau:
- Nhấp đúp vào file android-studio-bundle-135.1740770-windows
để cài đặt

9
Hình 1.7. Giao diện chào mừng việc cài đặt Android Studio

Hình 1.8. Giao diện chọn các thành phần cài đặt

10
Hình 1.9. Giao diện các yêu cầu pháp lý, bản quyền

Hình 1.10. Thiết lập địa chỉ cài đặt

11
Hình 1.11. Thiết lập trình giả lập

Hình 1.12. Chọn thư mục Start Menu

12
Hình 1.13. Quá trình cài đặt đang diễn ra

Hình 1.14. Download một số thành phần


Quá trình cài đặt kết thúc, giao diện lần đầu chạy Android Studio.
Nếu ta là một người lập trình mới, hoàn toàn không có các project trước
đó thì ta chọn mục Start a new Android Studio project. Ở đây do tôi đã có
các project từ Eclipse nên tôi import vào Android Studio

13
Hình 1.15. Import các project từ Eclipse
Sau khi import các project của ta đã có vào thì giao diện như sau.
Phía bên trái chứa các project của ta, phía bên phải là màn hình soạn thảo.
Tôi sẽ nói chi tiết phần này sau, vì trình tự làm có thể khác Eclipse một
chút.

Hình 1.16. Giao diện màn hình soạn thảo với các project đã được
import vào

14
1.3 Hướng dẫn tạo một ứng dụng mới từ Android Studio và debug nó
Dĩ nhiên với nếu bạn muốn bắt đầu một project hoàn toàn mới từ
Android studio thì cũng không phải là việc khó khăn. Ngay sau đây tôi sẽ
hướng dẫn các bạn bắt đầu với một ứng dụng đơn giản.
Ứng dụng của tôi có giao diện như sau:

Hình 1.17. Giao diện ứng dụng GUIBeginer


Khi tôi nhấn vào nút CLICK ON ME thì màn hình sẽ xuất hiện chuỗi
chữ “Chúc bạn có một sự khám phá đầy thú vị” hiện lên màn hình vài giây.
Đầu tiên ta tạo Project mới. Nhấn vào dòng Start a new Android
Studio project

15
Hình 1.18. Giao diện khởi tạo ứng dụng mới
Đặt tên cho ứng dụng và thư mục đặt project (các bạn có thể đặt tên
khác và thư mục khác, ở đây tôi đặt tên và đường dẫn như phần đóng
khung)

Hình 1.19. Đặt tên ứng dụng và tạo đường dẫn lưu ứng dụng
Bạn chọn thiết bị ứng dụng bạn sẽ chạy (điện thoại và máy tính bảng,
ti vi, đồng hồ thông minh hay kính thông minh) và phiên bản Android tối
thiểu của thiết bị mà ứng dụng có thể chạy.

16
Hình 1.20. Chọn loại thiết bị và phiên bản hệ điều hành
Bạn có thể chọn giao diện màn hình mẫu của ứng dụng (Activity
mẫu) mà Android hỗ trợ để việc xây dựng giao diện nhanh chóng. Ở đây
tôi chọn giao diện Activity của ứng dụng là Blank Activity (Activity để
trắng). Chú ý hiện tại khi quyển sách được xem xét trước khi xuất bản
(22/04/2016) thì Android Studio đã chạy phiên bản 1.5, lúc này có một sự
khác biệt nhỏ, bạn nên chọn Empty Activity cho giống các bạn tôi hướng
dẫn). Chúng ta sẽ bàn thảo về các giao diện màn hình mẫu này trong những
chương hoặc ứng dụng có liên quan.

Hình 1.21. Một số Activity cho ứng dụng được cung cấp sẵn
17
Tiếp theo bạn đặt tên Activity, Layout, Title và Menu

Hình 1.22. Đặt tên Activity, Layout, Title và Menu

Giao diện của Android Studio khi đó như sau:

Hình 1.23. Giao diện trình soạn thảo khi tạo ứng dụng
Đến đây bạn đã thấy khá quen thuộc với Eclipse rồi đúng không?
Thật sự không quá khó để bắt đầu với Android Studio, giờ chúng ta hãy
tiếp tục tìm hiểu nào!

18
Nhìn vào bên trái màn hình ta thấy vùng liệt kê những thư mục khá
giống Eclipse.
Chúng ta có thư mục manifests chứa file AndroidManifest.xml nơi
ta khai báo nhiều thứ quan trọng như Activity, Intent, Version,
Permission..
Thư mục Java sẽ chứa file .java chính là code của các Activity. Ở
đây ta chỉ có một Main Activity nên mặc định ta có một file
MainActivity.java
Ở thư mục res\layout ta sẽ có file thiết kế giao diện. Cụ thể là
activity_main.xml.
Ta chú ý thêm thư mục res\values\string.xml

Hình 1.24. Một số file quan trọng của ứng dụng


19
Bên phần các thiết bị ảo để ta chọn và dễ thiết kế giao diện, chạy giả
lập thì Android Studio có liệt kê cho chúng ta khá nhiều thiết bị, ta có thể
chọn thiết bị phù hợp

Hình 1.25. Một số giao diện thiết bị ảo sẵn có


Bây giờ ta bắt đầu thiết kế giao diện theo yêu cầu:
Đầu tiên ta thiết kế một TextView, bằng các kéo từ thanh Palete một
TextView ra màn hình thiết kế. Chú ý là ta có sẵn nhiều kiểu TextView, ở
đây tôi chọn Large Text, có nghĩa là chữ có kích thước tương đối lớn. Bạn
hoàn toàn có thể chọn Small Text và sau đó tùy chỉnh lại size chữ cho phù
hợp.

20
Hình 1.26. Tạo một Text View
Sau đó tôi vào mục Text bên file activity_main.xml để hiệu chỉnh lại
TextView này

Hình 1.27. Hiệu chỉnh TextView


Chú ý dòng android:text= “Large Text”. Bạn có thể sửa lại theo nội
dung bạn mong muốn, tuy nhiên ở đây tôi sửa theo cách khác, có thể gọi
là vòng vo hơn như “an toàn và hợp lý” hơn.

21
Các bạn mở file String.xml và chọn vào dòng Open editor

Hình 1.28. Chỉnh sửa file Strings.xml

Khi đó hộp thoại Editor của Strings.xml hiện ra. Bạn nhấn vào dấu
cộng ( + ) góc trái bên trên để thêm vào một Key liên kết. Ở đây tôi đặt
key là “tieude” với giá trị mặc định là “Chào mừng đến Android Studio”,
sau đó Ok.

Hình 1.29. Thêm Key trong file Strings.xml


Sau đó bạn quay trở lại file activity_main.xml và chỉnh lại nội dung
dòng android:text= “Large Text” thành android:text="@string/tieude"

22
Hình 1.30. Chỉnh sửa TextView trong file activity_main.xml

Bước tiếp theo là bạn tạo một Button

Hình 1.31. Tạo một Button mới


Sau đó giống như TextView bên trên bạn vào Strings.xml để tạo một
Key liên kết cho Button này

23
Hình 1.32. Khai báo một số key liên kết cho Button trong file
Strings.xml
Vào file activity_main.xml chỉnh lại dòng android:text="New
Button" thành android:text="@string/btn_click"
Khi đó giao diện cơ bản như sau:

Hình 1.33. Giao diện cơ bản của ứng dụng GUIBeginer theo yêu cầu

24
Giờ chúng ta viết code để thực hiện theo yêu cầu là khi nhấn vào nút
“CLICK ON ME” nó sẽ xuất hiện câu “Chúc bạn có một sự khám phá đầy
thú vị”
Bạn mở file MainActivity.java tại thư mục
app\java\com.example.sony.guibeginer\MainActivity , lưu ý là tên thực
mục có thể khác do bạn đặt ban đầu.

Hình 1.34. Giao diện file MainActivity cho ứng dụng GUIBeginer
Bạn viết code cho file này như sau (lưu ý có những phần có sẵn, ta
lưu ý các dòng in đậm)
package com.example.sony.guibeginer;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import android.view.View.OnClickListener;
import static android.widget.Toast.*;
public class MainActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
25
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Button btn1 =(Button) findViewById(R.id.button);


btn1.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Chúc bạn có một
sự khám phá đầy thú vị", LENGTH_LONG).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}}

26
Tôi xin nhắc lại là quyển sách này được viết cho các bạn đã có những
kiến thức lập trình Android cơ bản. Các giải nghĩa chi tiết dòng lệnh nằm
trong sách “Lập trình Android cơ bản” của cùng tác giả! Chúng tôi chỉ
giải thích những đoạn code mới hoặc thật sự cần thiết trong quyển sách
này. Nên phần code bên trên rất đơn giản tôi không giải thích, chủ yếu
phần ví dụ đó tôi giúp các bạn làm quen với môi trường Android Studio
mà thôi.

1.4 Cách chạy kiểm tra chương trình (Debug chương trình)
Bạn có thể có nhiều cách để chạy chương trình. Hoặc dùng máy ảo
của chính Android Studio hoặc debug thẳng xuống thiết bị Android mà
bạn đang có. Hoặc dùng một chương trình nào chạy trên window giả lập
môi trường Android để chạy được các ứng dụng apk bạn vừa biên dịch,
hoặc chép file apk ra thiết bị thật chạy. Việc chạy máy ảo của Android
Studio có nhiều thuận lợi là ta không phải cài thêm gì nữa, kích thước màn
hình phù hợp, các phiên bản hệ điều hành được cập nhật mới. Tuy nhiên
khuyết điểm là khá nặng, các máy cấu hình yếu khó đáp ứng. Việc chờ đợi
có thể khiến bạn khó chịu. Một số phần mềm gợi ý cho các bạn: Bluestacks,
Droid4x, Genymotion,…
Tôi sẽ nói về cách debug thiết bị thật thông qua Android Studio. Vì có
rất nhiều thiết bị chạy Android nên tôi không thể hướng dẫn cụ thể được.
Tốt nhất bạn hỏi “anh” Google về cách đưa thiết bị của bạn vào chế độ có
thể debug qua USB. Sau khi bạn thiết lập xong chế độ dành cho developer
bạn cắm cáp kết nối thiết bị Android với máy tính. Trên Android Studio bạn
làm như sau:
- Bạn vào biểu tưởng chú robot xanh, nhấp vào mũi tên xuống chọn
Edit Configurations như hình bên dưới

27
Hình 1.35. Thiết lập trước khi chạy App
Khi đó ta thiết lập một số tùy chọn như sau:
Ở đây ta có một ứng dụng duy nhất nên ta không cần chọn ứng dụng
debug.
Mục General chú phần :
- Package chọn: Deploy default APK
- Activity chọn: Launch default Activity
- Target Device: Tại đây nếu ta dùng thiết bị mô phỏng là máy ảo
thì ta chọn Emulator, sau đó chọn máy ảo chúng ta sẽ debug. Phần
này tôi debug trên thiết bị thật nên tôi chọn USB device.

Hình 1.36. Thiết lập các thông số tùy chọn Run/Debug


28
Tiếp theo bạn chọn thiết bị để debug. Bạn click vào biểu tượng
Attach debugger to Android proccess. Khi đó nó sẽ hiện tên thiết bị bạn
đang kết nối và có thể debug. Ở đây tôi dùng Sony xperia z1 nên tên như
hình bên dưới

Hình 1.37. Chọn thiết bị để debug


Lưu ý một số điện thoại sau khi làm tất cả các công đoạn trên như
Android Studio vẫn không nhận ra, khi đó bạn nên tìm driver cho chính
điện thoại của bạn và cài vô máy tính, restart tất cả lại nhé.
Sau đó ta nhấn nút Run App hoặc tổ hợp phím Shift + F10 để chạy
chương trình. Chương trình chúng ta sẽ được đổ xuống điện thoại, và các
thao tác trên ứng dụng khi đó được hiển thị ở khung logcat góc dưới của
Android Studio.
Chương trình được chạy trên điện thoại và bạn nhấn nút “CLICK
ON ME” để xem kết quả nhé. Tùy theo phiên bản hệ điều hành của thiết
bị Android đang chạy dòng thông báo hiển thị có thể khác đôi chút nhưng
nội dung không thay đổi.

29
Hình 1.38. Giao diện ứng dụng khi chạy và nhấn nút CLICK ON ME
Đến đây hẳn các bạn đã quen với môi trường làm việc của Android
Studio rồi đúng không? Bây giờ chúng ta sẽ đi vào các chương chính của
quyển sách là các vấn đề lập trình điều khiển thiết bị trong Android như
thế nào! Và đây cũng là vấn đề khá rộng, nhưng quyển sách hướng đến các
sinh viên năm ba, năm tư chuyên ngành liên quan điện tử, điều khiển tự
động và đòi hỏi người đọc phải có những nền tảng cơ bản về lãnh vực điện
tử!
Trước khi đi sâu vào vấn đề điều khiển, tôi xin bày vấn đề xử lý đa
luồng trong Android. Vì hầu hết trong các ứng dụng ta điều sử dụng nó,
mà quyển “Lập trình Android cơ bản” của tôi chưa đề cập đến vấn đề này.

30
1.5 Làm thế nào để thực thi đa luồng (multi-threading) trong Android
với Handler class
Trong chủ đề này, chúng ta sẽ thấy được tiến trình thực thi đa luồng
(multi-thread) trong Android. Chúng ta sẽ sử dụng lớp Handler để làm điều
này. Bởi vì các trong quyển Lập trình Android cơ bản của cùng tác giả, tôi
đã không trình bày nhiều về phần này, nhưng ở quyển này khi chúng ta
thực hiện kết nối Bluetooth, wifi, 3G,..trong bài toán điều khiển, chúng ta
sẽ sử dụng lập trình đa luồng rất nhiều. Nên sẽ thật hữu ích nếu bạn nắm
vững những vấn đề tôi sắp trình bày ở đây.
Giới thiệu
Xử lý đa luồng được định nghĩa như là một thuộc tính mà thông qua
đó ta có thể chạy nhiều hơn hai luồng song song của cùng một tiến trình.
Trong tiến trình này, dữ liệu chung được chia sẻ giữa các luồng với nhau,
các luồng cũng được biết như là các tiến trình con được thực thi. Trong
Android, có nhiều cách mà thông qua đó việc xử lý đa luồng được thiết lập
trong ứng dụng của bạn.
Mục tiêu của phần này là:
- Hiểu biết khái niệm cơ bản về đa luồng (multithreading).
- Hiểu biết về lớp Handler trong android.
- Hiểu biết về giao diện Runnable (Runnable interface).
Đa luồng (Multi-Threading) trong Android:
Đa luồng trong Android là một thuộc tính đồng nhất, thông qua đó
nhiều hơn một luồng cùng thực thi, và việc thực thi của luồng này không
làm ẩn đi sự thực thi của luồng khác. Đa luồng trong Android không có
khác biệt với khái niệm đa luồng truyền thống. Một lớp có thể được xem
như một tiến trình (process), nó có các phương thức xử lý của nó như là
các tiến trình con (sub-process) hay được gọi là các luồng (thread). Tất cả
phương thức này có thể chạy đồng thời bằng việc sử dụng thuộc tính của
đa luồng. Trong Android, đa luồng có thể được giải quyết thông qua việc
sử dụng nhiều lớp được xây dựng bên trong. Và Handler class là lớp được
dùng phổ biến nhất.
Khái niệm về Luồng (Thread)
- Luồng là một đơn vị chạy đồng thời của quá trình thực thi.
- Luồng có các ngăn xếp riêng của nó có thể được gọi riêng, nó có
các đối số và biến cục bộ.

31
- Mỗi thực thể máy ảo có ít nhất một luồng chính đang chạy khi
chúng được khởi động. Thông thường, có một vài luồng khác thực
hiện cùng lúc.
- Ứng dụng có thể quyết định phát ra các luồng hỗ trợ cho các mục
đích cụ thể.
Các luồng trong cùng một máy ảo tương tác và đồng bộ hóa bằng
cách sử dụng các đối tượng được chia sẻ và giám sát kết hợp với các đối
tượng này.
Về cơ bản có hai cách chính để một Luồng thực thi trong ứng dụng:
1. Một là cung cấp một class mới để mở rộng Luồng và được viết
thông qua phương thức run()
2. Hai là cung cấp một thực thể luồng mới với đối tượng Runnable
trong suốt quá trình tạo nó.
Trong cả hai trường hợp trên, phương thức start() phải được gọi để
thực thi luồng mới này.

Process 1 (Dalvik Virtual Machine 1) Process 2 (Dalvik Virtual Machine 2)

Các tài nguyên bộ nhớ chung Các tài nguyên bộ nhớ chung

MainThread
MainThread

Thread-2

Thread-1

Hình 1.39. Hoạt động của các luồng trong tiến trình xử lý
Thuận lợi của việc xử lý đa luồng (Multi-Threading)
1. Các luồng chia sẻ tài nguyên của Process (tiến trình) nhưng có thể
thực thi một cách độc lập.
2. Khả năng đáp ứng của các ứng dụng có thể được riêng biệt như:
32
- Luồng chính (main thread) sẽ chạy các UI (giao diện người dùng)

- Các công việc thấp (slow: xét về mức ưu tiên chẳng hạn) được gởi
đến các luồng ngầm (background threads).
3. Luồng cung cấp lớp trừu tượng hữu ích cho việc thực thi đa
nhiệm.
4. Đặc biệt hữu ích trong trường hợp một tiến trình đơn (single
process) phát sinh ra nhiều luồng trên hệ thống đa tiến trình
(multiprocessor: đa xử lý hay đa tiến trình). Khi đó sự đa nhiệm là thật sự
đạt được.
5. Do đó, một chương trình đa luồng vận hành nhanh hơn trên các
hệ thống máy tính có nhiều CPU.
Bất lợi của xử lý đa luồng
1. Việc coding sẽ phức tạp hơn
2. Cần khả năng phát hiện, tránh và giải quyết các bế tắc (deadlock)
Cách tiếp cận của Android với các hoạt động thấp (xét về mức ưu
tiên, thời gian tác động).
Một ứng dụng có thể liên quan đến một công việc tiêu tốn quá nhiều
thời gian, tuy nhiên chúng ta mong muốn rằng UI (giao diện người dùng)
vẫn có thể đáp ứng tốt với người dùng. Android cung cấp cho chúng ta hai
cách để giải quyết tình huống này:
1. Làm các công việc tiêu tốn nhiều thời gian đó trong dịch vụ nền
(background service), bằng sử dụng các notification (thông báo)
để chuyển đến người dùng về bước tiếp theo.
2. Làm các họat động thấp đó trong luồng background.
Sự tương tác giữa các luồng được thực hiện bằng việc sử dụng các
đối tượng (a)Handler và đẩy (b)các đối tượng Runnable đến view chính.
a) Handler Class trong Android
Handler class đến từ đóng gói android.os.Handler, do đó muốn sử
dụng ta phải import nó vào trước. Handler class được sử dụng phổ biến
nhất trong việc xử lý đa luồng. Handler cung cấp thuộc tính gởi và nhận
các tin nhắn (message) giữa hai luồng khác nhau và xử lý sự thi hành của
một luồng với sự hỗ trợ của thực thể lớp Handler đó.

33
Trong lớp android, mỗi luồng được sự hỗ trợ của một thực thể lớp
Handler và nó cho phép luồng đó chạy dọc theo các luồng khác và truyền
thông với chúng thông qua các tin nhắn.
Handler được dùng cho hai việc chính:
- Để sắp xếp các message (tin nhắn) và runnable (hàm có thể chạy)
cho nó thực thi tại một vài thời điểm nào đó trong tương lai.
- Để xếp hàng một hoạt động được thực thi trên luồng còn lại
Cảnh báo:
- Các luồng nền (background threads) không được cho phép tương
tác với UI.
- Chỉ process chính mới có thể truy cập đến view của activity.
- Các biến class toàn cục có thể được thấy và update trong tất cả các
luồng.
Khởi tạo Handler class:
Có hai cách theo sau thường được dùng để khởi tạo Handler class
hỗ trợ cho việc xử lý đa luồng:
 Thông qua hàm tạo mặc định:
Handler handlerObject = new Handler();
 Thông qua hàm tạo tham số
Handler handleObject = new Handler(Runnable
runnableObject, Handler.Callback callbackObject);
Các phương thức của Handler class cho việc xử lý đa luồng:
- Public final Boolean post(Runnable runnableObject){ return
booleanValue; }
Phương thức này đính kèm một thực thể có thể chạy (runnable) với
luồng nó hỗ trợ và các lệnh của thực thể runnable sẽ được thực thi mỗi khi
luồng này được thực thi.
- Public final Boolean postAtTime((Runnable runnableObject,
long timeinMillisecondObject) { return booleanValue; }
Phương thức này đính kèm một thực thể có thể chạy (runnable) với
luồng nó hỗ trợ và các lệnh của thực thể runnable sẽ được thực thi mỗi khi
luồng này được thực thi tại một thời điểm cụ thể theo đối số giây (mili
giây).

34
- Public final Boolean postDelayed((Runnable runnableObject,
long timeinMillisecondObject){ return booleanValue; }
Phương thức này đính kèm một thực thể có thể chạy (runnable) với
luồng nó hỗ trợ và các lệnh của thực thể runnable sẽ được thực thi mỗi khi
luồng này được thực thi sau một thời gian cụ thể theo đối số giây (mili
giây).
b) Runnable Interface (giao diện có thể chạy)
Giao diện Runnable được gọi ra trong một vòng lặp khi luồng đó
được khởi động. Nó sẽ thực thi các lệnh bên trong của nó hoặc gọi các
phương thức khác cho một trường hợp cụ thể hoặc với số lần không xác
định.
Giao diện Runnable này được dùng với lớp Handler để thực thi xử
lý đa luồng, tức là để thực thi một hoặc nhiều luồng trong một thời gian cụ
thể. Runnable là một giao diện thực thi bởi lớp này nhằm hỗ trợ xử lý đa
luồng và lớp này phải được viết bởi phương thức trừu tượng của nó là
public void run().
Phương thức Run() là lõi của xử lý đa luồng nó bao gồm các lệnh
hoặc gọi các phương thức khác cần thiết. Ta có thể tạo một class thực thi
hàm Run() này như sau:
class ClassName implements Runnable {
@override
Public void run()
{ Body of method }
}
Giao diện Runnable cũng có thể được sử dụng bởi lớp thích nghi
được giải thích bên dưới:
Runnable runnableObject =new Runnable()
{
@override
Public void run()
{Body of method
};
}

35
Trước khi bàn về vấn đề khởi tạo xử lý các luồng khác nhau ta hãy
làm một ví dụ về Handler để các bạn có cái nhìn cụ thể hơn.
Ta muốn viết một ứng dụng đếm ngược thời gian. Ban đầu nút nhấn
được ẩn đi, sau 10 giây thì nút nhấn hiện lên và khi đó nếu ta tương tác với
nút nhấn đó nó sẽ hiện ra thông báo. Về giao diện khá đơn giản:

Hình 1.40. Giao diện ứng dụng lúc thiết kế và lúc thực thi
trên máy thật
Về layout ta có một TextView hiển thị tiêu đề, một TextView đếm
ngược số từ 9 về 0, một Button “CLICK ON ME”
File text của activity_main.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.sony.multithreading.MainActivity"
android:id="@+id/layout_me">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/tv_title_caption"
android:id="@+id/tv_title"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true"
36
android:layout_marginTop="30dp"
android:textSize="20sp"/>

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"

android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/tv_timer_caption"
android:id="@+id/tv_timer"
android:textSize="70sp"
android:gravity="center_horizontal"
android:textColor="#e50b8a"
android:layout_above="@+id/bt_click"
android:layout_alignParentEnd="true"
android:layout_marginBottom="113dp" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/bt_click_caption"
android:id="@+id/bt_click"
android:visibility="invisible"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="67dp"
android:onClick="ShowText"/>
</RelativeLayout>

File String.xml chứa các key như sau


<resources>
<string name="app_name">MultiThreading</string>
<string name="tv_title_caption">A button will appear after 10
seconds</string>
<string name="tv_timer_caption">9</string>
<string name="bt_click_caption">Click On Me</string>
</resources>

Ta tiến hành Coding cho chương trình trong file MainActivity.java


import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

Handler hand = new Handler();


Button bt_click;
TextView tv_timer;

37
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv_timer=(TextView)findViewById(R.id.tv_timer);
bt_click=(Button)findViewById(R.id.bt_click);

hand.postDelayed(run1, 1000);
}

public Runnable run1=new Runnable() {

@Override
public void run() {
int count=
Integer.parseInt(tv_timer.getText().toString());
if(count==0) bt_click.setVisibility(View.VISIBLE);
else
{
count=count-1;
//tv_timer.setText(""+count);
tv_timer.setText(String.valueOf(count));
hand.postDelayed(run1, 1000);
}

;
}
};

public void ShowText(View view)


{
Toast.makeText(getApplication(),"Chúc mừng bạn đã hoàn
thành ứng dụng đầu tiên về Handler",Toast.LENGTH_LONG).show();
return;
}
}

Giải thích về chương trình trên:


Thiết lập ban đầu ta sẽ cho nút “CLICK ON ME” ẩn đi trong file
thiết kế xml dùng thuộc tính:
android:visibility="invisible"
Ta tạo một Hanler có tên là hand:
Handler hand = new Handler();
Lưu ý ta phải tự add thư viện Handler vào dùng cú pháp
import android.os.Handler;
Tại hàm onCreate ta sử dụng Handler dùng hàm
hand.postDelayed(run1, 1000);
38
có nghĩa là phương thức Runnable “run1” sẽ được thực hiện sau mỗi
1000 mili giây. Giờ ta sẽ xem xét phương thức Runnable “run1”
public Runnable run1=new Runnable() {

@Override
public void run() {

int count=Integer.parseInt(tv_timer.getText().toString());
if(count==0) bt_click.setVisibility(View.VISIBLE);
else
{
count=count-1;
//tv_timer.setText(""+count);
tv_timer.setText(String.valueOf(count));
hand.postDelayed(run1, 1000);
}

;
}
};

Ta khai báo biến count để chứa giá trị TextView tv_timer, đây là giá
trị thời gian đếm ngược. Ta phải chuyển từ dữ liệu chuỗi sang số nguyên.
Ban đầu nó hiển thị số 9, và ta muốn sau mỗi giây nó sẽ giảm đi một đơn
vị cho đến khi bằng 0 thì sẽ hiện nút nhấn “CLICK ON ME”. Và do ta đã
dùng phương thức hand.postDelayed(run1,1000), vì thế hàm run1 này sẽ
được thực thi sau mỗi giây. Và khi đó trong hàm run1 ta sẽ kiểm tra giá trị
của count. Nếu nó bằng 0 thì ta cho hiện nút “CLICK ON ME” lên dùng
cú pháp bt_click.setVisibility(View.VISIBLE); Nếu count khác 0 thì ta
giảm count và thiết lập lại cho Runnable của Handler tiếp tục thực thi sau
giây tiếp theo dùng cú pháp như ban đầu hand.postDelayed(run1, 1000);
Như vậy qua ví dụ trên ta đã có cái nhìn dễ hiểu hơn về Handler và
phương thức Runnable. Giờ ta sẽ nói về các luồng dữ liệu, cách tạo ra nó
và sử dụng nó như thế nào.
Trước tiên, ta xem xét cách xếp hàng tin nhắn của handler
Một luồng thứ cấp (background) muốn thông tin với luồng chính thì
nó phải yêu cầu một mã tin nhắn bằng việc sử dụng phương thức
obtainMesage(). Khi có được mã tin nhắn này rồi thì luồng background có
thể làm đầy dữ liệu và đính kèm nó đến hàng đợi tin nhắn của Handler
bằng việc sử dụng phương thức sendMessage().

39
Handler sử dụng phương thức handleMessage() để liên tục phát hiện
sự hiện diện của các tin nhắn mới chuyển đến luồng chính.
Một tin nhắn được tách ra từ hàng đợi của tiến trình này có thể trả
lại các dữ liệu cho luồng chính hoặc yêu cầu thực thi các đối tượng
Runnable thông qua phương thức post()
Ta xem xét hình bên dưới để tóm tắt lại quá trình các luồng nền
(background thread) giao tiếp với luồng chính (main thread) thông qua đối
tượng Handler.

Hình 1.41. Các background thread thông tin với main thread thông
qua Handler
Như vậy ta có thể sử dụng các tin nhắn hoặc phương thức post() để
giao tiếp giữa các luồng. Sau đây ta sẽ phân tích từng trường hợp.
Trường hợp sử dụng các tin nhắn.
Lưu ý tên các Thread, Handler, Message tác giả đặt tạm, các bạn khi
làm có thể thay đổi cho phù hợp và dễ hiểu với ứng dụng cụ thể.

40
Main Thread (Luồng chính) Background Thread (Luồng nền)
…. …
//tạo Handler mới Thread backgroundJob = new
Thread (new Runnable (){

Handler myHandler=new
Handler(){ @Override
public void run( ){
@Override //…làm gì đó với luồng này
public void //yêu cầu một mã tin nhắn từ
handleMessage(Message msg){ //luồng chính
//đính kèm các dữ liệu cần thiết
//vào msg này
//làm gì đó với tin nhắn nhận
//được… Message
msg=myHandler.obtainMessage();
//update các giao diện UI nếu
//cần ….
… //chuyển tin nhắn đến hàng đợi
//luồng chính
} //handleMessage
myHandler.sendMessage(msg)
} //run
};//myHandler
}); //background thread

//cho phép luồng này thực thi song


song
backgroundJob.start();

Trường hợp sử dụng phương thức post( )


Main Thread (Luồng chính) Background Thread (Luồng nền)
…. //đây là đối tượng “Runnable” sẽ được
//thực thi khi luồng nền chạy. Ta đặt
//tạo Handler mới
//tên backgroundTask

41
Handler myHandler=new private Runnable backgroundTask=
Handler(); new Runnable ( ) {
@Override
@Override public void run( ) {
public void onCreate( //làm các công việc background ở
//đây
Bundle savedInstanceState) {
//có thể tương tác các biến toàn cục

//nhưng không thể tác động UI
//tạo một luồng background
//gọi hàm Runnable của luồng
Thread myThread1= new
//chính
Thread(backgroundTask,
“backAlias1”); myHandler.post(foregroundTask);
myThread1.start();
}//run
} //onCreate }; //backgroundTask

//đây là hàm runnable của
//luồng chính
//ta đặt tên là foregroundTask
private Runnable
foregroundTask
=new Runnable( ){
@Override
public void run( ) {
//ta có thể làm việc trên các UI
//tại đây nếu cần

}

42
Sau đây ta sẽ làm hai ví dụ để minh họa cho hai trường hợp trên. Xét
ví dụ đầu tiên ta sẽ dùng tin nhắn để thực hiện việc thông tin giữa các
luồng.
Khi ta nhấn vào nút “CREAT A THREAD” thì chương trình sẽ phát
sinh một luồng background. Luồng background sẽ “ngủ” trong một giây,
sau mỗi một giây nó sẽ phát sinh một con số ngẫu nhiên từ 0-100, con số
này sẽ được truyền qua main thread thông qua tin nhắn Handler.
Main thread sẽ hiển thị giá trị ngẫu nhiên từ background thread gởi
qua, đồng thời cập nhật giá trị thanh ProgressBar. Khi giá trị progressBar
đụng giá trị max thì nó sẽ thay đổi biến toàn cục để background thread sẽ
không gởi tin nhắn qua luồng chính nữa. Lưu ý chỉ có main thread mới có
thể thay đổi UI. Chúng ta cứ làm và đọc thật kỹ code để hiểu rõ hơn về
hoạt động của các luồng dữ liệu nhé!

Hình 1.42. Giao diện ứng dụng ở các trạng thái khi chạy
Phần design giao diện ta có file main_activity.xml
<?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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.example.sony.multithread_usingmessage.MainActi
vity"
43
android:orientation="vertical"
android:weightSum="100">

<TextView
android:layout_width="fill_parent"
android:layout_height="0sp"

android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/tv_status_caption"
android:id="@+id/tv_status"
android:layout_marginTop="5sp"
android:layout_weight="5"
android:visibility="invisible"
/>

<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="0sp"
android:id="@+id/myBar1"
android:layout_weight="5"
android:max="100"
android:indeterminate="false"
android:progress="0" />

<TextView
android:layout_width="fill_parent"
android:layout_height="0dp"

android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/tv_display_caption"
android:id="@+id/tv_display"
android:layout_weight="30"
android:background="#3166e0"/>

<Button
android:layout_width="wrap_content"
android:layout_height="0sp"
android:text="Creat a thread"
android:id="@+id/button"
android:layout_gravity="center"
android:layout_weight="10" />

</LinearLayout>

Nội dung file String.xml


<resources>
<string name="app_name">MultiThread_UsingMessage</string>
<string name="tv_status_caption">Working...</string>
<string name="tv_display_caption">Returned by background
thread</string>
</resources>

44
Nội dung file MainActivity.java như sau:
package com.example.sony.multithread_usingmessage;

import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import android.os.Handler;

import java.util.Random;

public class MainActivity extends AppCompatActivity {


ProgressBar myBar1;
TextView tv_status,tv_display;
Button btnRedo;
boolean isRunning=false;

int Max_sec=60; //(giây) chu kỳ cho background thread


String strTest="Gobal value seen by all threads ";
int intTest=0;

Handler myhandler=new Handler(){


@Override
public void handleMessage(Message msg){
String returnedValue=(String)msg.obj;
//sau khi nhận được giá trị từ background thread ta
sẽ
// hiển thị giá trị ra TextView display
tv_display.setText("Returned by background
thread:\n\n"
+returnedValue);
//tăng giá trị progressBar lên 2 đơn vị
myBar1.incrementProgressBy(2);
//kiểm tra xem đến điểm kết thúc luồng chưa?
if(myBar1.getProgress()==Max_sec){
tv_display.setText("Done \n back thread has been
stopped");
isRunning=false;

}
if(myBar1.getProgress()==myBar1.getMax()){
tv_status.setText("Done");
myBar1.setVisibility(View.INVISIBLE);
btnRedo.setVisibility(View.VISIBLE);
}
else {
tv_status.setText("Working..." +
myBar1.getProgress());

45
}

}
}; //handler

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myBar1=(ProgressBar)findViewById(R.id.myBar1);
myBar1.setMax(Max_sec);
myBar1.setVisibility(View.INVISIBLE);

tv_status=(TextView)findViewById(R.id.tv_status);
tv_display=(TextView)findViewById(R.id.tv_display);

btnRedo=(Button)findViewById(R.id.button);
btnRedo.setVisibility(View.VISIBLE);

btnRedo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {

tv_status.setVisibility(View.VISIBLE);
myBar1.setProgress(0);
strTest +="-01";
intTest =1;

final Thread backgroundThread=new Thread(new Runnable() {


@Override
public void run() {
try{
for(int i=0;i<Max_sec&&isRunning;i++){
//ở đây ta không thể dùng phương thức Toast hoặc
//thay đổi UI được
Thread.sleep(1000);
Random randomValue=new Random();
String data="Thread Value: "+(int)
randomValue.nextInt(101);

data +="\n" +strTest+ " "+intTest;


intTest++;

Message msg=
myhandler.obtainMessage(1,(String)data);

if(isRunning){
myhandler.sendMessage(msg);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});//background
isRunning=true;
backgroundThread.start();
46
myBar1.setVisibility(View.VISIBLE);
btnRedo.setVisibility(View.INVISIBLE);
}
});

public void onStop(){


super.onStop();
isRunning=false;
}

Chắc hẳn sau khi làm hai ví dụ trên bạn đã có cái nhìn rõ ràng hơn
về hoạt động đa luồng rồi. Bây giờ ta tiếp tục làm thêm một ứng dụng nho
nhỏ để thấy việc giao tiếp giữa hai luồng sử dụng phương thức post(). Giao
diện ứng dụng như hình bên dưới

Hình 1.43. Giao diện ứng dụng sử dụng phương thức post()
File thiết kế giao diện main_activity.xml
<?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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
47
android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.example.sony.multithreading_postmethod.MainAct
ivity"
android:weightSum="100"
android:orientation="vertical"
android:id="@+id/layout1">

<TextView
android:layout_width="wrap_content"
android:layout_height="0sp"

android:textAppearance="?android:attr/textAppearanceLarge"
android:text="@string/tv_display_caption"
android:id="@+id/tv_display"
android:layout_weight="20"
android:textSize="18sp"
android:gravity="left"
/>

<ProgressBar
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/myBar"
android:indeterminate="false"
android:max="100"
android:progress="1"
android:layout_weight="10"/>

<EditText
android:layout_width="wrap_content"
android:layout_height="0sp"
android:id="@+id/editText"
android:inputType="textMultiLine"
android:layout_weight="20"
android:hint="@string/edt_nhap"
android:textColorHint="#e7ace7"
/>

<Button
android:layout_width="wrap_content"
android:layout_height="0sp"
android:text="@string/btn_caption"
android:id="@+id/btn_dosomething"
android:layout_weight="10"
android:gravity="center"
android:layout_gravity="center"/>

</LinearLayout>

Các key khai báo trong String.xml

48
<resources>
<string name="app_name">MultiThreading_Post method</string>
<string name="tv_display_caption">Một vài dữ liệu quan trọng
đang được thu thập. Vui lòng chờ.</string>
<string name="btn_caption">Do something</string>
<string name="edt_nhap">Đang xử lý Foreground. Nhập bất cứ gì
vào đây</string>
</resources>

Coding ở file MainActivity.java như sau:


package com.example.sony.multithreading_postmethod;
// using Handler post(...) method to execute foreground runnables
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.ProgressBar;
import android.widget.TextView;
import android.os.Handler;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

ProgressBar myBar;
TextView tv_display;
EditText edit_nhap;
Button btn_dosomething;
int accum=0;
//lấy thời gian hiện tại của hệ thống gán cho biến
long startingMills=System.currentTimeMillis();
//dulieuhienthi là biến hiển thị lên TextView tv_display
String dulieuhienthi;
//Tạo Hanler
Handler myhandler=new Handler();

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

tv_display=(TextView)findViewById(R.id.tv_display);
edit_nhap=(EditText)findViewById(R.id.editText);

btn_dosomething=(Button)findViewById(R.id.btn_dosomething);
myBar=(ProgressBar)findViewById(R.id.myBar);
myBar.setMax(100);

btn_dosomething.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View view) {
String txt = edit_nhap.getText().toString();
Toast.makeText(getApplication(), "You typed :" +
txt, Toast.LENGTH_LONG).show();
//onClick
49
}
}); //setOnClickListner

}//onCreate

@Override
protected void onStart(){
super.onStart();

myBar.incrementProgressBy(0);
// tạo một luồng background để xử lý công việc bận rộn, công
//việc này có thể kéo dài
Thread myThread1=new Thread(new Runnable() {
@Override
public void run() {
//các lệnh xử lý cho background thread đặt ở đây
try {
for (int i = 0; i < 50; i++)
{ //mô phỏng 1s bận rộn của activity
Thread.sleep(1000);
//giao tiếp với main thread sử dụng
//phương thức post()
myhandler.post(foregroundTask);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
} //backgroundTask
});
myThread1.start();

//Hàm Runnable của luồng chính


private Runnable foregroundTask= new Runnable() {
@Override
public void run() {
try{
int progressStep=2;
tv_display.setText("Một vài dữ liệu quan trọng
đang được thu thập. Vui lòng chờ.." +
"\nSố giây còn lại " + (50-
((System.currentTimeMillis() - startingMills))/1000));
myBar.incrementProgressBy(progressStep);
accum+=progressStep;
if(accum>=myBar.getMax()){
tv_display.setText("Background đã xử lý
xong!");
myBar.setVisibility(View.INVISIBLE);
}
}
catch (Exception e){
e.printStackTrace();
}
}
};
50
}

Ở ví dụ trên, ta khởi tạo một luồng ngầm, luồng này xử lý các công
việc bận rộn kéo dài, ở đây ta dùng phương thức Thread.sleep(1000) để
mô phỏng sự bận rộn của luồng background. Thực chất công việc ở đây là
ta cho luồng background ngủ một giây, sau đó nó sẽ giao tiếp với luồng
chính thông qua hàm Runable của luồng chính để cập nhật các UI mong
muốn. Luồng background sẽ thực hiện 50 lần, mỗi lần tiêu tốn thời gian
khoảng 1s. Và bên hàm Runable của luồng chính, mỗi lần được thực thi
(khi luồng phụ sử dụng phương thức post() để gọi nó) thì nó sẽ tăng giá trị
của thanh progressbar lên 2 đơn vị. Như vậy sau 50 lần thì thanh
progressbar sẽ đầy (vì giá trị max ta thiết lập là 100) và lúc đó luồng
background cũng hoàn thành công việc xử lý.
Quan sát coding cho ví dụ trên ta còn dùng biến thời gian của hệ
thống tính toán thời gian còn lại để cập nhật làm đầy thanh progressbar.

51
Chương 2
GIỚI THIỆU ARDUINO VÀ PHẦN MỀM
ARDUINO IDE

Arduino là board ma ̣ch phát triể n dùng các vi điề u khiể n AVR của
hañ g Atmel đươc̣ rấ t nhiề u sinh viên trong khố i kỹ thuâ ̣t nói chung và
ngành điê ̣n nói riêng biế t đế n. Nếu bạn phải sử dụng một vi điều khiển để
phát triển cho sản phẩm của mình, bạn phải nghiên cứu thật chi tiết phần
cứng, cấu trúc, trình biên dịch dành cho vi điều khiển, sau đó bạn viết các
chương trình và xây dựng phần cứng cho hệ thống. Hiển nhiển đây là một
công việc rất bình thường với một sinh viên chuyên ngành điện tử, lập trình
nhúng. Tuy nhiên, khi bạn muốn phát triển nhanh các ứng dụng đơn giản,
bạn muốn tiết kiệm thời gian xây dựng phần cứng (ở mức chấp nhập được),
bạn muốn kế thừa các thư viện được có sẵn (rất đa dạng và phong phú) thì
giải pháp Ardunio tỏ ra rất hiệu quả. Hoặc bạn là một người không chuyên
về lãnh vực lập trình lắm, các kiến thức chuyên sâu về lập trình vi điều
khiển, thiết kế phần cứng có thể làm cho bạn bối rối và tiêu tốn nhiều
nguồn lực thì bạn nên nghĩ đến Ardunio.
Arduino phát đã đươ ̣c phát triể n từ rấ t lâu, đươ ̣c ra đời vào năm 2005
ta ̣i Italia, và sau đó ta ̣o đươ ̣c những thành công bấ t ngờ nhờ phầ n cứng đơn
giản và ngôn ngữ lâ ̣p trình dễ tiế p câ ̣n. Và nhờ đó Arduino đã xây dựng
đươ ̣c mô ̣t cô ̣ng đồ ng rấ t lớn, các tài nguyên đươ ̣c chia sẽ rô ̣ng rãi giúp các
ba ̣n có thể tự ho ̣c rấ t dễ dàng.

2.1 Tổ ng quan về Arduino


Arduino là board ma ̣ch sử du ̣ng các chip AVR dòng megaAVR của
hañ g Atmel làm vi điề u khiể n trung tâm, với mỗi phiên bản Arduino sẽ sử
du ̣ng mô ̣t loa ̣i vi điề u khiể n khác nhau. Tùy theo yêu cầu của hệ thống cần
phát triển mà người dùng nên xem qua các đặc tính kỹ thuật và chọn một
phiên bản board Ardunio phù hợp. Arduino mặc dù có rấ t nhiề u phiên bản
nhưng viê ̣c lâ ̣p triǹ h không khác nhau nhiề u, nên tôi xin giới thiê ̣u về mô ̣t
phiên bản thông du ̣ng nhấ t là Arduino Uno R3. Đây là phiển biên thông
dụng vì kích thước nhỏ gọn, giá thành phải chăng, tính năng đáp ứng tốt
các ứng dụng đơn giản.

52
Hình 2.1. Hình board Ardunio Uno R3
Dưới đây là các thông số cơ bản của Arduino Uno R3 (theo nhà sản
xuất)
Bảng 2.1. Các đặc tính của Ardunio Uno R3
Vi điều khiển ATmega328P
Điện áp hoạt động 5V
Điện áp vào khuyên dùng 7-12V
Điện áp vào giới hạn 6-20V
Các chân xuất nhập (I/O) 14 (trong đó 6 pin có khả năng điều
digital xung PWM)
Các chân PWM 6 chân
Các chân ngõ và Analog 6 chân
Dòng điện DC trên các chân
20 mA
I/O
Dòng điện trên chân 3.3V 50 mA
32 KB (ATmega328P), trong đó 0.5 KB
Bộ nhớ Flash
được sử dụng cho bootloader
SRAM 2 KB (ATmega328P)
EEPROM 1 KB (ATmega328P)

53
Tốc độ xung clock 16 MHz
Chiều dài 68.6 mm
Chiều rộng 53.4 mm
Trọng lượng 25 g
Nế u nói về yế u tố thành công của Arduino thì phải nói đế n phầ n mề m
lâ ̣p trình đă ̣c trưng Arduino IDE, đây là phầ n mề m lâ ̣p trình thông minh,
giúp cho người lâ ̣p triǹ h làm viê ̣c với ho ̣ vi điề u khiể n megaAVR theo mô ̣t
phương thức hoàn toàn mới. Và điể m nổ i bâ ̣t nhấ t chính là cửa sổ Serial
để giao tiế p với Arduino qua giao thức UART giúp người lâ ̣p triǹ h thuâ ̣n
tiê ̣n quan sát.

2.2 Hướng dẫn cài đă ̣t Arduino IDE


Để có thể cài đă ̣t và sử du ̣ng Arduino IDE các ba ̣n phải làm theo
hướng dẫn sau:
 Cài đặt Java Runtime Environment (JRE)
 Cài đă ̣t Arduino IDE
 Cài đă ̣t Driver cho Arduino IDE
Bước 1: Cài đặt Java Runtime Environment (JRE)
Vào đường link:
http://www.oracle.com/technetwork/java/javase/downloads/jre7-
downloads-1880261.html tải JRE phù hợp với hệ điều hành bạn đang sử
dụng.

54
Hinh 2.2. Giao diêṇ để tải JRE
Bước 2: Cài đă ̣t Arduino IDE
Vào link sau để tải: https://www.arduino.cc/en/Main/Software/

Hin
̀ h 2.3. Giao diêṇ download Arduino IDE
Nếu ba ̣n cho ̣n Windows installer thì sau khi tải về các ba ̣n cài như
bình thường
Hoă ̣c nếu bạn cho ̣n Windows ZIP file for non admin install thì
sau khi tải về các ba ̣n giải nén file ZIP và dùng bình thường

55
Bước 3: Cài đă ̣t Driver cho Arduino IDE
Các ba ̣n vào thư mu ̣c gố c của Arduino và theo đường dẫn sau:
arduino-1.6.6-windows\arduino-1.6.6\drivers và cho ̣n Driver phù
hơ ̣p với máy tiń h của ba ̣n.

Hin
̀ h 2.4. Cho ̣n Driver cho arduino
Sau khi cài đă ̣t xong các ba ̣n có thể kế t nố i Board Arduino của các
ba ̣n vào máy tính để lâ ̣p trình.
Thiế t lập cơ bản cho Arduino IDE như sau:
ToolsBoardcho ̣n board mà ba ̣n muố n lâ ̣p trình.
ToolsPortcho ̣n cổ ng COM máy tính mà ba ̣n dùng để kế t nố i với
Arduino

56
Chương 3
ĐIỀU KHIỂN THIẾT BỊ QUA TIN NHẮN SMS

3.1 Đặt vấn đề


Vấn đề cơ bản và cốt lõi ở đây là ta dùng một điện thoại có thể soạn
tin nhắn SMS gởi đi lệnh nhằm điều khiển đóng mở các thiết bị điện. Và
chúng ta đã biết thì tin nhắn SMS phải có “địa chỉ” để nó gởi đến. Như vậy
ở thiết bị cuối tối thiểu phải nhận được tin nhắn SMS, có nghĩa phải là một
thuê bao có thể nhận tin nhắn SMS. Thông thường trong điều khiển thì
người ta hạn chế tối đa vấn đề điều khiển “mù” (khái niệm chỉ việc thiết
bị điều khiển gởi lệnh điều khiển đến thiết bị cuối mà không biết được
trạng thái thực thi của thiết bị cuối như thế nào?), nên khi thiết bị cuối đã
nhận được lệnh điều khiển, đã điều khiển thiết bị được theo lệnh thì thiết
bị cuối gởi thông tin phản hồi. Và trong trường hợp này thì người ta dùng
luôn tin nhắn SMS để phản hồi. Thiết bị đầu cuối có thể nhận và gởi tin
nhắn khá đa dạng. Ở phần này, tác giả muốn sử dụng một điện thoại di
động để gởi tin nhắn điều khiển. Hiển nhiển các tin nhắn có thể soạn bằng
tay thông qua các điện thoại thông thường, nhưng trong tài liệu này, tác
giả muốn đề cập đến việc sử dụng các điện thoại thông minh để tiến hành
thao tác gởi tin nhắn điều khiển. Điều đó khác nhau ở chỗ, người dùng sẽ
không phải soạn tin nhắn gởi đi, mà thông qua thao tác nhấn nút điều khiển
trên màn hình cảm ứng thì tin nhắn điều khiển sẽ được phát sinh và gởi đi.
Còn ở thiết bị cuối, tác giả muốn dùng thiết bị có thể nhận và gởi tin nhắn
SMS khá phổ biến trên thị trường là các Module Sim. Cụ thể ở đây dùng
Module Sim 900.
Sơ đồ mô hình hệ thống như sau:
SMS
SIM900 Thiết bị 1
Phần mềm điều khiển
chạy trên thiết bị
Android SMS Thiết bị 2
(Điều khiển và giám Vi xử lý
sát thông qua SMS)
Thiết bị 3

Phần mềm điều khiển Bộ điều khiển thiết bị

Hình 3.1. Hệ thống điều khiển thiết bị qua SMS


57
Theo sơ đồ trên, khi muốn điều khiển thiết bị nào đó thì người dùng
sẽ sử dụng phầm mềm được xây dựng để điều khiển. Ví dụ trên phần mềm
có nút nhấn ON1, OFF1 điều khiển thiết bị 1. Người dùng muốn mở thiết
bị 1 thì nhấn vào nút điều khiển ON1. Khi đó phần mềm trên điện thoại
Android sẽ phát sinh tin nhắn điều khiển với nội dung là cú pháp lệnh mở
thiết bị 1 và gởi đến một số điện thoại được cài đặt trước đó (số điện thoại
này đang được gắn tại Module SIM900 của chúng ta). Khi tin nhắn đến
SIM900 (được gắn sim với số điện thoại cài đặt), vi xử lý sẽ kiểm tra xem
có đúng tin nhắn từ số điện thoại trong danh sách được điều khiển hay
không? Nếu đúng số điện thoại trong danh sách được điều khiển thì nó sẽ
đọc xem nội dung tin nhắn là gì và giải mã lệnh nội dung đó. Trong trường
hợp này, nội dung chính là lệnh mở thiết bị 1, vi xử lý sẽ thực hiện lệnh
mở thiết bị 1. Tùy chọn người dùng khi đó có thể lập trình vi điều khiển
gởi tin nhắn SMS phản hồi để phần mềm trên điện thoại biết được trạng
thái của thiết bị điều khiển đã được thực thi. Và trên phần mềm Android
sẽ có thông báo hiện thị trạng thái thiết bị đó để người dùng quan sát.
Tác giả không đi vào chi tiết một dự án cụ thể, vì cụ thể vấn đề lập
trình và điều khiển khá phức tạp để đáp ứng các yêu cầu thực tế như lưu
trữ lịch sử trạng thái thiết bị, xuất file lưu trữ, tạo cơ sở dữ liệu online,
thống kê, …ở đây tác giả chủ yếu đi vào phần cốt lỗi là gửi và nhận tin
nhắn SMS.

3.2 Phần mềm trên điện thoại android


Mục tiêu ở đây chúng ta sẽ xây dựng một phần mềm điều khiển, tạm
gọi là SMS_CONTROL, ứng dụng này ta sẽ gởi tín hiệu điều khiển đến
bộ điều khiển để đóng mở thiết bị (có thể đơn giản là một led đơn chẳng
hạn). Và trên ứng dụng chúng ta cũng xử lý được việc nhận tin nhắn phản
hồi để biết trạng thái của thiết bị sau khi đã thực thi lệnh điều khiển.
Giao diện ứng dụng ta xây dựng đơn giản như sau:

58
Hình 3.2. Giao diện ứng dụng điều khiển SMS_Control
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 ta hiệu chỉnh như sau:
Ở đây tôi dùng LinearLayout.
<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"

59
tools:context=".MainActivity"
android:id="@+id/LinearLayout"
android:background="@android:color/background_dark"
android:orientation="vertical"
android:weightSum="100">

<TextView android:text="@string/hello_world"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/textView"
android:textColor="#D41714"
android:textSize="22sp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dp"
android:layout_weight="19.37"
android:layout_gravity="center_horizontal"/>

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TURN ON THE LIGHT"
android:id="@+id/btn_on"
android:layout_above="@+id/btn_off"
android:layout_marginBottom="53dp"
android:background="#78ffb3"
android:layout_alignEnd="@+id/textView"
android:layout_alignStart="@+id/textView"
android:textSize="12sp"
android:layout_gravity="center_vertical"
android:layout_weight="12" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="TURN OFF THE LIGHT"
android:id="@+id/btn_off"
android:background="#ff5053"
android:layout_alignEnd="@+id/btn_on"
android:layout_alignStart="@+id/btn_on"
android:textSize="12sp"
android:layout_gravity="center_vertical"
android:layout_weight="12" />

</LinearLayout>

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
60
<resources>
<string name="app_name">SMS_CONTROL</string>
<string name="hello_world">SMS DEVICE
CONTROLLER</string>
<string name="action_settings">Settings</string>
</resources>

Vì ứng dụng của ta cho phép (permission) việc gởi và nhận tin nhắn
SMS 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.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" />

61
</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. Tôi có giải 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 bên dưới tôi sẽ giải thí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;
import android.telephony.gsm.SmsManager;
import android.telephony.SmsMessage;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

//Tạo một đối tượng BroadcastReceiver


BroadcastReceiver receiver=null;

//Khai báo các biến


Button btn_on1;
Button btn_off1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//ẩn thanh tiêu đề của ứng dụng
requestWindowFeature(Window.FEATURE_NO_TITLE);

setContentView(R.layout.activity_main);

//ánh xạ các biến và các UI control

btn_on1 = (Button) findViewById(R.id.btn_on);


btn_off1 = (Button) findViewById(R.id.btn_off);

62
//viết hàm xử lý khi nút btn_on1 được nhấn
btn_on1.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
sendSMS("+84982201xxx", "1234 ON1");
}
});

//viết hàm xử lý nút btn_off1


btn_off1.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
sendSMS("+84982201xxx", "1234 OFF1");
}
});

//phần code xử lý tin nhắn phản hồi


//tạo bộ lắng nghe để nhận tin nhắn đến

IntentFilter filter_sms=new
IntentFilter("android.provider.Telephony.SMS_RECEIVED");

receiver=new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent
intent) {
//hàm này sẽ thực thi khi tin nhắn được gởi đến

//khai báo chuỗi có nội dung "pdus" để nhận tin nhắn


String sms_extra="pdus";
//khai báo đóng gói bundle để nhận gói dữ liệu
Bundle bundle=intent.getExtras();
//đóng gói bundle sẽ trả về tập các tin nhắn gửi đến cùng
//một lúc
Object[] smsArr=
(Object[])bundle.get(sms_extra);

String body="";
String address="";
//Dùng vòng lặp để đọc từng tin nhắn
for(int i=0;i<smsArr.length;i++)
{
SmsMessage smsMsg =
SmsMessage.createFromPdu((byte[]) smsArr[i]);
//lấy nội dung tin nhan
body = smsMsg.getMessageBody();
//lấy địa chỉ (so dien thoai) của thiết bị gởi tin nhắn
63
address =
smsMsg.getDisplayOriginatingAddress();
}

if(address.equals("+84982201xxx"))
{
if(body.equals("1234 ON1"))
{
Toast.makeText(context,"The light
is on",Toast.LENGTH_LONG).show();
}
else if(body.equals("1234 OFF1"))
{
Toast.makeText(context,"The light
is off", Toast.LENGTH_LONG).show();
}

}
}
};

//đăng ký bộ lắng nghe vào hệ thống


registerReceiver(receiver,filter_sms);
}

//Hàm xử lý gởi tin nhắn

public void sendSMS(String phonenumber, String


message){

SmsManager sms= SmsManager.getDefault();

sms.sendTextMessage(phonenumber,null,message,null,null);
}

//huy bo dang ky lang nghe nhan tin nhan khi tat ung dung
protected void onDestroy(){
super.onDestroy();
unregisterReceiver(receiver);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the menu; this adds items to the action bar if


it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;

64
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action
bar will
// automatically handle clicks on the Home/Up
button, so long
// as you specify a parent activity in
AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if (id == R.id.action_settings) {
return true;
}

return super.onOptionsItemSelected(item);
}
}

Chương trình trên hoạt động như thế nào?


Đầu tiên tôi xin nói về việc xử lý hai nút nhấn. Đơn giản là việc điều
khiển thiết bị bằng tin nhắn thì ta phải gởi tin nhắn đi đến bộ điều khiển và
tin nhắn chứa mã điều khiển. Ở bộ điều khiển sẽ có một sim900 có gắn
sim số để nhận tin nhắn điều khiển. Ở đây tôi giả sử như bộ điều khiển
đang gắn sim số +84982201xxx (số tôi chọn ngẫu nhiên, khi các bạn thực
hiện thực dùng đúng số thuê bao bạn đã gắn trên modul sim900 của bộ
điều khiển). Và lệnh điều khiển để mở sáng Led là “1234 ON1”, lệnh điều
khiển để tắt Led là “1234 OFF1”. Việc gởi tin nhắn SMS tôi viết một hàm
sendSMS (số thuê bao, nội dung) để thực hiện. Công việc khá đơn giản vì
ta được hỗ trợ các hàm có sẵn và xây dựng lên thôi.
public void sendSMS(String phonenumber, String message){
SmsManager sms= SmsManager.getDefault();

sms.sendTextMessage(phonenumber,null,message,null,null);
}
Vấn đề tiếp theo là tôi muốn nhận được các thông tin phản hồi, chẳng
hạn khi ta gởi tin nhắn để tắt Led, bộ điều khiển nhận được tin nhắn lệnh
sẽ thực thi việc tắt Led và sau đó phản hồi thông tin lại điện thoại cho biết
là Led đã được tắt. Ứng dụng của chúng ta cần cập nhật trạng thái này, để
người dùng biết bộ điều khiển đã được thực thi xong lệnh. Hiển nhiên đối
với ứng dụng phức tạp bạn phải sao lưu trạng thái này và luôn hiển thị trên
65
Activity điều khiển của bạn. Ở đây tôi chỉ muốn mọi việc đơn giản, khi
thiết bị đã tắt hoặc mở Led nó sẽ gởi tin nhắn phản hồi về số điện thoại của
ta. Và ứng dụng của ta trong trường hợp này khi nhận tin nhắn của đúng
số thuê bao đã gắn trên module sim nó sẽ kiểm tra nội dung. Nếu nội dung
trả về là “1234 ON1” thì nó sẽ xuất ra thông báo “The light is on”, nội
dung là “1234 OFF1” thì nội dung là “The light is off”.
Để lấy nội dung tin nhắn ta dùng cú pháp:
body = smsMsg.getMessageBody();
Để láy số thuê bao ta dùng cú pháp:
address = smsMsg.getDisplayOriginatingAddress();
Tin nhắn phản hồi phải xuất phát từ số thuê bao mà ta đã thiết lập
trên Module sim gắn với bộ điều khiển, cho nên tôi kiểm tra xem đúng số
thuê bao tôi mới kiểm tra nội dung tin nhắn. Lệnh bên dưới kiểm kiểm số
thuê bao của tin nhắn.
if(address.equals("+84982201xxx"))
{…
}
Nếu đúng số thuê bao của bộ điều khiển gởi phản hồi ta tiếp tục kiểm
tra nội dung tin nhắn. Lệnh bên dưới sẽ kiểm tra nếu nội dung tin nhắn là
“1234 ON1” thì sẽ xuất ra dòng “The light is on” trên màn hình vài giây
if (body.equals("1234 ON1"))
{
Toast.makeText(context,"The light is
on",Toast.LENGTH_LONG).show();
}
Ở đây ta đăng ký lắng nghe tin nhắn ngay trong Activity của ứng
dụng, nên khi ứng dụng đang chạy (có thể chạy ngầm) thì nó mới xử lý tin
nhắn đến. Nếu tắt ứng dụng nó sẽ không xử lý tin nhắn đến. Hàm đăng ký
lắng nghe như sau:
registerReceiver(receiver,filter_sms);
Tất nhiên nếu bạn muốn xử lý bất cứ khi nào tin nhắn đến, không
quan tâm đến ứng dụng có đang chạy hay không thì bạn vẫn có thể làm
được. Bạn có thể tham khảo phần xử lý tin nhắn SMS trong sách “Lập trình
Android cơ bản” NXB Đại học Quốc gia của cùng tác giả.

66
3.3 Bộ điều khiển
Chức năng của bộ điều khiển là giải mã lệnh điều khiển thông qua
tin nhắn nhận từ thuê bao của Module Sim900. Dựa vào lệnh điều khiển vi
xử lý sẽ điều khiển các thiết bị theo yêu cầu. Sau khi điều khiển xong nó
sẽ gởi tin nhắn cập nhật trạng thái cho phần mềm trên điện thoại biết trạng
thái của thiết bị. Ta phải lập trình chỉ cho những số thuê bao trong danh
sách đã thiết lập mới có thể đưa ra lệnh điều khiển hoặc có mã điều khiển
đặc biệt nào đó nhằm mục đích không để bất kỳ thuê bao nào cũng có thể
nhắn tin điều khiển được.
Phần này bạn phải tìm hiểu về Module SIM900, có kiến thức về lãnh
vực vi điều khiển để chọn một vi điều khiển thích hợp kết nối phần cứng
và lập trình, bạn cũng cần những kiến thức cơ bản về điện tử để xây dựng
mạch nguồn, mạch công suất điều khiển. Và tất nhiên, tôi không thể trình
bày tất cả những điều đó ở đây. Tôi chỉ gợi mở cho bạn hướng đi, vì phần
chính của quyển sách chúng ta là ở trên ứng dụng Android.
SIM900 là module GSM/GPRS của SIMCom được thiết kế dưới
dạng module chipset, nhỏ gọn, giá thành thấp, hoạt động ổn định và phù
hợp cho nhiều mục đích sử dụng. Module SIM900 có các tính năng cơ bản
của một chiếc điện thoại như gọi điện thoại, nhắn tin, truy cập GPRS,..
Module SIM900 có rất nhiều biến thể khác nhau. Nếu bạn muốn mua
một module nhỏ gọn, bạn về tự thiết kế mọi thứ để tránh các thiết kế “thừa”
thì bạn chọn mua module SIM900 “nguyên thủy”, còn nếu bạn muốn tiện
lợi, nhanh chóng phát triển ứng dụng của mình thì có rất nhiều kit tích hợp
modul SIM900 để cho bạn chọn lựa. Như hình bên dưới, tôi liệt kê một vài
loại, và các loại này đều có thể sử dụng tập lệnh AT như modul sim900
gốc.
Hình bên dưới là SIM900 “gốc” của nhà sản xuất SIMCOM, GPRS
lớp 10, hỗ trợ 4 dãy tần số.

67
Hình 3.3. SIM900
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..

Hình 3.4. Board TDGGSM_900


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.

68
Hình 3.5. Board GPRS Shield-EFCom

3.4 Kế t nố i phầ n cứng và chương trin


̀ h Arduino
Xin nhắc lại ở đây tác giả muốn phát triển nhanh ứng dụng, tiết kiệm
thời gian và kinh phí nên tất cả các phần mạch điều khiển tôi đều chọn
board Ardunio Uno R3. Bạn hoàn toàn có thể dựa vào các giải thuật sau
khi đọc hiểu chương trình để xây dựng cho mình một phần cứng riêng,
chương trình riêng phù hợp với hệ thống của bạn. Hiện tại vi điều khiển
có rất nhiều họ khác nhau như 8051, PIC, AVR, MSP430,… mỗi họ có
hàng trăm con vi điều khiển cùng với rất nhiều trình biên dịch khác nhau.
Nên mong muốn của quyển sách là muốn dùng Ardunio để xây dựng ứng
dụng nhằm mục tiêu mọi đọc giả đã từng lập trình vi điều khiển có thể đọc
và hiểu được thuật toán xử lý cơ bản. Vì ngôn ngữ lập trình các board
Ardunio khá dễ hiểu cho các bạn đã từ lập trình vi xử lý.
Trở lại phần board mạch điều khiển của chúng ta. Chúng ta cần
module Sim 900 để nhận và phản hồi tin nhắn. Ở đây tôi chọn Module
SIM900A khá phổ biến trên thị trường. Nó giao tiếp với vi điều khiển
thông qua công truyền thông nối tiếp UART chuẩn, nên rất thuận tiện trong
việc kết nối. Ta kết nối chân Tx của SIM900A vào chân Rx (chân số 0)
của board Ardunio UNO. Chân Rx của SIM900A vào chân Tx (chân số 1)
của board Ardunio UNIO (hình 3.6). Vi điều khiển có hỗ trợ phần cứng

69
truyền dữ liệu UART chuẩn qua chân 0,1 nên sử dụng hai chân này để giao
tiếp với module SIM900 rất hợp lý, code sẽ tối ưu hơn. Tuy nhiên ở đây
tôi muốn làm một cách khác, vì cách truyền thống sử dụng hai chân mặc
định TxD, RxD của board Arduino sẽ được dùng cho các chương sau.

Hin
̀ h 3.6. Sơ đồ kế t nố i phầ n cứng
Ở đây tôi dùng hai chân khác (chân số 2,3) để kết nối giao tiếp với
SIM900, do phần cứng vi điều khiển không hỗ trợ sẵn nên ta phải dùng
phần mềm để tạo giao thức truyền nhận dữ liệu. Cách này sẽ làm code dài
hơn, tuy nhiên ta lại được nhiều điều hay khác. Thứ nhất là ta có thể kết
nối giao tiếp ở các chân khác chân mặc định, điều này cho phép ta mở rộng
module kết nối dễ dàng trong trường hợp ta cần nhiều hơn một cổng
UART. Thứ hai ta có thể dùng phần mềm Serial để quan sát trạng thái
truyền nhận dữ liệu từ máy tính dễ dàng hơn.
Ta kết nối chân TxD của SIM900A vào chân số 2 của board Ardunio
UNO. Chân RxD của SIM900A vào chân số 3 của board Ardunio UNIO
(hình 3.7). Ta có phần cứng kết nối ta sẽ sử dụng như sau.

70
Hình 3.7 Giao tiếp SIM900 qua hai chân khác chân Tx, Rx mặc định
Lưu ý là chúng ta hoàn toàn có thể làm theo phần cứng 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 ta trong mạch khá quen thuộc và đơn giản
là 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 led (nếu có, giá trị này tùy thuộc
vào nguồn cung cấp, tùy màu sắc led và độ sáng mong muốn).
Bên dưới là chương trình cho Ardunio UNO R3 nhận tin nhắn điều
khiển, giải mã lệnh điều khiển led và gởi tin nhắn phản hồi trạng thái điều
khiển. Chú ý các dòng chú thích để hiểu rõ hoạt động của chương trình.
Lưu ý giả sử số điện thoại của điện thoại sử dụng phần mềm điều
khiển là 09x1234567 (10 số) thì ta ghi lại theo mã quốc gia của Việt Nam
là +849x1234567 (tổng cộng 12 kí tự). Các bạn khi viết chương trình nên
chỉnh lại phần này cho phù hợp.
Và các bạn lưu ý phần lập trình có viết thêm các đoạn code giao tiếp
máy tính, nhằm quan sát hoạt động của Ardunio một cách dễ dàng trên
màn hình máy tính. Các đoạn code này không liên quan đến hoạt động của
hệ thống, bạn có thể bỏ qua nếu mong muốn chương trình sáng hơn, gọn
hơn.

71
#include <SoftwareSerial.h>
SoftwareSerial SIM900(2, 3); // Tx nố
i chân 2, Rx nố
i
chân 3
char tempchar;
const unsigned int buff_size = 160;
char buff1[buff_size];
int k = 0, i = 0, index = 0, j = 0;
char sdt[13];
char number[13] = “+849x1234567”; // ghi và
o sốđiệ
n
//thoa
̣i của điện thoại
char on[] = “1234 ON1”;
char off[] = “1234 OFF1”;
boolean ktsdt = true;
void setup()
{
Serial.begin(9600); //thiết lập tốc độ baud giao tiếp
máy tính
SIM900.begin(9600); //thiết lập tốc độ baud giao tiếp
SIM900
pinMode(5, OUTPUT); //cấu hình chân số 5 là ngõ ra, kết
nối led
digitalWrite(5, LOW); //cho led tắt
SIM900.println(“AT+CMGD=1,4\r\n”); //xó
a tấ
t cảtin
nhắ
n trong bộnhớ
delay(20);
SIM900.println(“ATE0\r\n”); //bỏecho củ
a module sim
delay(1000);
SIM900.println(“AT+CNMI=2,2,0,0,0\r\n”);
//đặ
t chếđộhiệ
n nộ
i dung tin nhắ
n
delay(1000);
SIM900.print(“AT+CMGF=1\r\n”);
//cho phé
p module sim nhậ
n vàgử
i tin
delay(500);
}
void loop()

72
{
receive_uart();
delay(200);
xu_ly_sms();
xoa_buffer();
}

void sendSMS(String message)


{
SIM900.print(“AT+CSCS=\”GSM\”\r\n”);
//lệ
nh AT đểgử
i tin
SIM900_response(500);
SIM900.print(“AT+CMGF=1\r\n”);
//cho phé
p gử
i vànhậ
n tin nhắ
n
SIM900_response(500);
SIM900.print(“AT+CMGS=\”+849x1234567\”\r”);
// gử
i tin nhắ
n đế
n sốdt
SIM900_response(500);
SIM900.print(message);
//gử
i nộ
i dung tin nhắ
n
SIM900_response(500);
SIM900.print((char)26);
//kế
t thú
c lệ
nh gử
i
SIM900_response(5000);
}

//dù
ng timer đelay kết hợp đọc SIM900 và giao tiếp máy
//tính
void SIM900_response(int time)
{
unsigned long tnow = millis();
while ((millis() – tnow) < time) {
if (SIM900.available()) {

73
tempchar = (char)SIM900.read();
if (tempchar == ‘\r’) {
Serial.print(“/R/”);
}
else if (tempchar == ‘\n’) {
Serial.println(“/N/”);
}
else Serial.print(tempchar);
}
}
}

void receive_uart()
//hà
m nhậ
n tin nhắ
n vàlưu và
o mả
ng buff1
{
k=0;
while ((SIM900.available() == 0)) {}
while (SIM900.available() > 0)
{
buff1[k++] = SIM900.read();
}
}

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;
}
}

74
//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");
}
}
Serial.println(buff1);
Serial.println(ktsdt);
}
void xoa_buffer() //xó
a bộđệ
m
{
for (i = 0; i <= k; i++) {
buff1[i] = NULL;

75
}
for (i = 0; i < 13; i++) {
sdt[i] = NULL;
}
k = 0; j = 0; i = 0; index = 0;}

Để 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 ý là 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ì ta
không dùng các chân Tx, Rx chuẩn của Arduino, và khi dùng hàm con thì
ta 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)
{
buff1[k++] = SIM900.read();
}
}

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 thoa ̣i bằ ng cách tìm 12 ký tự bắ t đầ u bằ ng +84.
Tim̀ 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 thoa ̣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.
76
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) {

77
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. Hiển nhiên 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
SIM900_response(500);
SIM900.print(“AT+CMGF=1\r\n”);
//cho phé
p gử
i vànhậ
n tin nhắ
n
SIM900_response(500);
SIM900.print(“AT+CMGS=\”+849x1234567\”\r”);
// gử
i tin nhắ
n đế
n sốdt
SIM900_response(500);
SIM900.print(message);
//gử
i nộ
i dung tin nhắ
n
SIM900_response(500);
SIM900.print((char)26);
//kế
t thú
c lệ
nh gử
i
SIM900_response(5000);
}

Như vâ ̣y tương ứng với ứng du ̣ng Android trên, khi gửi tin nhắ n điề u khiể n
tắ t mở thiế t bi ̣ thì Arduino sẽ thực hiê ̣n bâ ̣t tắ t đèn và trả lời la ̣i bằ ng tin
nhắ n tương ứng để thông báo tra ̣ng thái.

78
Chương 4
ĐIỀU KHIỂN VÀ GIÁM SÁT THIẾT BỊ QUA
BLUETOOTH

4.1 Đă ̣t vấ n đề


Với mu ̣c đích điề u khiể n và giám sát thiế t bi ̣ trong pha ̣m vi gầ n, ví
du ̣ như Smart Home thì viê ̣c sử du ̣ng SMS sẽ rấ t tố n chi phí và không phù
hơ ̣p. Thay vào đó môi trường Bluetooth sẽ rấ t thích hơ ̣p để điề u khiể n vì
giao thức đơn giản và tố i ưu hóa chi phi.́ Chúng ta đã biết hầu các
smartphone điều tích hợp Bluetooth, giao thức Bluetooth liên tục được cải
tiến qua các thế hệ để tối ưu việc truyền nhận dữ liệu, bảo mật, hạn chế
nhiễu.
Bluetooth là công nghệ không dây cho phép các thiết bị điện, điện tử
giao tiếp với nhau trong khoảng cách ngắn (thường từ 10m – 100m), bằng
sóng vô tuyến qua băng tần chung ISM (Industrial, Scientific, Medical)
trong dãy tần số 2.4 – 2.48 GHz và có khả năng truyền tải dữ liệu nhanh,
bảo mật và ít nhiễu. Đây là dãy băng tần không cần đăng ký được dành
riêng cho các thiết bị không dây trong công nghiệp, khoa học và y tế.
Bluetooth được thiết kế nhằm mục đích thay thế dây cable giữa máy
tính và các thiết bị truyền thông cá nhân, kết nối vô tuyến giữa các thiết bị
điện tử lại với nhau một cách thuận lợi với giá thành rẻ.
Khi được kích hoạt, Bluetooth có thể tự động định vị những thiết bị
khác có chung công nghệ trong vùng xung quanh và bắt đầu kết nối với
chúng. Nó được định hướng sử dụng cho truyền dữ liệu lẫn giọng nói.
Công nghệ không dây Bluetooth là một tiêu chuẩn trong thực tế,
dùng cho các thiết bị cỡ nhỏ, chi phí thấp, liên kết giữa PC và điện thoại
di động hoặc giữa các máy tính với nhau…
Trở lại vấn đề, chúng ta cần 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ị.

79
Tương tự vấn đề điều khiển thiết bị ta hoàn toàn có thể điều khiển,
cập nhật các thông số của một số cảm biến (chẳng hạn như nhiệt độ, độ
ẩm, nồng độ khí CO2…) cho hệ thống.
Bên dưới là mô hình của hệ thống điều khiển, giám sát thiết bị đơn
giản.

Bluetooth

Phần mềm điều khiển HC05/06 Thiết bị 1


chạy trên thiết bị
Android
Bluetooth Cảm biế n
(Điều khiển và giám
sát thông qua Vi xử lý
Bluetooth)

Phần mềm điều khiển Bộ điều khiển giám sát thiết bị

Hin
̀ h 4.1. Sơ đồ điề u khiể n thiế t bi bằ
̣ ng Bluetooth
Theo như sơ đồ trên thì khi ta gửi mô ̣t lê ̣nh điề u khiể n từ ứng du ̣ng
Android sang bô ̣ điề u khiể n thiế t bi,̣ ví du ̣ ứng du ̣ng có các nút điề u khiể n
ON, OFF khi nhấ n vào nút nhấ n ON thì ứng du ̣ng gửi ký tự “1” vào bô ̣
điề u khiể n, khi bô ̣ điề u khiể n nhâ ̣n đươ ̣c ký tự “1” sẽ bâ ̣t thiế t bi ̣1 và gửi
la ̣i một thông báo thông qua Bluetooth để ứng du ̣ng nhâ ̣n biế t đã gửi thành
công.

4.2 Phầ n mề m trên điêṇ thoa ̣i android


Mu ̣c đích của ứng du ̣ng này chỉ mô phỏng đơn giản viê ̣c điề u khiể n
thiế t bi,̣ khi nhấ n vào ImageView hiǹ h bóng đèn thì sẽ gửi một ký tự “lệnh”
đến bô ̣ điề u khiể n thông qua Bluetooth, ở bô ̣ điề u khiể n khi đó sẽ xử lý
bâ ̣t/ tắ t đèn. Khi đèn đang tắ t nế u nhấn vào biểu tượng đèn tắt thì ứng du ̣ng
gửi ký tự “1” thông qua Bluetooth đến bộ điều khiển. Bộ điều khiển nhận
lệnh sẽ điều khiển đèn sáng và sau đó bô ̣ điề u khiể n gửi la ̣i một chuỗi với
nô ̣i dung là “ON”. Ứng du ̣ng nhâ ̣n la ̣i phản hồ i và đổ i biể u tươ ̣ng
ImageView thành đèn sáng để ta biế t viê ̣c gửi lê ̣nh điề u khiể n thành công,
và ngươ ̣c la ̣i khi đèn đang sáng, nhấ n vào ImageView thì ứng du ̣ng sẽ gửi
ký tự “0” đến bô ̣ điề u khiể n. Bô ̣ điề u khiể n sẽ cho đèn tắ t và gửi phản hồ i

80
“OFF”. Ứng du ̣ng nhâ ̣n phản hồ i và đổ i biể u tươ ̣ng ImageView thành bóng
đèn đang tắ t.

Hin
̀ h 4.2. Giao diêṇ điều khiể n của ứng du ̣ng
Khi khởi đô ̣ng ứng du ̣ng, giao diê ̣n sẽ như trên, ấ n vào Scan để quét
các thiế t bi Bluetooth
̣ xung quanh. Danh sách các thiết bị được hiển thị ra,
sau đó nhấ n vào thiế t bi muố
̣ n ghép nố i, đánh mâ ̣t khẩ u để kế t nố i với thiế t
bi ̣đó.
Khi kế t nố i hoàn tấ t viê ̣c điề u khiể n đơn giản chỉ là cha ̣m vào
ImageView bóng đèn trên màn hiǹ h. Ở đây 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 tắt ta chạm
vào thì nó sẽ gởi lệnh đến bộ điều khiển để điều khiển bật đèn. Khi đèn đã
bật và có thông tin phản hồi đèn đã bật thì ImageView sẽ load một hình
đèn đã sáng. Và ngược lại, khi ImageView đang hiển thị đèn sáng ta chạm
vào thì nó sẽ gởi lệnh điều khiển đèn tắt.

81
Hin
̀ h 4.3. Giao diêṇ khi đèn được bật
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 tra ̣ng thái của thiế t bi.̣ ListView để hiê ̣n
các thiế t bi ̣ Bluetooth mà ứng du ̣ng tim
̀ đươ ̣c. Button Scan để dò tim ̀
Bluetooth của các thiế t bi ̣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"

82
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"
android:id="@+id/imageView_light"
android:layout_gravity="center_horizontal"
android:src="@drawable/light_off"
android:contentDescription="@string/img_light" />

<ListView
android:layout_width="match_parent"
android:layout_height="202dp"
android:id="@+id/listView_device"
android:layout_gravity="center_horizontal" />

<Button
android:layout_width="150dp"
android:layout_height="50dp"
android:text="@string/btn_scan"
android:id="@+id/button_scan"
android:layout_gravity="center_horizontal" />
</LinearLayout>

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 của ta 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

83
<?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 đó ta vào file MainActivity.java để viết code cho ứng dụng


thực hiện yêu cầu. Tôi 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 bên dưới tôi sẽ giải thích cụ thể
hơn:
package com.example.ngb.bluetooth_control;

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.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;

84
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
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 AppCompatActivity {


ImageView img_light;
ListView list_device;
Button btn_scan;
BluetoothAdapter myBluetoothAdapter;
BluetoothSocket btSocket;
private OutputStream outStream = null;
private InputStream inStream= null;
private static final UUID MY_UUID =
UUID.fromString("00001101-0000-1000-8000-
00805F9B34FB");
private ArrayAdapter<String> BTArrayAdapter;
ArrayList<String> Adress;
ArrayList <String> ID;
String BtAddress= null;
private static final String TAG = "HC-05";
Handler handler = new Handler();
byte delimiter = 10;
boolean stopWorker = false;
int readBufferPosition = 0;
byte[] readBuffer = new byte[1024];
private boolean chophepgui=false;
boolean light_status=false;

@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

//tạo liên kết giữa các biến java và giao diện


img_light=(ImageView)findViewById(R.id.imageView_light);
list_device=(ListView)findViewById(R.id.listView_device);
btn_scan=(Button)findViewById(R.id.button_scan);

85
//tạo hai arraylist là ID và Adress
ID=new ArrayList<String>();
Adress=new ArrayList<String>();
BTArrayAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, ID);
list_device.setAdapter(BTArrayAdapter);

// Khởi tạo các công cụ cho Bluetooth


myBluetoothAdapter =
BluetoothAdapter.getDefaultAdapter();
IntentFilter filter1 = new
IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
this.registerReceiver(bReceiver, filter1);
on();

// tạo trình lắng nghe cho nút scan


btn_scan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
find();
}
});
//tạo trình lắng nghe cho listview
list_device.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?>
parent, View view, int position, long id) {
BtAddress = Adress.get(position);
Toast.makeText(MainActivity.this, "đang
kết nối....", Toast.LENGTH_LONG).show();
Connect();
}
});

//tạo trình lắng nghe cho ImageView


img_light.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (chophepgui)
{

if (light_status) { writeData("0");}
else writeData("1");
beginListenForData();
}
}

86
});
}

//Ham bat ket noi Bluetooth


public void on(){
if (!myBluetoothAdapter.isEnabled()) {
Intent turnOnIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(turnOnIntent, 1);

Toast.makeText(getApplicationContext(),
"Bluetooth turned on",
Toast.LENGTH_LONG).show();
}
else{

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

87
myBluetoothAdapter.cancelDiscovery();
}
else {
BTArrayAdapter.clear();
myBluetoothAdapter.startDiscovery();

registerReceiver(bReceiver, new
IntentFilter(BluetoothDevice.ACTION_FOUND));
}
}

protected void onDestroy() {


// TODO Auto-generated method stub
super.onDestroy();
if (chophepgui) {
unregisterReceiver(bReceiver);
}
myBluetoothAdapter.disable();
}

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");
}

private void writeData(String data) {


try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.d(TAG, "Bug BEFORE Sending stuff", e);
}

88
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";
}
}

public void beginListenForData() {


try {
inStream = btSocket.getInputStream();
} catch (IOException e) {
}

Thread workerThread = new Thread(new Runnable()


{
public void run()
{

while(!Thread.currentThread().isInterrupted() &&
!stopWorker)
{
try
{
int bytesAvailable =
inStream.available();
if(bytesAvailable > 0)
{
byte[] packetBytes = new
byte[bytesAvailable];
inStream.read(packetBytes);
for(int i=0;i<bytesAvailable;i++)
{
byte b = packetBytes[i];
if(b == delimiter)
{
byte[] encodedBytes = new
byte[readBufferPosition];
System.arraycopy(readBuffer,
0, encodedBytes, 0, encodedBytes.length);
final String data = new
String(encodedBytes, "US-ASCII");
readBufferPosition = 0;
handler.post(new Runnable()

89
{

//ham run
public void run() {
if (data.substring(0,2).equals("ON"))
{
img_light.setImageResource(R.drawable.light_on);
light_status=true;
}

if (data.substring(0,3).equals("OFF"))
{
img_light.setImageResource(R.drawable.light_off);
light_status=false;
}
}
});
}
else
{
readBuffer[readBufferPosition++] = b;
}
}
}
}
catch (IOException ex)
{
stopWorker = true;
}
}
}
});

workerThread.start();
}
}

Chương triǹ h trên hoa ̣t đô ̣ng như thế nào?


Đầ u tiên tôi xin nói về viêc̣ sử du ̣ng gói Bluetooth API trong Android
Studio như thế nào:

90
Để tạo kết nối bluetooth chúng ta sử dụng gói android.bluetooth có
sẵn. Android.bluetooth: Cung cấp các lớp quản lý chức năng Bluetooth,
chẳng hạn như quét các thiết bị lân cận đang hoạt động, kết nối với các
thiết bị, và quản lý truyền dữ liệu giữa các thiết bị.
Gói android.bluetooth gồm các lớp nào?
 BluetoothAdapter: cho phép bạn thực hiện các nhiệm vụ
Bluetooth cơ bản, chẳng hạn như bắt đầu phát hiện ra thiết bị,
truy vấn một danh sách các kho ngoại quan (ghép nối) các thiết
bị, tạo một BluetoothDevice bằng cách sử dụng một địa chỉ
MAC được biết đến, và tạo ra một BluetoothServerSocket để
lắng nghe yêu cầu kết nối từ các thiết bị khác.
 BluetoothDevice: cho phép bạn tạo một kết nối với các thiết bị,
truy vấn thông tin về nó, chẳng hạn như tên, địa chỉ..
Khi ứng du ̣ng khởi đô ̣ng, ta yêu cầ u kích hoa ̣t tính năng Bluetooth
của thiế t bi ̣bằ ng hàm sau:
public void on(){
if (!myBluetoothAdapter.isEnabled()) {
Intent turnOnIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(turnOnIntent, 1);

Toast.makeText(getApplicationContext(),
"Bluetooth turned on",
Toast.LENGTH_LONG).show();
}
else{

Toast.makeText(getApplicationContext(),"Bluetooth is
already on",
Toast.LENGTH_LONG).show();
}
}

Nế u thiế t bi ̣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 bi ̣đã 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 bi ̣đang hoa ̣t đô ̣ng:
91
btn_scan.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
find();
}
});

Ta ̣o mô ̣t lắ ng nghe cho nút Scan, khi nhấ n vào thì go ̣i hàm find();
Đây là hàm dùng để quét các thiế t bi ̣đang hoa ̣t đô ̣ng trong pha ̣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);
}

Tiế p theo là lấ y tên của thiế t bi ̣lưu vào Arraylist ID, lấ y điạ chỉ của
thiế t bi ̣lưu vào một ArrayList Adress.
final BroadcastReceiver bReceiver = new BroadcastReceiver()
{
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
// When discovery finds a device
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
// Get the BluetoothDevice object from the Intent
BluetoothDevice device =
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
// add the name and the MAC address of the object to
the arrayAdapter
BTArrayAdapter.add(device.getName());
BTArrayAdapter.notifyDataSetChanged();

92
Adress.add(device.getAddress());

}
}
};

Dùng Array adapter để hiể n thi ̣tên của các thiế t bi ̣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 bi ̣quét đươ ̣c thì kế t nố i
với thiế t bi ̣đó 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 go ̣i 1 hàm Connect để kế t nố i tới thiế t bi ̣
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");
}
}

93
Vâ ̣y là hoàn thành các bước cơ bản để kế t nố i với mô ̣t thiế t bi ̣
Bluetooth quét đươ ̣c
Khi muố n điề u khiể n thiế t bi ̣chỉ cầ n cha ̣m vào ImageView thì ứng
du ̣ng sẽ gửi lê ̣nh điề u khiể n nhờ vào 1 lắ ng nghe đã đươ ̣c đăng ký
img_light.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(light_status) { writeData("0");}

else writeData("1");

beginListenForData();
}
});

Trong lắ ng nghe này tôi go ̣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 da ̣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 la ̣i nếu dữ liệu trả về là “OFF” thì ta thay đổi

94
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 ba ̣n có thể lấ y
để làm tài nguyên cho những ứng du ̣ng tương tự.

4.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 du ̣ng
sau khi điề u khiể n thiế t bi xong
̣ sẽ gửi la ̣i phản hồ i về điê ̣n thoa ̣i, tấ t cả đề u
thông qua Bluetooth,
Đề tài này tôi cho ̣n Module Bluetooth HC05/HC06 là thiế t bi để
̣ giao
tiế p Bluetooth giữa điê ̣n thoa ̣i và bô ̣ điề u khiể n

Hin
̀ h 4.4. Module Bluetooth HC05/06
Đây là module Bluetooth phổ biế n và thông du ̣ng hiê ̣n nay, với giá
thành rẻ và hỗ trơ ̣ nhiề u tố c đô ̣ truyề n khác nhau theo chuẩ n UART
Module bluetooth HC-06 ra chân hoàn chỉnh giúp dễ dàng kết nối để
thực hiện các thí nghiệm, module được thiết kế để cho thể hoạt động từ
mức điện áp 3.3V đến 5V. Khi kết nối với máy tính, HC-06 được sử dụng
như một cổng COM ảo, việc truyền nhận với COM ảo sẽ giống như truyền
nhận dữ liệu trực tiếp với UART trên module. Lưu ý là khi thay đổi
Baudrate cho COM ảo không làm thay đổi baudrate của UART, baudrate
UART chỉ có thể thay đổi bằng lệnh AT trên module. Có hai loại module
Bluetooth, loại Master và Slave. Module HC-06 ta sử dụng ở đây được
setup mặc định là Slave. Vì vậy nó chỉ có thể giao tiếp với các thiết bị
bluetooth ở dạng master như Smart phone, HC-06 master,... hai module
bluetooth được thiết lập là Slave không thể giao tiếp với nhau.

95
 Đặc điểm kỹ thuật
- Sử dụng CSR mainstream bluetooth chip, bluetooth V2.0 protocol
standard;
- Sử dụng băng tần ISM: 2.4GHz – 2.48GHz;
- Mức năng lượng được sử cho phép: class2;
- Điện áp làm việc của module từ 2.7 – 3.3V;
- Điện áp hoạt động của UART từ 3.3 – 5V;
- Nhiệt độ lưu trữ: -40oC – 85oC;
- Dòng điện khi hoạt động: khi pairing là 30mA, sau khi pairing
hoạt động truyền nhận bình thường 8mA;
- Tốc độ Baudrate UART cho phép: 1200, 2400, 4800, 19200,
38400, 57600, 115200, 230400, 460800, 921600, 1382400… ;
- Kích thước của module: 28mm x 15mm x 2.35mm;
- Thiết lập mặc định của module là: tốc độ baud 9600, databits là 8,
stopbit là 1, parity là N, pairing code là 1234.
Như vậy ở phần mạch điều khiển ta có kết nối tương tự như phần
mạch điều khiển thiết bị bằng tin nhắn SMS. Vị trí Module SIM được thay
thế bằng HC-05/06. Bởi vì HC-05 và Module SIM900 đều giao tiếp với vi
điều khiển qua cổng nối tiếp UART chuẩn nên ta thấy có sự tương đồng.
Phần đối tượng điều khiển ta vẫn dùng một led đơn kết nối tại ngõ ra chân
số 5 của board Ardunio Uno R3. Chú ý gắn nối tiếp thêm điện trở hạn dòng
cho led (giá trị từ 100-330 ohm, tùy thuộc vào led và độ sáng mong muốn).
Khác với chương giao tiếp với SIM900, Module Bluetooth trong bài
này tôi kết nối trực tiếp đên các chân Tx, Rx chuẩn có sẵn của Ardunio.
Điều này sẽ tối ưu code cho vi điều khiển hơn vì ta không cần dùng phần
mềm để tạo giao thức nữa. Vì giao thức đã được hỗ trợ bở phần cứng.
Ta có sơ đồ kết nối như bên dưới:

96
Hin
̀ h 4.5. Sơ đồ kế t nố i Ardunio Uno R3 với HC-05
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 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. 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á qua các hàm Serial. Nhắc lại việc
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, 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ác bạn nên nắm các hàm điều khiển cơ bản để phục vụ cho
các chương tiếp theo.
Nhiều bạn không chuyên về lãnh vực điện tử có thắc mắc, tại sao tôi
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 220 VAC, động cơ, quạt…Tôi xin nhắc lại việc chọn led đơn để đơn
97
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 bạn mua về thì kết nối thêm led đơn
là điều cực kỳ gọn nhẹ. Việc giao tiếp với các thiết bị điện 220VAC,
24VDC,…tất cả chỉ khác nhau ở phần công suất, với các sinh viên chuyên
ngày điện tử thì việc này khá dễ dàng. Còn với các bạn không phải 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,.. Anh “Google” hân hạnh tài trợ các bạn những kiến
thức rất bổ ích từ cộng đồng, và các bạn cứ yên tâm rằng: việc đó là rất
đơn giản. Như vậy sẽ không còn phải bận tâm thêm vấn đề thiết bị rồi,
chúng ta tham khảo tiếp chương trình điều khiển cho board mạch bên dưới.
Lưu ý các dòng chú thích các bạn sẽ hiểu rất rõ về chương trình.

char buff1; //chuỗi nhận dữ liệu từ cổng nối tiếp


void setup()
{
Serial.begin(9600); //khởi tạo tốc độ baud cổng nối
//tiếp
pinMode(5, OUTPUT); //cấu hình chân kết nối led là ngõ
//ra
digitalWrite(5, LOW); //tắt led
}
void loop()
{
receive_uart();
delay(100);
if (buff1 == '1')
{ digitalWrite(5, HIGH); //cho led sáng
Serial.println("ON");
//gởi phản hồi qua module //HC-05 trạng thái led
}
else if (buff1 == ‘0’)
{
digitalWrite(5, LOW); //cho led tắt
Serial.println("OFF");
//gởi phản hồi trạng thái led tắt
}
}

98
}
//chương trình nhận dữ liệu từ cổng nối tiếp- HC_05
void receive_uart()
{
//while ((Serial.available() == 0)) {}
while (Serial.available() > 0)
{
buff1 = Serial.read();
//đọc dữ liệu cổng nối tiếp chứa vào buff1
}
}

Chức năng chương trình như sau:


Khi có tiń hiê ̣u điề u khiể n từ điê ̣n thoa ̣i gừi đế n Module Hc-05 thì
dùng Arduino đo ̣c dữ liê ̣u về qua cổng nối tiếp UART.
Nế u dữ liê ̣u nhâ ̣n là ‘1’ thì cho chân số 5 lên mức cao (điều khiển
led sáng) và gửi la ̣i một chuỗi dữ liệu phản hồi qua Bluetooth là “ON”.
Ngươ ̣c la ̣i nếu dữ liệu nhận là ‘0’ thì cho chân 5 mức thấ p (điều khiển led
tắt) và gửi lại một chuỗi dữ liệu phản hồi qua Bluetooth là “OFF”.
Như vậy về cơ bản hệ thống điều khiển thiết bị đơn giản qua Bluetooth
đã ổn, mọi nhu cầu phát triển thêm tính năng, hoạt động điều khiển đa dạng ta
có thể xây dựng từ đây. Chúc các bạn có nhiều ý tưởng độc đáo và đầy hấp dẫn
cho những bước nghiên cứu tiếp theo.

99
Chương 5
ĐIỀU KHIỂN THIẾT BỊ DÙNG
CẢM BIẾN GIA TỐC

5.1 Đă ̣t vấ n đề


Cảm biế n gia tố c (accelerometer sensor) là mô ̣t trong những cảm
biế n thông du ̣ng, đươc̣ sử du ̣ng khá nhiề u trong các ứng du ̣ng hay nghiên
cứu làm đồ án. Những cảm biế n gia tố c như MPU6050 hay HMC5883
đươ ̣c sử du ̣ng nhiề u cho các ứng du ̣ng như quadcopter hay robot balance
(robot cân bằng). Trong lãnh vực y sinh cảm biến gia tốc có thể dùng để
đo bước chân, sự vận động, phát hiện sự té ngã,…Và các smartphone hiê ̣n
nay hầ u như đề u đươ ̣c tích hơ ̣p cảm biế n gia tố c.
Những chức năng phổ thông của mô ̣t chiế c smartphone như tự xoay
màn hiǹ h hay những game đua xe điề u khiể n bằ ng cách nghiêng điê ̣n thoa ̣i
để tương tác cũng chính là những ứng du ̣ng từ cảm biế n gia tố c.

Hình 5.1. Cảm biến gia tốc trong điện thoại


Trong chương này tôi muố n giới thiê ̣u với các ba ̣n về cảm biế n gia
tố c trong điê ̣n thoa ̣i Android và các sử dụng dụng nó thông qua mô ̣t ứng
du ̣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. Ta làm một ứng dụng đơn giản,
giả sử có bốn led đơn, 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 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ử du ̣ng cảm biế n Accelerometer tić h hơ ̣p trong điê ̣n thoa ̣i
100
Android và đo ̣c về các giá tri ̣ gán vào ba thông số tương ứng cho ba tru ̣c
là ax, ay, az. Ta ̣o ngưỡng để so sánh với các giá tri ̣ cảm biế n. Khi giá tri ̣
ax đo ̣c về bé hơn -2 thì đang nghiêng điê ̣n thoa ̣i về phía trước. Lớn hơn 2
thì đang nghiêng về phiá sau. Nế u giá tri trong
̣ khoảng -1 đế n 1 ta xem như
cân bằ ng. Tương tự cho tru ̣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.

5.2 Phần mề m trên điêṇ thoa ̣i Android


Phần mềm trên điện thoại có chức năng như sau: dò tìm kết nối bộ
điều khiển thông qua Bluetooth, khi nghiêng điện thoại về một hướng nào
đó thì nó sẽ gởi tín hiệu tương ứng đến bộ điều khiển thông qua giao tiếp
Bluetooth. Như vậy trên màn hình ta cần thiết kế các ImageView để hiển
thị các hình mũi tên tương ứng hướng nghiêng của điện thoại. Nút scan để
dò tìm kết nối thiết bị qua Bluetooth như các chương trước. Ở đây ta sử
dụng bluetooth để giao tiếp giữa phần mềm Android và bộ điều khiển bên
ngoài. Ngoài ra ta cũng dùng một TextView để hiển thị giá trị x, y, z của
cảm biến gia tốc.
Giao diê ̣n đươ ̣c thiế t kế đơn giản với khung màn hiǹ h nằ m ngang
như sau:

Hin
̀ h 5.2. Giao diêṇ của ứng du ̣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 du ̣ng mới cho phép gửi dữ liê ̣u điề u khiể n đi.
101
Hin
̀ h 5.3. Giao diêṇ điề u khiể n
Dùng LinearLayout để thiết kế giao diện, nô ̣i dung file XML thiế t kế
giao diê ̣n 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:id="@+id/layout_main"
android:orientation="vertical"
android:weightSum="100"
android:background="#ffffff">

//textview hiển thị các giá trị x,y,z


<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/textView_ax"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:textSize="10sp"
android:layout_weight="5"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"

102
android:text="scan"
android:id="@+id/button_scan"
android:layout_below="@+id/imageView_bot"
android:layout_alignLeft="@+id/imageView_bot"
android:layout_alignStart="@+id/imageView_bot"
android:layout_weight="5"
android:layout_gravity="center_horizontal" />

<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_weight="60"
android:weightSum="10"
android:id="@+id/layout_con1">
//layout con chứa các hình mũi tên
<RelativeLayout
android:layout_width="153dp"
android:layout_height="387dp"
android:id="@+id/layout_control">

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_top"
android:src="@drawable/top"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_left"
android:src="@drawable/left"
android:layout_below="@+id/imageView_top"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_right"
android:src="@drawable/right"
android:layout_alignTop="@+id/imageView_left"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_bot"
android:src="@drawable/bot"
android:layout_below="@+id/imageView_left"
android:layout_alignLeft="@+id/imageView_top"
android:layout_alignStart="@+id/imageView_top"

103
/>

</RelativeLayout>
//Listview để hiển thị danh sách các thiết bị kết nối qua
bluetooth
<ListView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/listView_bluetooth"
android:background="#ffffff" />

</LinearLayout>

</LinearLayout>

Với bốn ImageView ta hiể n thi cho ̣ bốn hướng của góc nghiêng, một
Listview để lưu các điạ chỉ Bluetooth quét đươ ̣c. Một Button để dò tìm các
thiế t bi co
̣ ́ thể kế t nố i Bluetooth. Một Textview để hiể n thi gia
̣ ́ tri ca
̣ ̉ m biế n
đo ̣c đươ ̣c.
File String.xml với hiê ̣u chỉnh như sau 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 hiǹ h thiế t kế nằ m ngang nên phải hiê ̣u chin̉ h 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

104
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" />

<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

</application>

</manifest>

Chú ý các dòng bên dưới activity. 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 hiǹ h ngang và Potrait là màn hiǹ h do ̣c.
Trong ứng dụng ta sử dụng cảm biến gia tốc nên ta 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à chúng ta giao tiếp với thiết bị bên ngoài qua bluetooth nên ta cũng cho
phép sử dụng bluetooth. Cuối cùng trong ứng dụng ta có thể viết cho điện
thoại rung lên phản hồi trong một vài trường hợp nên ta 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 đó ta vào file MainActivity.java để viết code cho ứng dụng thực
hiện yêu cầu. Tôi 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 bên dưới tôi sẽ giải thích cụ thể hơn:

105
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;
ListView list;
float ax=0,ay=0,az=0;
Button btn_scan;

//khai bá
o mộ
t sensorManager đểquả
n lýcá
c cả
m biế
n
private SensorManager sensorManager;

//khai bá
o mộ
t cả
m biế
n vớ
i tên làAccelerometer
private Sensor Accelerometer;

boolean chophepgui=false;

//khai bá
o cá
c thà
nh phầ
n đểkế
t nố
i vàgử
i nhậ
n bluetooth
BluetoothAdapter myBluetoothAdapter;

106
BluetoothSocket btSocket;
private OutputStream outStream = null;
private InputStream inStream= null;
private static final UUID MY_UUID = UUID
.fromString("00001101-0000-1000-8000-
00805F9B34FB");
private ArrayAdapter<String> BTArrayAdapter;
ArrayList<String> Adress;
ArrayList <String> ID;
String BtAddress= null;
private static final String TAG = "HC-05";
Handler handler = new Handler();

@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtv_view=(TextView)findViewById(R.id.textView_ax);

//khở
i tạo sensormanager
sensorManager =
(SensorManager)getSystemService(SENSOR_SERVICE);

//khở
i tạo cả
m biế
n TYPE_ACCELEROMETER.
Accelerometer =
sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
top=(ImageView)findViewById(R.id.imageView_top);
bot=(ImageView)findViewById(R.id.imageView_bot);
left=(ImageView)findViewById(R.id.imageView_left);
right=(ImageView)findViewById(R.id.imageView_right);
list=(ListView)findViewById(R.id.listView_bluetooth);
btn_scan=(Button)findViewById(R.id.button_scan);

//tạo filter đểnhậ


n phả
n hồ
i xem kế
t nố
i Bluetooth chưa?
IntentFilter filter1 = new

IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
this.registerReceiver(bReceiver, filter1);

//khở
i tạo hay Arraylist đểlưu ID vàAdress củ
a thiế
t bị qué
t
//đượ
c
ID=new ArrayList<String>();
Adress= new ArrayList<String>();

//tạo arrayadapter đểquả


n lývàhiệ
n ID lên ListView
BTArrayAdapter = new ArrayAdapter<String>(this,

android.R.layout.simple_list_item_1, ID);
list.setAdapter(BTArrayAdapter);
myBluetoothAdapter =
BluetoothAdapter.getDefaultAdapter();

107
on();
btn_scan.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
find();
Toast.makeText(MainActivity.this, "Đang quét
thiế
t bi
̣, xin chờtrong giây lá
t", Toast.LENGTH_LONG).show();
}
});
list.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View
view, int position, long id) {
BtAddress = Adress.get(position);
Toast.makeText(MainActivity.this, "Đang kế
t
nố
i", Toast.LENGTH_LONG).show();
Connect();
}
});
}

//hai hà
m con đểthự
c hiệ
n việ
c đọc cả
m biế
n
public void onAccuracyChanged(Sensor arg0, int arg1) {}

public void onSensorChanged(SensorEvent event) {


if (event.sensor.getType()==Sensor.TYPE_ACCELEROMETER)
{
ax=Math.round(event.values[0]);
//đọc và làm tròn giátrị trục x
ay=Math.round(event.values[1]);
//đọc và làm tròn giátrị trục y
az=Math.round(event.values[2]);
//đọc và làm tròn giátrị trục z

//xuấ
t ra textview cá
c giátrị đọc đượ
c
txtv_view.setText("x:"+ax+"y"+ay+"z"+az); }

//sau khi kế t nố


i thà
nh công tớ
i thiế
t bị mớ
i cho phé
p gử
i cá
c
giátrị điề
u khiể n
//cá
c giátrị gửi tương ứ
ng theo ngưỡng

if (chophepgui==true)
{
if (ax <= -2)
{
top.setImageResource(R.drawable.top);
writeData("1");
108
}
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");
}
}
}

//đăng kýlắ
ng nghe khi ứ
ng dụng vừ
a đượ
c bậ
t
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, Accelerometer,
SensorManager.SENSOR_DELAY_NORMAL);
}

//khi ứ
ng dụng pause lại thìngừ
ng đăng ký
@Override
protected void onPause() {
super.onPause();
sensorManager.unregisterListener(this);
}

//hà
m con bậ
t Bluetooth cho thiế
t bị
public void on()

109
{
if (!myBluetoothAdapter.isEnabled())
{
Intent turnOnIntent = new

Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(turnOnIntent, 1);

Toast.makeText(getApplicationContext(), "Bluetooth turned


on",Toast.LENGTH_LONG).show();
}
else{
Toast.makeText(getApplicationContext(),"Bluetooth is
already on", Toast.LENGTH_LONG).show();
}
}

//hà
m nhận phả
n hồ
i vàlấ
y địa chỉID vàAdress củ
a thiế
t bị
bluetooth
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());
}

//khi kế
t nố
i thà
nh công thi chophepgui=true,
// khi chophepgui=true ung dung moi gui du lieu
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;
}
}
};

//hà
m tì
m cá
c thiế
t bị Bluetooth
public void find() {
if (myBluetoothAdapter.isDiscovering()) {
// the button is pressed when it discovers, so
cancel the discovery
110
myBluetoothAdapter.cancelDiscovery();
}
else {
BTArrayAdapter.clear();
myBluetoothAdapter.startDiscovery();

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";
}
}
111
//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();

}
}

Chương trình trên hoa ̣t đô ̣ng như sau:


Vấ n đề quét, kế t nố i và gửi Bluetooth tôi sẽ không nói la ̣i nhiề u vì
chương trước đã nói rõ, các ba ̣n chỉ cầ n chú ý vào một chi tiế t mới này:
Ứng du ̣ng này chỉ hoa ̣t đô ̣ng gửi lê ̣nh khi đã kế t nố i Bluetooth với
thiế t bi tha
̣ ̀ nh công, vì vâ ̣y viê ̣c xác đinh
̣ Bluetooth đã kế t nố i chưa sẽ thông
qua phương thức sau:
Trong hàm Oncreate tôi khởi ta ̣o mô ̣t filter (bộ lọc Intent) như sau:
IntentFilter filter1 = new
IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
Sau đó đăng ký lắ ng nghe cho nó khi BluetoothSocket đã kế t nố i
this.registerReceiver(bReceiver, filter1);
Trong hàm BroadcastReceiver tôi 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 đề đo ̣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); }
112
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á tri ̣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 ax<=-2 thì đang nghiêng phía trước,
ax>=2 đang nghiêng phiá sau, tương tự cho ay khi ay<=-2 thì nghiêng bên
trái và ay>=2 thì nghiêng phải. Có được hướng nghiêng ta thiết lập hình
mũi tên tương ứng với hướng điện thoại đang nghiêng bằng cách load các
hình mũi tên tạo sẵn. Nếu hướng không phải hướng nghiêng (tức là đang
cân bằng) thì các ImageView không phải hướng nghiêng ta load ảnh trắng
(thật ra ta dùng ảnh trùng màu nền hoặc disvible nó đi). Dùng hàm
writedata để gửi dữ liê ̣u tương ứng cho các tra ̣ng thái qua Bluetooth.
Khi ứng du ̣ng đươ ̣c tiếp tục trở lại, ta đăng ký bộ lắ ng nghe cảm biế n
trong hàm onResume
protected void onResume() {
super.onResume();
sensorManager.registerListener(this, Accelerometer,

SensorManager.SENSOR_DELAY_NORMAL);

113
5.3 Bô ̣ Điều Khiể n
Bô ̣ điề u khiể n ở đây vẫn dùng Arduino và module HC05. Việc kết
nối khá đơn giản. Chú ý ở đây ta dùng bốn led đơn tương ứng với 4 hướng
nghiêng của điện thoại. Kết nối điện trở hạn dòng phù hợp cho từng led.
Phần chương trình cho bộ điều khiển rất đơn giản, vi điều khiển sẽ nhận
dữ liệu từ bộ điều khiển và điều khiển led đơn đại diện cho hướng nghiêng
tương ứng hướng nghiêng của điện thoại sáng, các hướng còn lại tắt. Khi
điện thoại ở trạng thái cân bằng led tắt.
Sơ đồ kết nối board Ardunio Uno R3, module HC05 và bốn led đơn

Hin
̀ h 5.4. Sơ đồ kế t nố i phầ n cứng
Code chương triǹ h Arduino
char buff1;
int k;
void receive_uart();
void setup()
{
Serial.begin(9600);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, OUTPUT);
pinMode(9, OUTPUT);

114
}

void loop()
{
receive_uart();
delay(20);
switch (buff1)
{
case 1: digitalWrite(6, HIGH); break;
case 2: digitalWrite(7, HIGH); break;
case 3: digitalWrite(6, LOW);
digitalWrite(7, LOW); break;
case 4: digitalWrite(8, HIGH); break;
case 5: digitalWrite(9, HIGH); break;
case 6: digitalWrite(8, LOW);
digitalWrite(9, LOW); break;
default: digitalWrite(6, LOW); digitalWrite(7, LOW);
digitalWrite(8, LOW); digitalWrite(9, LOW);
break;
}
}
void receive_uart()
{
while ((Serial.available() == 0)) {}
//chờ nhận dữ liệu từ cổng nối tiếp, vì chương trình ta
//không làm việc khác nên ta thêm dòng này, nếu chương
//trình xử lý nhiều việc thì ta nên bỏ nó, và dùng ngắt
//sẽ hiệu quả hơn
while (Serial.available() > 0)
{
buff1 = Serial.read() - 0x30;
}
}

115
116
Chương 6
ĐIỀU KHIỂN THIẾT BỊ BẰNG GIỌNG NÓI

6.1 Đặt vấn đề


Công nghệ nhận dạng và điều khiển bằng tiếng nói xuất hiện khá lâu,
tuy nhiên ngày nay nó trở nên chuẩn xác và phổ biến hơn bao giờ hết. Việc
nhận dạng tiếng nói cho phép ta điều khiển nhanh chóng thiết bị, tạo ra
một cách tương tác khá mới và linh hoạt. Chỉ mới hơn chục năm trước đây,
việc nhận dạng tiếng nói mới được phát triển các thuật toán, các phương
pháp được mô phỏng trên Matlab, C,..các từ nhận dạng còn ít ỏi và kết quả
đạt được chưa như mong đợi. Kể từ khi các thiết bị thông minh ra đời, đặc
biệt là sự hỗ trợ công nghệ mạnh mẽ của các tập đoàn lớn thì các trợ lý
giọng nói như Siri trên IOS hay Google Now trên Android đã đưa nhận
dạng tiếng nói lên một tầm cao mới. Việc nhận dạng và điều khiển bằng
giọng nói dần trở nên quen thuộc và nhu cầu ứng dụng nó ngày càng nhiều.
Một trong một ứng dụng mới nổi của Google là Translate cho phép nhận
dạng tiếng nói, dịch ra một ngôn ngữ khác (bằng chữ hoặc phát âm) gần
như ngay lập tức. Điều đó cho ta thấy rào cản ngôn ngữ dần được phá bỏ.
Ngày 20/02/2016 với sự nghiên cứu và phát triển không ngừng nghỉ,
Google đã chính thức bổ sung thêm 13 ngôn ngữ vào Google Translate,
nâng tổng số ngôn ngữ hỗ trợ lên 103 ngôn ngữ, bao phủ 99% ngôn ngữ
hiện đang xuất hiện trên mạng internet.
Hôm nay tôi sẽ nói về một ứng dụng khá hay trong Android đó là
điều khiển thiết bị bằng giọng nói. Ở đây tôi sử dụng gói SPEECH TO
TEXT của Google. Với việc sử dụng gói mã nguồn mở của Google ta có
thể nhận dạng được rất nhiều ngôn ngữ khác nhau, đặc biệt nó nhận dạng
tiếng Việt phải nói là rất tốt! Từ kết quả nhận dạng ta có thể đưa ra vào
nhiều ứng dụng điều khiển khác nhau. Trong ứng dụng tôi xây dựng bên
dưới đây, tôi sẽ dùng tiếng nói với cú pháp quy ước để điều khiển tắt, mở
ba bóng đèn led. Việc điều khiển bằng giọng nói cực kỳ có ý nghĩa đối với
những người tàn tật, và trong một số trường hợp khác.
Từ giao diện ứng dụng trên điện thoại tôi sẽ nói từ khóa “bật” sau đó
nói thiết bị cần điều khiển. Ví dụ ở đây tôi có 3 bóng đèn là đỏ, xanh lục,
xanh dương, chương trình sẽ tìm kiếm từ khóa trong chuỗi dữ liệu chuyển
về, nếu có từ “bật” và từ “đỏ” thì đèn đỏ sẽ bật, tương tự cho 2 bóng đèn
còn lại. Việc tắt thiết bị chỉ cần thay từ “bật” thành từ “tắt” là được. Đây
là quy ước của tôi, bạn đọc hoàn toàn có thể xây dựng các từ lệnh điều
khiển cho riêng mình.
117
Sau khi có kết quả nhận dạng từ giọng nói về việc điều khiển tắt/mở
các đèn tương ứng, ta lại gởi tín hiệu điều khiển qua bluetooth đến bộ điều
khiển để điều khiển thiết bị. Phần cứng bộ điều khiển và giao thức giao
tiếp với điện thoại giống như các chương trước ta đã làm.

6.2 Ứng dụng trên điện thoại android


Trên điện thoại Android ta viết một ứng dụng điều khiển tạm gọi là
Voice Control. Ứng dụng có giao diện khá đơn giản bao gồm:
- Một TextView hiển thị trạng thái điều khiển liên quan đèn đỏ.
- Một TextView hiển thị trạng thái điều khiển liên quan đèn xanh
dương.
- Một TextView hiển thị trạng thái điều khiển liên quan đèn xanh
lá.
- Một ImageView biểu tưởng Mic để khi ta nhấn vào nó sẽ tiến hành
thu thập và nhận dạng giọng nói.
- Một TextView hiển thị chữ “Tap on Mic”
- Button Scan để ta tìm và kết nối với bộ điều khiển thông qua
Bluetooth.
- Một ListView hiển thị bộ điều khiển kết nối qua Bluetooth
- Một ListView hiển thị dữ liệu nhận dạng của Google trả về.

118
Hình 6.1. Giao diện ứng dụng android
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"

119
android:id="@+id/textView_ledred"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:textSize="30sp"
android:gravity="center"
android:textColor="#f60c0c"
android:textStyle="bold" />

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/textView_ledblue"
android:layout_below="@+id/textView_ledred"
android:layout_centerHorizontal="true"
android:textSize="30sp"
android:gravity="center"
android:textStyle="bold"
android:textColor="#1730ed" />

<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/textView_ledgreen"
android:layout_below="@+id/textView_ledblue"
android:layout_centerHorizontal="true"
android:textSize="30sp"
android:gravity="center"
android:textStyle="bold"
android:textColor="#15f22b" />

<ImageButton
android:id="@+id/btnSpeak"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@null"
android:src="@drawable/mic"
android:layout_above="@+id/textView4"
android:layout_alignRight="@+id/textView4"
android:layout_alignEnd="@+id/textView4" />

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Tap on MIC"
android:textSize="15dp"
android:textStyle="normal"
android:layout_centerVertical="true"
android:layout_centerHorizontal="true"
android:id="@+id/textView4" />

<Button
120
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Scan"
android:id="@+id/button_scan"
android:layout_below="@+id/textView4"
android:layout_alignLeft="@+id/textView4"
android:layout_alignStart="@+id/textView4" />

<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/listView_blue"
android:layout_below="@+id/button_scan"
android:layout_toLeftOf="@+id/linearLayout"
android:layout_toStartOf="@+id/linearLayout"
android:background="#39eea6" />

<ListView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/listView_data"
android:layout_alignParentBottom="true"
android:layout_toEndOf="@+id/linearLayout"
android:layout_below="@+id/button_scan"
android:background="#c0f89d"
android:layout_toRightOf="@+id/linearLayout" />

</RelativeLayout>

File String.XML có nội dung như sau


<resources>
<string name="app_name">Voice-Control</string>
<string name="list_blue">Bluetooth List</string>
</resources>

File Manifest được chỉnh sửa như sau


<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android
"
package="com.example.ngb.voice_control" >

<uses-permission
android:name="android.permission.BLUETOOTH" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN" />

121
<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>

Phần nội dung quan trong nhất. File MainActivity.Java


package com.example.ngb.voice_control;

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.os.Bundle;
import android.os.Handler;
import android.speech.RecognizerIntent;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageButton;
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;
122
public class MainActivity extends AppCompatActivity {
ImageButton btn_speak;
TextView txtv_blue, txtv_red, txtv_green;
Button btn_scan;
final static int RESULT_SPEECH = 1;
ListView list_device, list_data;
ArrayList<String> text;
BluetoothAdapter myBluetoothAdapter;
BluetoothSocket btSocket;
private OutputStream outStream = null;
private InputStream inStream= null;
private static final UUID MY_UUID = UUID
.fromString("00001101-0000-1000-8000-
00805F9B34FB");

//khởi tạo arrayadapter để lưu list thiết bị quét được


// khởi tạo arrayadapter để lưu text chuyển đổi từ giọng
//nói
private ArrayAdapter<String> BTArrayAdapter;
private ArrayAdapter<String> V2TArrayAdapter;
ArrayList<String> Adress;
ArrayList <String> ID;
String BtAddress= null;
private static final String TAG = "HC-05";

Handler handler = new Handler();


byte delimiter = 10;
boolean stopWorker = false;
int readBufferPosition = 0;
byte[] readBuffer = new byte[1024];
private boolean chophepgui=false;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtv_blue = (TextView)
findViewById(R.id.textView_ledblue);
txtv_green = (TextView)
findViewById(R.id.textView_ledgreen);
txtv_red = (TextView)
findViewById(R.id.textView_ledred);
btn_speak = (ImageButton)
findViewById(R.id.btnSpeak);
list_device = (ListView)
findViewById(R.id.listView_blue);
list_data = (ListView)
findViewById(R.id.listView_data);
btn_scan=(Button)findViewById(R.id.button_scan);

123
//khởi tạo 2 array list để lưu ID và địa chỉ của thiết bị
//quét được
//hiển thị các thiết bị quét được trên listview
//list_device
ID=new ArrayList<String>();
Adress=new ArrayList<String>();
BTArrayAdapter = new ArrayAdapter<String>(this,

android.R.layout.simple_list_item_1, ID);
list_device.setAdapter(BTArrayAdapter);
//đăng ký bộ lọc để nhận phản hồi kết nối với thiết bị
//Bluetooth
IntentFilter filter1 = new

IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED);
this.registerReceiver(bReceiver, filter1);

myBluetoothAdapter =
BluetoothAdapter.getDefaultAdapter();

on(); //mở kết nối Bluetooth

//hàm xử lý nút micro được nhấn để nhận dạng giọng nói


btn_speak.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new
Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);

intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,

RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
startActivityForResult(intent,
RESULT_SPEECH);
}
});
//hàm xử lý khi nút Scan được nhấn để dò tìm thiết bị
btn_scan.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
find();
}
});

//xử lý khi listview thiết bị được nhấn


list_device.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
@Override

124
public void onItemClick(AdapterView<?> parent,
View view, int position, long id) {
BtAddress = Adress.get(position);
Toast.makeText(MainActivity.this,"đang
kết nối....",Toast.LENGTH_LONG).show();
Connect();
}
});
}

@Override
//hàm chuyển đổi giọng nói thành text
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_RESUL
TS);
//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);
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"))

125
{
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);
startActivityForResult(turnOnIntent, 1);

Toast.makeText(getApplicationContext(),
"Bluetooth turned on",
Toast.LENGTH_LONG).show();
}
else{

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 =
126
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if
(BluetoothDevice.ACTION_FOUND.equals(action)) {
BTArrayAdapter.add(device.getName());
BTArrayAdapter.notifyDataSetChanged();
Adress.add(device.getAddress());
}
//khi kết nố
i thà
nh công thi chophepgui=true,
khi chophepgui=true ứng
//dụng mới cho phép gởi dữ liệu
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
myBluetoothAdapter.cancelDiscovery();
}
else {
BTArrayAdapter.clear();
myBluetoothAdapter.startDiscovery();

registerReceiver(bReceiver, new

IntentFilter(BluetoothDevice.ACTION_FOUND));
}
}

protected void onDestroy() {


// TODO Auto-generated method stub
super.onDestroy();
if (chophepgui) {
unregisterReceiver(bReceiver);
}
myBluetoothAdapter.disable();

public void Connect() {


Log.d(TAG, BtAddress);
BluetoothDevice device =
myBluetoothAdapter.getRemoteDevice(BtAddress);
Log.d(TAG, "Connecting to ... " + device);
127
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");
}
beginListenForData();
}

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";
}
}

public void beginListenForData() {


try {
inStream = btSocket.getInputStream();
} catch (IOException e) {
}

Thread workerThread = new Thread(new Runnable()


{
public void run()
{

128
while(!Thread.currentThread().isInterrupted() &&
!stopWorker)
{
try
{
int bytesAvailable = inStream.available();
if(bytesAvailable > 0)
{
byte[] packetBytes = new byte[bytesAvailable];
inStream.read(packetBytes);
for(int i=0;i<bytesAvailable;i++)
{
byte b = packetBytes[i];
if(b == delimiter)
{
byte[] encodedBytes = new
byte[readBufferPosition];

System.arraycopy(readBuffer, 0, encodedBytes,
0, encodedBytes.length);
final String rdata = new String(encodedBytes,
"US-ASCII");
readBufferPosition = 0;
//tạo handler nhận dữ liệu phản hồi từ Bluetooth
handler.post(new Runnable()
{
public void run() {

if (rdata.length()>1)
{
//hiển thị trạng thái các đèn dựa vào dữ liệu phản hồi
if (rdata.substring(0,2).equals("ds"))
{txtv_red.setText("đèn đỏ bật");}
if (rdata.substring(0,2).equals("dt"))
{txtv_red.setText("đèn đỏ tắt");}
if (rdata.substring(0,2).equals("ls"))
{txtv_green.setText("đèn xanh lục bật");}
if (rdata.substring(0,2).equals("lt"))
{txtv_green.setText("đèn xanh lục tắt");}
if (rdata.substring(0,2).equals("xs"))
{txtv_blue.setText("đèn xanh dương bật");}
if (rdata.substring(0,2).equals("xt"))
{txtv_blue.setText("đèn xanh dương tắt");}
}
}
}); //handler
}
else
{

129
readBuffer[readBufferPosition++] = b;
}
}
}
}
catch (IOException ex)
{
stopWorker = true;
}
}
}
});

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ỉ nói 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.
1. 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 nói có
được dữ liệu giọng nói (đã chuyển thành text) thì nó hiển thị kết quả 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. Và bây giờ nhiệm vụ của 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);

130
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");
}

//tìm từ khóa đèn xanh lục


if (buff.contains("lục") || buff.contains("Lục"))
{//nếu đúng từ khóa bật đèn xanh lục thì gởi lệnh “2”
writeData("2");
}

//Tìm từ khóa xanh dương


if (buff.contains("dương") || buff.contains("Dương"))
{//nếu đúng từ khóa bật đèn xanh dương gởi lệnh “3”
writeData("3");
}
}

//tìm từ khóa tắt thiết bị


if (buff.contains("tắt")|| buff.contains("Tắt"))
{
if (buff.contains("đỏ") || buff.contains("Đỏ"))
{//nếu đúng từ khóa tắt đèn đỏ thì gởi lệnh “4”
writeData("4");
}
if (buff.contains("lục") || buff.contains("Lục"))
{
writeData("5");
}
if (buff.contains("dương") || buff.contains("Dương"))

131
{
writeData("6");
}
}
}
}
}
}

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_R
ESULTS);
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_R
ESULTS);
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.
2. 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);
}
});

132
6.3 Bộ điều khiển
Việc nhận và điều khiển thiết bị tương tự như những chương trước,
kết nối phần cứng gồm board Arduino Uno R3 điều khiển ba led xanh
dương, đỏ và xanh lục. Việc truyền nhận dữ liệu từ bộ điều khiển đến
smartphone vẫn dùng giao thức Bluethooth thông qua module HC05

Hình 6.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 ta 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 ta cũng phải dùng mã Ascii để kiểm
tra, hoặc theo cách khác là ta 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 ta cộng
thêm 0x30, còn từ Ascii ta chuyển thành nhị phân thường thì trừ 0x30.
Các bạn chú ý các ghi chú kế bên các dòng lệnh để hiểu rõ chương
trình:

133
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
pinMode(9, OUTPUT);
}

void loop()
{
//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");

134
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;

135
//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
}
}

136
Chương 7
ĐIỀU KHIỂN THIẾT BỊ QUA WIFI

7.1 Đặt vấn đề


Trong chương này, tác giả xin giới thiệu giải pháp điều khiển thiết
bị qua wifi. So với Bluetooth thì giải pháp wifi có vẻ phức tạp hơn, tuy
nhiên tính ổn định và phạm vi điều khiển là một điểm cộng cho giải pháp
này. Từ ứng dụng Android chạy trên thiết bị di động tác giả giao tiếp với
bộ điều khiển thông qua module wifi ESP8266. Do sự phức tạp của hệ
thống và cách điều khiển nên ở đây tác giả xin giới thiệu sơ lược qua về
phần cứng của ESP8266 và cách xử lý trên ứng dụng Android trước khi ta
đi vào bài toán cụ thể.

7.2 Đặc điểm Module wifi ESP8266


ESP8266 cung cấp một giải pháp mạng Wi-Fi khá ấn tượng, cho
phép nó có thể lập trình các ứng dụng trên chính nó hoặc có thể dùng nó
để giảm tải cho tất cả các chức năng mạng Wi-Fi từ một ứng dụng vi xử lý
khác.
Module ESP có ba chế độ vận hành. Chế độ đầu tiên là ST (Station).
Ở chế độ ST nó sẽ hoạt động như một thiết bị và kết nối đến một Access
point đã tồn tại. Hay nói cách khác nó đóng vai trò như một trạm thu sóng
wifi. Chế độ thứ hai là AP (Access Point) nó hoạt động như là một Access
Point, hay nói cách khác nó là một trạm phát wifi và các thiết bị khác (điện
thoại chẳng hạn) có thể kết nối đến nó. Chế độ thứ ba là kết hợp cả hai chế
độ trên. Ở chế độ này sự vận hành của nó được thiết lập bằng tập lệnh AT.
Vì esp8266 có khoảng cách phát wifi khá ngắn trong khoảng 10 mét
nên trong ứng dụng này tôi chọn chế độ Station, nó đóng vai trò là một
trạm thu. Điện thoại Android và ESP8266 làm client giao tiếp với nhau
qua modem WIFI.

137
Hình 7.1. Hình dáng và sơ đồ chân module wifi ESP8266
Một số thuộc nổi bật của nó là:
- Sử dụng giao thức 802.11 b/g/n
- Wi-Fi Direct (P2P : điểm đến điểm), soft-AP (đóng vai trò
như một Access Point)
- Stack giao thức tích hợp TCP/IP
Kết nối phần cứng
Phần cứng kết nối với module ESP8266 khá đơn giản, tuy
nhiên cần một vài lưu ý về nguồn cung cấp như sau:
- ESP8266 yêu cầu nguồn 3.3V, không được cấp nguồn 5V.
- ESP8266 giao tiếp với các vi điều khiển qua cổng nối tiếp
UART với mức điện áp là 3.3V vì vậy nếu có giao tiếp với vi điều
khiển thì nên có sự chuyển đổi nguồn phù hợp với các vi điều khiển
đang dùng nguồn 5V.
Tuy nhiên, nếu bạn thích mạo hiểm và không sợ hãi thì bạn có
thể bỏ qua yêu cầu thứ hai. Và nhớ rằng nếu có không ai chịu trách
nhiệm về những gì xảy ra nếu bạn làm điều đó 
Dòng khởi động tối đa của ESP8266 khoảng 320mA tại 3.3V.
Khi hoạt động bình thường ở chế độ AP thì khoảng 60mA. Ở chế độ
phát wifi thì dòng tiêu thụ xấp xỉ 200mA. Cụ thể như sau:

138
Bảng 7.1. Dòng tiêu thụ của ESP8266 trong các chế độ

Sơ đồ bố trí lớp Top của ESP8266 như sau:

Hình 7.2. Sơ đồ bố trí lớp Top của ESP8266


Khi cấp nguồn cho module ESP thì bạn sẽ thấy đèn đỏ sáng và đèn
xanh sẽ nhấp nháy trong khoảng thời gian ngắn.
Cách tốt nhất để cho ESP8266 và board điều khiển hoạt động tốt là
ta nên có một bộ nguồn riêng phù hợp với điện áp 3.3V, dòng ngõ ra đáp
ứng được hoạt động của hệ thống.

139
Khi sử dụng kết hợp với board Ardunio Uno R3, nếu bạn nối trực
tiếp đến ESP8266 thì đây không phải là giải pháp an toàn. Bạn có thể làm
hỏng board Arduino hoặc module ESP bởi các lí do sau:
- Esp8266 không chấp nhận các ngõ vào 5V – bạn có thể phá hủy
module wifi
- Esp8266 có thể hút dòng lớn hơn dòng từ chân nguồn 3.3V của
Arduino, dẫn đến board Arduino bị phá hủy.
- Các hoạt động bên ngoài giới hạn ổn định của Esp8266 sẽ có thể
không ổn định và không đáng tin cậy, dẫn đến những kết quả sai với logic
suy nghĩ của bạn.

7.3 Một số hỗ trợ kết nối wifi trong Android Studio


Do ứng dụng dùng để điều khiển thiết bị qua mạng wifi nội bộ
nên cần có liên kết mạng.
Lập trình mạng trong android được hỗ trợ trực tiếp bởi lớp
java.net của ngôn ngữ java. Trong đó có hai lớp cơ bản sau để xây
dựng lên một ứng dụng mạng sử dụng Socket.

 Lớp InetAddress
- Lớp InetAddress được sử dụng để biểu diễn các địa chỉ IP
trong một ứng dụng mạng. Lớp này được sử dụng bởi hầu hết các
lớp mạng, bao gồm Socket, ServerSocket, DatagramSocket,
DatagramPacket,…
- Nó bao gồm hai trường thông tin :
+ hostName (một đối tượng kiểu String)
+ address (một số kiểu int).
Các trường này không phải là trường public, vì thế ta không thể
truy xuất chúng trực tiếp.
- Mô tả mô hình khai báo đối tượng InetAddress :
public class InetAddress
{
private String hostName;

140
private int address;
public String getHostName()
{
return hostName;
}
};
 Phương Thức và Thuộc Tính
- Lớp InetAddress không có các hàm khởi tạo (constructor) .
Tuy nhiên, có ba phương thức tĩnh trả về các đối tượng thuộc kiểu
InetAddress :
1/ public static InetAddress
InetAddress.getByName(String hostname)
Phương thức này được sử dụng để kết nối đến một máy chủ xác
định, tham số truyền vào là một chuỗi ký tự. Chuỗi này có thể là : tên
máy, địa chỉ IP hoặc là địa chỉ một trang web…
2/ public static InetAddress[ ]
InetAddress.getAllByName(String hostname)
Kết quả trả về là một mảng đối tượng thuộc kiểu InetAddress
3/ public static InetAddress InetAddress.getLocalHost()
Phương thức này được sử dụng để đọc thông tin của máy cục
bộ đang làm việc.
- Một số phương thức khác :
public String getHostName() :
Phương thức này trả về một chuỗi biểu diễn hostname của một
đối tượng InetAddress. Nếu máy không có hostname, thì nó sẽ trả về
địa chỉ IP của máy này dưới dạng một chuỗi ký tự.
public byte[ ] getAddress() :
Nếu bạn muốn biết địa chỉ IP của một máy, phương thức
getAddress() trả về một địa chỉ IP dưới dạng một mảng các byte.
Thông qua kết nối wifi, người dùng sẽ điều khiển và theo dõi
thiết bị hoạt động qua app trên điện thoại android. Ta chọn Wifi vì
141
tính thuận tiện và phổ biến của nó hiện nay, trong đó sử dụng giao
thức UDP trên tiêu chuẩn TCP/IP để gửi dữ liệu điều khiển.
Hàm transfer(string s) được sử dụng để gửi dữ liệu qua giao
thức UDP cho mạch điều khiển.
Trước đó ta cần import các thư viện của java để khởi tạo việc
nhập xuất dữ liệu và kết nối mạng.
import java.io.*;
import java.net.*;
- Import java.net.*; Import java.net.*;
byte[] b=(s.getBytes());
byte[] a= new byte[1024];
- Khởi tạo server để chờ dữ liệu:
server = new DatagramSocket();
- Gửi dữ liệu điều khiển:
try{
send = new DatagramPacket(b,b.length, ip, udpport);

server.send(send);

server.setSoTimeout(50);

while(receive.getData()==null) {

receive = new DatagramPacket(a, a.length);

server.receive(receive);

Thread.sleep(10);
}

}catch(Exception e){

}
- Gán dữ liệu nhận vào biến để hiển thị lên giao diện điều khiển:

142
modifiedSentence = new String(receive.getData());
incomming.setText(modifiedSentence);
- Đóng server:
server.close();
Tuy nhiên thay vì chúng ta làm tất cả, các bạn hoàn toàn có thể sử
dụng các class được viết sẵn và chia sẻ của cộng đồng mạng. Đây chính là
điều tuyệt vời của mã nguồn mở và bạn chỉ cần hiểu đầu vào, đầu ra, sử
dụng cho hợp lý và có các tùy chỉnh nếu cần.

7.4 Hệ thống điều khiển thiết bị qua Wifi


Trong ứng dụng của chúng ta ở đây, ta viết phần mềm trên điện thoại
Android điều khiển hai thiết bị.
Phần mềm trên điện thoại có các ImageView để hiển thị hình ảnh
tương ứng trạng thái các thiết bị tắt/mở. Hai swich để điều khiển hai led.
Giao diện ứng dụng như sau:

TẮT TẮT

SEND TO HOST

Hình 7.3. Giao diện ứng dụng khi khởi động và Dialog đăng nhập
Wifi

143
Khi gạt công tắc điều khiển thiết bị thì nó gởi lệnh điều khiển qua
wifi, sau đó nó chờ tín hiệu phản hồi để cập nhật trạng thái thiết bị thông
qua hình ảnh trên các ImageView

Dev1 Dev2 Dev1 Dev2

Hình 7.4. Giao diện điều khiển và hiển thị trạng thái khi nhận được
phản hồi
Giao diện được thiết kế đơn giản như sau code trong file
MainActivity.xml

<?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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:orientation="vertical"
tools:context="com.example.ngb.scan_wifi.MainActivity"
android:id="@+id/layout_1"
android:weightSum="100"
android:background="#ffffff">

<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="20"

144
android:weightSum="10"
android:id="@+id/layout_2">

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_dv1"
android:layout_gravity="center_vertical"
android:src="@drawable/light_off"
android:layout_weight="5"
android:contentDescription="@string/img1" />

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_dv2"
android:layout_gravity="center_vertical"
android:layout_weight="5"
android:src="@drawable/light_off"
android:contentDescription="@string/img2" />
</LinearLayout>

<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_gravity="center_horizontal"
android:layout_weight="20"
android:weightSum="10">

<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dev1"
android:id="@+id/switch_dv1"
android:layout_weight="5"
android:checked="false" />

<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Dev2"
android:id="@+id/switch_dv2"
android:layout_weight="5"
android:checked="false" />
</LinearLayout>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send to Host"
android:id="@+id/button_send"
android:layout_gravity="center_horizontal"
android:layout_weight="5"/>

145
<ListView
android:layout_width="fill_parent"
android:layout_height="94dp"
android:id="@+id/listView_list"
android:layout_weight="37.64" />

</LinearLayout>

Định nghĩa các key trong file String.XML


<resources>
<string name="app_name">scan_wifi</string>
<string name="img1">img1</string>
<string name="img2">img2</string>
</resources>

Lưu ý các khai báo trong file Manifest


<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ngb.scan_wifi">
<uses-permission
android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission
android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"></uses-
permission>
<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>

146
Hai dòng permission trong file manifest sau dùng để cho phép sử
dụng gói Wifi của Android
<uses-permission
android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission
android:name="android.permission.CHANGE_WIFI_STATE" />
Mặc dù ứng dụng điều khiển trong mạng LAN nhưng ứng dụng có
dùng Class HttpRequest sử dụng giao thức HTTP để truyền dữ liệu, nên
để sử dụng Class này phải cho phép hai permission sau.

<uses-permission android:name=
"android.permission.INTERNET"/>
<uses-permission android:name=
"android.permission.ACCESS_NETWORK_STATE">

File HttpRequest.Class
package com.example.ngb.scan_wifi;

/**
* Created by nk on 27/12/2015.
*/

import android.util.Log;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;

147
/*
* This helper class was created by StackOverflow user: MattC
http://stackoverflow.com/users/21126/mattc
* IT was posted as an Answer to this question:
http://stackoverflow.com/questions/2253061/secure-http-post-in-
android
*/

public class HttpRequest{

DefaultHttpClient httpClient;
HttpContext localContext;
private String ret,sttline;

HttpResponse response = null;


HttpPost httpPost = null;
HttpGet httpGet = null;

public HttpRequest(){
HttpParams myParams = new BasicHttpParams();

HttpConnectionParams.setConnectionTimeout(myParams,
20000);
HttpConnectionParams.setSoTimeout(myParams, 20000);
httpClient = new DefaultHttpClient(myParams);
localContext = new BasicHttpContext();
}

public void clearCookies() {


httpClient.getCookieStore().clear();
}

public void abort() {


try {
if (httpClient != null) {
System.out.println("Abort.");
httpPost.abort();
}
} catch (Exception e) {
System.out.println("Your App Name Here" + e);
}
}

public String sendPost(String url, List<NameValuePair>


data) {
return sendPost(url, data, null);
}

public String sendJSONPost(String url, List<NameValuePair>


data) {
return sendPost(url, data, "application/json");
}

//public String sendPost(String url, String data, String


contentType)
public String sendPost(String url, List<NameValuePair>

148
data, String contentType) {
ret = null;

httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY,
CookiePolicy.RFC_2109);

httpPost = new HttpPost(url);


response = null;

StringEntity tmp = null;

httpPost.setHeader("Connection", "Close");
//httpPost.setHeader("User-Agent", "Android");
//httpPost.setHeader("Accept",
"text/html,application/xml,application/xhtml+xml,text/html;q=0.
9,text/plain;q=0.8,image/png,*;q=0.5");

/*if (contentType != null) {


httpPost.setHeader("Content-Type", contentType);
} else {
httpPost.setHeader("Content-Type",
"application/x-www-form-urlencoded");
}*/
try {
httpPost.setEntity(new
UrlEncodedFormEntity(data,"UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
/*try {
tmp = new StringEntity(data,"UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e("Your App Name Here", "HttpUtils :
UnsupportedEncodingException : "+e);
}
httpPost.setEntity(tmp);*/

//Log.d("Your App Name Here", url + "?" + data);

try {
response =
httpClient.execute(httpPost,localContext);

if (response != null) {
//ret =
EntityUtils.toString(response.getEntity());
sttline=response.getStatusLine().toString();
}
} catch (Exception e) {
Log.e("Your App Name Here", "HttpUtils: " + e);
Log.d("TAG", "ERR: Execute");
}

//Log.d("Your App Name Here", "Returning value:" +


ret);

149
//return ret;
return sttline;
}

public String sendGet(String url) throws IOException {


httpGet = new HttpGet(url);
response = httpClient.execute(httpGet);
ret=response.getStatusLine().toString();
//int status =
response.getStatusLine().getStatusCode();

// we assume that the response body contains the error


message
/*try {
ret = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
Log.e("Your App Name Here", e.getMessage());
}*/

return ret;
}

public InputStream getHttpStream(String urlString) throws


IOException {
InputStream in = null;
int response = -1;

URL url = new URL(urlString);


URLConnection conn = url.openConnection();

if (!(conn instanceof HttpURLConnection))


throw new IOException("Not an HTTP connection");

try{
HttpURLConnection httpConn = (HttpURLConnection)
conn;
httpConn.setAllowUserInteraction(false);
httpConn.setInstanceFollowRedirects(true);
httpConn.setRequestMethod("GET");
httpConn.connect();

response = httpConn.getResponseCode();

if (response == HttpURLConnection.HTTP_OK) {
in = httpConn.getInputStream();
}
} catch (Exception e) {
throw new IOException("Error connecting");
} // end try-catch

return in;
}
}

150
Để sử dụng được HttpRequest.Class này các bạn phải sửa trong file
Android/Gradle Scripts/Build.gradle như sau:

apply plugin: 'com.android.application'

android {
compileSdkVersion 22
buildToolsVersion "22.0.0"

defaultConfig {
applicationId "com.example.ngb.google_form"
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-
android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:appcompat-v7:22.1.1'
compile 'com.squareup.okhttp:okhttp:2.0.0'

File MainActivity.java
package com.example.ngb.scan_wifi;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.text.InputType;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;

151
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.Switch;
import android.widget.Toast;

import org.apache.http.NameValuePair;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {


ListView lv;
Switch dv1,dv2;
ImageView icon_dv1, icon_dv2;
Button btn_send;
WifiManager wifi;
String wifis[];
WifiScanReceiver wifiReciever;
String ssid;
ArrayList<String> Ssid;
String key=null;
String col1;
String col2;
final String myTag = "Mytag";
Handler ktra;
String res;
String IP="192.168.1.88";
private ArrayAdapter<String> arrayAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ktra=new Handler();
Ssid=new ArrayList<String>();
icon_dv1=(ImageView)findViewById(R.id.imageView_dv1);
icon_dv2=(ImageView)findViewById(R.id.imageView_dv2);
lv=(ListView)findViewById(R.id.listView_list);
dv1=(Switch)findViewById(R.id.switch_dv1);
dv2=(Switch)findViewById(R.id.switch_dv2);
btn_send=(Button)findViewById(R.id.button_send);

wifi=(WifiManager)getSystemService(Context.WIFI_SERVICE);
if (!wifi.isWifiEnabled()){wifi.setWifiEnabled(true);}
else{
Toast.makeText(MainActivity.this, "Wifi is
enabled", Toast.LENGTH_LONG).show();}
wifiReciever = new WifiScanReceiver();
wifi.startScan();
lv.setOnItemClickListener(new
AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View
view, int position, long id) {

152
ssid = Ssid.get(position);
final AlertDialog.Builder builder = new
AlertDialog.Builder(MainActivity.this);
final EditText input = new
EditText(MainActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD);
builder.setView(input);
builder.setTitle("nhập mật khẩu");
builder.setPositiveButton("OK", new
DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
key = input.getText().toString();
Toast.makeText(MainActivity.this, "Đang kết nối với " +
ssid, Toast.LENGTH_LONG).show();
WifiConfiguration wifiConfig = new WifiConfiguration();
wifiConfig.SSID = String.format("\"%s\"", ssid);
wifiConfig.preSharedKey = String.format("\"%s\"", key);
int netId = wifi.addNetwork(wifiConfig);
wifi.disconnect();
wifi.enableNetwork(netId, true);
wifi.reconnect();
}
});

builder.setNegativeButton("Cancel", new
DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
dialog.cancel();
}
});

builder.show();
}
});

btn_send.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v) {
res = null;
if (dv1.isChecked()) {
col1 = "1";
}
if (!dv1.isChecked()) {
col1 = "0";
}
if (dv2.isChecked()) {
col2 = "1";

153
}
if (!dv2.isChecked()) {
col2 = "0";
}
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
postData();
} catch (IOException e) {
e.printStackTrace();
}
}
});
t.start();
}
});
}
protected void onDestroy()
{
wifi.setWifiEnabled(false);
super.onDestroy();
}
protected void onPause() {
unregisterReceiver(wifiReciever);
super.onPause();
}
protected void onResume() {
registerReceiver(wifiReciever, new
IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
super.onResume();
}
private class WifiScanReceiver extends BroadcastReceiver{
public void onReceive(Context c, Intent intent) {
List<ScanResult> wifiScanList =
wifi.getScanResults();
wifis = new String[wifiScanList.size()];
for(int i = 0; i < wifiScanList.size(); i++) {
wifis[i] = ((wifiScanList.get(i)).SSID);
Ssid.add(wifis[i]);
}
arrayAdapter=new
ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_
list_item_1,wifis);
lv.setAdapter(arrayAdapter);
}
}
public void postData() throws IOException {
List<NameValuePair> pairs= new ArrayList<>();
HttpRequest mReq = new HttpRequest();

res=mReq.sendGet("http://"+IP+"/Device1="+col1+"Device2="+col2)
;
Log.d(myTag, res);
if (res != null) {
ktra.post(new Runnable() {

154
@Override
public void run()
{
if (res.equals("HTTP/1.1 200 OK"))
{
if (dv1.isChecked())
{icon_dv1.setImageResource(R.drawable.light_on);}
if (!dv1.isChecked())
{icon_dv1.setImageResource(R.drawable.light_off);}
if (dv2.isChecked())
{icon_dv2.setImageResource(R.drawable.light_on);}
if (!dv2.isChecked())
{icon_dv2.setImageResource(R.drawable.light_off);}
}
}
});
}
}
}

Phần điều khiển ở đây khá đơn giản nên tôi sẽ nói sơ lược qua như
sau:
- Có hai Switch để điều khiển hai thiết bị, có hai ImageView hình
hai bóng đèn để hiển thị trạng thái của thiết bị khi nhận phản hồi về từ bộ
điều khiển.
- Sau khi chọn trạng thái nhấn vào Button “SEND TO HOST” để
điều khiển. Nếu điều khiển thành công bộ điều khiển sẽ gửi lại phản hồi
theo định dạng HTTP. Ở đây tôi chỉ đọc tiêu đề của chuổi phản hồi là
HTTP/1.1 200 OK
- Nhận được phản hồi hai ImageView sẽ đổi trạng thái theo Swith
đã gạt.
- Phần Switch, Listview, ImageView, Button vẫn khai báo và ánh xạ
như những ứng dụng khác.
- Khai báo và khởi tạo bộ WifiManager để quản lý sử dụng các chức
năng của WIFI.
WifiManager wifi;
wifi=(WifiManager)getSystemService(Context.WIFI_SERVICE);
- Khai báo và khởi tạo bộ wifiReciever để tạo bộ thu nhận Broadcast
khi quét Wifi xung quanh thiết bị.

155
WifiScanReceiver wifiReciever, wifiReciever = new
WifiScanReceiver();
- Khởi tạo quét Wifi trong hàm OnResum khi vừa bật ứng dụng lên
protected void onResume() {
registerReceiver(wifiReciever, new
IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTI
ON));
super.onResume();
}
- Đây là hàm nhận Broadcast khi quét Wifi xung quanh. Sau khi quét
ta dùng một List là wifiScanList để lưu các mạng Wifi và thông số liên
quan của các mạng Wifi xung quanh.

private class WifiScanReceiver extends BroadcastReceiver{


public void onReceive(Context c, Intent intent) {
List<ScanResult> wifiScanList = wifi.getScanResults();
wifis = new String[wifiScanList.size()];
for(int i = 0; i < wifiScanList.size(); i++) {
wifis[i] = ((wifiScanList.get(i)).SSID);
Ssid.add(wifis[i]);
}
arrayAdapter=new

ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_it
em_1,wifis);
lv.setAdapter(arrayAdapter);
}
}

Sau đó ta khởi tạo thêm một String Wifis[] để tách SSID tức là tên
của các mạng Wifi và bỏ đi các thông số khác bằng lệnh :
wifis[i] = ((wifiScanList.get(i)).SSID);
Dùng ArrayList Ssid đã khởi tạo từ trước để lưu tất cả các tên của
các mạng Wifi quét được
Ssid.add(wifis[i]);
Xuất các SSID quét được ra ListView cho hiểu thị ra MainActivity

156
arrayAdapter=new
ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_list_it
em_1,wifis);
lv.setAdapter(arrayAdapter);
Giống như ứng dụng điều khiển Bluetooth trước đó, khi chọn vào
một trong các tên mạng quét được trên ListView thì kết nối vào mạng đó.
Do đặc trưng Wifi không như Bluetooth nên không có Dialog sẵn hiện lên
cho ta đăng nhập, vì thế ta phải tự tạo dialog đăng nhập như sau:

public void onItemClick(AdapterView<?> parent, View view, int


position, long id) {
ssid = Ssid.get(position);
final AlertDialog.Builder builder = new
AlertDialog.Builder(MainActivity.this);
final EditText input = new EditText(MainActivity.this);
input.setInputType(InputType.TYPE_CLASS_TEXT |
InputType.TYPE_TEXT_VARIATION_PASSWORD);
builder.setView(input);
builder.setTitle("nhập mật khẩu");

builder.setPositiveButton("OK", new DialogInterface.OnClickListener()


{
@Override
public void onClick(DialogInterface dialog, int which) {
key = input.getText().toString();
Toast.makeText(MainActivity.this, "Đang kết nối với "
+ ssid, Toast.LENGTH_LONG).show();
WifiConfiguration wifiConfig = new WifiConfiguration();
wifiConfig.SSID = String.format("\"%s\"", ssid);
wifiConfig.preSharedKey = String.format("\"%s\"",
key);
int netId = wifi.addNetwork(wifiConfig);
wifi.disconnect();
wifi.enableNetwork(netId, true);
wifi.reconnect();
}
});

157
builder.setNegativeButton("Cancel", new
DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
}
});

builder.show();
}
});

Ở trên là code để tạo giao diện Dialog đơn giản gồm một tiêu đề,
một EditText để nhập Password.
Hai Button: Button OK được Set là PositiveButton, còn Button
Cancel được set là NegativeButton.
Khi nhấn OK là có một đoạn code:
WifiConfiguration wifiConfig = new WifiConfiguration();
wifiConfig.SSID = String.format("\"%s\"", ssid);
wifiConfig.preSharedKey = String.format("\"%s\"", key);
int netId = wifi.addNetwork(wifiConfig);
wifi.disconnect();
wifi.enableNetwork(netId, true);
wifi.reconnect();

Đó là đoạn đăng nhập vào mạng wifi chọn được trên ListView, với Key
chính là Password của mạng được ta nhập trực tiếp trên Dialog.
Sau khi đăng nhập xong ứng dụng có thể gửi lệnh điều khiển tới
thiết bị khác.
Chú ý: String IP="192.168.1.88"; ở đây là IP của Module
Esp8266 của tôi trong mạng Wifi hiện tại, nếu là các bạn sẽ có IP khác,
tôi sẽ nói về cách lấy IP của Esp8266 trong phần bộ điều khiển Arduino
và Esp8266.
Khi Click vào nút nhấn SEND TO HOST bắt đầu phần điều khiển:
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

158
res = null;
if (dv1.isChecked()) {
col1 = "1";
}
if (!dv1.isChecked()) {
col1 = "0";
}
if (dv2.isChecked()) {
col2 = "1";
}
if (!dv2.isChecked()) {
col2 = "0";
}
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
postData();
} catch (IOException e) {
e.printStackTrace();
}
}
});
t.start();
}
});
Khi nhấn Button SEND TO HOST ta lấy trạng thái của các Switch
điều khiển và dùng Hàm PostData để gửi lệnh điều khiển đi. Do hàm
postData cần được nối mạng liên tục nên phải dùng một luồng để chạy
độc lập. Ta tạo một luồng Thread là t.
Hàm PostData
public void postData() throws IOException {
HttpRequest mReq = new HttpRequest();

res=mReq.sendGet("http://"+IP+"/Device1="+col1+"Device2="+col2
);
Log.d(myTag, res);
if (res != null) {
ktra.post(new Runnable() {
@Override
159
public void run() {
if (res.equals("HTTP/1.1 200 OK"))
{
if (dv1.isChecked())
{icon_dv1.setImageResource(R.drawable.light_on);}
if (!dv1.isChecked())
{icon_dv1.setImageResource(R.drawable.light_off);}
if (dv2.isChecked())
{icon_dv2.setImageResource(R.drawable.light_on);}
if (!dv2.isChecked())
{icon_dv2.setImageResource(R.drawable.light_off);}
}
}
});
}
}

Ta tạo một res kiểu dữ liệu String và dùng giao thức GET để gửi dữ
liệu đi, có thể dùng giao thức POST cũng được, ở đây để đơn giản tôi dùng
GET.
res=mReq.sendGet("http://"+IP+"/Device1="+col1+"Device2="+col2);
res sẽ nhận về một chuỗi ký tự được phản hồi về từ bộ điều khiển
đó là HTTP/1.1 200 OK
Khi nhận được chuỗi điều khiển đó thì xem như việc gửi điều khiển
đã thành công. Ta bắt đầu thay đổi trạng thái của các ImageView cho phù
hợp. Vì đang trong Thread nên ta phải dùng một Handler để thực hiện công
việc này.
chú ý: để tạo được Handler ta phải Import lớp này vào bằng cú
pháp:
import android.os.Handler;

Về bộ điều khiển, nhắc lại ta sẽ dùng board Arduino UNO R3 kết


nối đến module Wifi ESP8266. Để đảm bảo hệ thống hoạt động tốt ta nên
cấp nguồn đúng yêu cầu cho các module. Một gợi ý ở đây là dùng IC ổn
áp 3.3V 1117. Hiển nhiên bạn có thể chọn các giải pháp khác phù hợp hơn.

160
Hình 7.5. Sơ đồ kết nối nguồn cung cấp 3.3V cho ESP8266
Vì ta muốn xem các thông tin truyền nhận dữ liệu qua phần mềm
Serial trên máy tính nên ta không sử dụng hai chân mặc định Tx, Rx của
Arduino để kết nối đến module ESP. Bạn có thể sử dụng các chân khác và
import thư viện <SoftwareSerial.h> phục vụ cho việc truyền nhận dữ liệu.
Tương ứng với phần mềm điều khiển ở bộ điều khiển ta gắn hai led
đơn giả lập thiết bị 1 (Dev1) và thiết bị 2 (Dev2). Chú ý đến các điện trở
hạn dòng cần thiết. Sơ đồ đơn giản của bộ điều khiển như sau.

161
Hình 7.6. Bộ điều khiển Arduino+Esp8266
Ta sử dụng chân 10 Ardunio kết nôi đến chân Tx của ESP, chân 11 của
Ardunio kết nối chân Rx của ESP. Việc khai báo kết nối các chân này
nằm trong file uartWIFIUNO.h. Tôi sẽ giải thích bên dưới.
Chương trình điều khiển cho Arduino như sau:
/*
ESP8266_TX->RX(D10)
ESP8266_RX->TX(D11)
ESP8266_CH_PD->3.3V
ESP8266_VCC->3.3V
ESP8266_GND->GND
*/
#define SSID "pog11"
#define PASSWORD "12355678"

#include "uartWIFIUNO.h"

#include <SoftwareSerial.h>

WIFIUNO wifi;

extern int chlID;


162
// set pin numbers:
const int L1 = 2; //khai báo thiết bị 1
const int L2 = 3; //khai báo thiết bị 2
void setup() {

pinMode(L1, OUTPUT);
pinMode(L2, OUTPUT);
digitalWrite(L1, LOW);
digitalWrite(L2, LOW);
DebugSerial.println("ESP8266 Server v0.21");

if (!wifi.begin())
{
DebugSerial.println("Begin error");
}
bool b = wifi.Initialize(STA, SSID, PASSWORD);
if (!b)
{
DebugSerial.println("Init error");
}
//chắc chắn rằng module đủ thời gian để lấy địa chỉ IP
delay(2000);
String ipstring = wifi.showIP();
//cho hiển thị địa chỉ IP của module
DebugSerial.println(ipstring);
delay(1000);
wifi.confMux(1);
delay(100);
if (wifi.confServer(1, 80))
DebugSerial.println("Server is set up");

163
}
void loop()
{

char buf[500];
int iLen = wifi.ReceiveMessage(buf);

if (iLen > 0)
{

DebugSerial.print(buf);

if (strstr(buf, "Device1=1") != NULL) {


DebugSerial.print("RELAY 1 ON/r/n");
digitalWrite(L1, HIGH);
}
else if (strstr(buf, "Device1=0") != NULL) {
DebugSerial.print("RELAY 1 OFF/r/n");
digitalWrite(L1, LOW);
}

if (strstr(buf, "Device2=1") != NULL) {


DebugSerial.print("RELAY 2 ON/r/n");
digitalWrite(L2, HIGH);
}
else if (strstr(buf, "Device2=0") != NULL) {
DebugSerial.print("RELAY 2 OFF");
digitalWrite(L2, LOW);
}
String cmd;
cmd = "HTTP/1.1 200 OK\r\n";
cmd += "Content-Type: text/html\r\n";
cmd += "Connection: close\r\n";

164
//cmd += "Refresh: 15\r\n";
cmd += "\r\n";
//cmd += "<!DOCTYPE HTML>\r\n";
cmd += "<html>\r\n";
//cmd += "<header><title>ESP8266
Webserver</title><h1>\"ESP8266 Web Server
Control\"</h1></header>";
cmd += "<html>\r\n";
wifi.Send(chlID, cmd);
//delay(200);
wifi.closeMux(chlID);
//delay(1000);
}
}

Chú ý là ở đây tôi có sử dụng thư viện hỗ trợ xử lý wifi là


uartWIFIUNO
Thư viện được tải tại:
https://github.com/DonEduardo/SprinklerControl/tree/master/ESP8266U
NO
Sau khi tải thư viện ta mở file uartWIFIUNO.h để sửa tốc độ BAUD
của UART arduino cho đúng với tốc độ màn hình Serial. Ta thiết lập tốc
độ baud là 9600. Vấn đề quan trọng nữa là ta thiết lập hai chân 10, 11 của
Ardunio tương đương hai chân Rx, Tx trong giao tiếp UART để kết nối
đến chân Tx, Rx module ESP đúng như thiết kế phần cứng của ta.

165
Hình 7.7. Chỉnh sửa lại thư viện uartWIFIUNO.h

Tiếp theo mở file uartWIFIUNO.c pp thiết lập lại tốc độ baud

Hình 7.8. Thiết lập lại tốc độ baud trong file uartWIFIUNO.cpp
Ta sửa tốc độ baud cho đúng với module Esp8266. Module của tôi
là 38400.
Sau khi sử dụng thư viện thì việc coding khá ngắn và đơn giản,
Trong phần viết ứng dụng trên Android ta cần biết IP của module. Ở
đây ta lấy IP như sau: khi nạp code cho arduino các bạn mở Serial lên và
đợi vài giây để lấy IP của Module, sau đó điền vào mục IP trong chương
trình Android phía trên.

166
Hình 7.9. Lấy IP thông qua Serial của Arduino

167
168
Chương 8
ĐIỀU KHIỂN THIẾT BỊ QUA INTERNET

8.1 Đặt vấn đề


Ở chương trước đó ta điều khiển thiết bị qua wifi nhưng mục đích
chính của bài toán là điều khiển các thiết bị trong nhà, mạng nội bộ. Ta tận
dụng wifi bởi vì tốc độ truyền nhanh và phạm vi phát rộng. Do đó việc
điều khiển nhiều thiết bị trong nhà qua wifi là một lựa chọn khá hay mà
không cần thiết lập nhiều hạ tầng, vì hiện nay hầu hết các nhà của cư dân
thành phố đều trang bị wifi. Ở đây ta mở rộng ra, việc điều khiển thiết bị
ở khoảng cách xa hơn, bạn có thể điều khiển thiết bị ở bất cứ nơi nào miễn
có kết nối internet. Ý tưởng là ta sẽ viết phần mềm trên điện thoại, phần
mềm có thể kết nối mạng internet (thông qua 3G, wifi,..), từ phần mềm ta
có thể điều khiển được các thiết bị ở khoảng cách xa. Các thiết bị được
điều khiển từ bộ điều khiển. Bộ điều khiển ta vẫn sử dụng Arduino UNO
R3, giao tiếp với module wifi ESP8266 để thực hiện kết nối internet. Có
nhiều cách để kết nối luồng dữ liệu từ ứng dụng đến bộ điều khiển. Ở đây
tôi dùng giải pháp đơn giản là sử dụng web IoT thingspeak.com. Ứng dụng
này được thiết kế đơn giản, nhằm giúp bài toán của chúng ta dễ dàng hơn.
Tuy nhiên, việc điều khiển qua internet thường phát sinh nhiều lỗi, nên
khắc phục tất cả lỗi sẽ rất phức tạp. Vấn đề đó từng trường hợp cụ thể các
bạn nghiên cứu để hoàn thiện hơn, vì điều đó nằm ngoài phạm vi của quyển
sách này.
Thingspeak cho ta tạo miễn phí những trường lưu dữ liệu gọi là Field.
Với ứng dụng này ta sẽ tạo hai field. Một field là nhận tín hiệu điều khiển
từ Android, Arduino sẽ load giá trị cuối cùng của trường điều khiển để
điều khiển thiết bị. Khi điều khiển hoàn tất Arduino sẽ gửi tín hiệu trạng
thái lên field thứ hai. Khi đó android sẽ load field thứ hai và nhận trạng
thái điều khiển từ arduino để cập nhật giao diện điều khiển.
Để đăng ký Thingspeak ta tiến hành các bước sau:
Bước 1: vào trang web http://www.thingspeak.com và đăng ký tài
khoản, tương tự như những trang web khác.

169
Hình 8.1. Giao diện trang web thingspeak

Bước 2: tạo hai trường trạng thái bằng cách vào Chanels-> new
Chanels

Hình 8.2. Giao diện tạo hai field mới trong thingspeak
Bước 3: nhận API key, API key là key riêng cho từng Chanel, mỗi
channel có thể chứa được nhiều Field. Key ở đây là dùng cho thao tác gửi
dữ liệu lên Fields để lưu trữ.

170
Hình 8.3. Nhận API key

Tiếp theo ta xây dựng ứng dụng trên điện thoại Android với giao
diện đơn giản gồm một ImageView để hiển thị trạng thái của thiết bị, một
Switch để chọn trạng thái bật hay tắt của thiết bị, một button để gửi trạng
thái của Switch lên Thingspeak;

Hình 8.4. Giao diện phần mềm điều khiển thiết bị qua Internet

171
Giao diện được thiết kế đơn giản như sau:
<?xml version="1.0" encoding="utf-8"?>
<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:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"

tools:context="com.example.ngb.internet_control.MainActivity"
android:background="#ffffff">

<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/imageView_Device"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:layout_marginTop="104dp"
android:src="@drawable/device1" />

<Switch
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Status"
android:id="@+id/switch_control"
android:layout_below="@+id/imageView_Device"
android:layout_centerHorizontal="true"
android:layout_marginTop="28dp"
android:checked="false" />

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send"
android:id="@+id/button"
android:layout_below="@+id/switch_control"
android:layout_centerHorizontal="true" />
</RelativeLayout>

File Mainifest, tương tự với những ứng dụng cần internet, ta thêm
các dòng cho phép kết nối internet.
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ngb.internet_control">
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"/>
172
<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>

Với những ứng dụng cần Internet ta phải có hai dòng permission
sau:
<uses-permission
android:name="android.permission.INTERNET"/>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE
"/>
Và để kết nối và gửi dữ liệu lên Internet bằng giao thức Http ta cần
một Class HttpRequest. File Httprequest.class như sau:
package com.example.ngb.internet_control;

import android.util.Log;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.json.JSONObject;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;

173
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

public class HttpRequest {

DefaultHttpClient httpClient;
HttpContext localContext;
private String ret;

HttpResponse response = null;


HttpPost httpPost = null;
HttpGet httpGet = null;

public HttpRequest(){
HttpParams myParams = new BasicHttpParams();

HttpConnectionParams.setConnectionTimeout(myParams,
10000);
HttpConnectionParams.setSoTimeout(myParams, 10000);
httpClient = new DefaultHttpClient(myParams);
localContext = new BasicHttpContext();
}

public void clearCookies() {


httpClient.getCookieStore().clear();
}

public void abort() {


try {
if (httpClient != null) {
System.out.println("Abort.");
httpPost.abort();
}
} catch (Exception e) {
System.out.println("Your App Name Here" + e);
}
}

public String sendPost(String url, String data) {


return sendPost(url, data, null);
}

public String sendJSONPost(String url, JSONObject data) {


return sendPost(url, data.toString(),
"application/json");
}

public String sendPost(String url, String data, String


contentType) {
ret = null;

httpClient.getParams().setParameter(ClientPNames.COOKIE_POLICY,
CookiePolicy.RFC_2109);

174
httpPost = new HttpPost(url);
response = null;

StringEntity tmp = null;

Log.d("Your App Name Here", "Setting httpPost


headers");

httpPost.setHeader("User-Agent", "SET YOUR USER AGENT


STRING HERE");
httpPost.setHeader("Accept",
"text/html,application/xml,application/xhtml+xml,text/html;q=0.
9,text/plain;q=0.8,image/png,*;q=0.5");

if (contentType != null) {
httpPost.setHeader("Content-Type", contentType);
} else {
httpPost.setHeader("Content-Type", "application/x-
www-form-urlencoded");
}

try {
tmp = new StringEntity(data,"UTF-8");
} catch (UnsupportedEncodingException e) {
Log.e("Your App Name Here", "HttpUtils :
UnsupportedEncodingException : " + e);
}

httpPost.setEntity(tmp);

Log.d("Your App Name Here", url + "?" + data);

try {
response =
httpClient.execute(httpPost,localContext);

if (response != null) {
ret =response.getStatusLine().toString();
}
} catch (Exception e) {
Log.e("Your App Name Here", "HttpUtils: " + e);
}

Log.d("Your App Name Here", "Returning value:" + ret);

return ret;
}

public String sendGet(String url) {


httpGet = new HttpGet(url);

try {
response = httpClient.execute(httpGet);
} catch (Exception e) {
Log.e("Your App Name Here", e.getMessage());
}

175
//int status = response.getStatusLine().getStatusCode();

// we assume that the response body contains the error message


try {
ret = EntityUtils.toString(response.getEntity());
} catch (IOException e) {
Log.e("Your App Name Here", e.getMessage());
}

return ret;
}

public InputStream getHttpStream(String urlString) throws


IOException {
InputStream in = null;
int response = -1;

URL url = new URL(urlString);


URLConnection conn = url.openConnection();

if (!(conn instanceof HttpURLConnection))


throw new IOException("Not an HTTP connection");

try{
HttpURLConnection httpConn = (HttpURLConnection)
conn;
httpConn.setAllowUserInteraction(false);
httpConn.setInstanceFollowRedirects(true);
httpConn.setRequestMethod("GET");
httpConn.connect();

response = httpConn.getResponseCode();

if (response == HttpURLConnection.HTTP_OK) {
in = httpConn.getInputStream();
}
} catch (Exception e) {
throw new IOException("Error connecting");
} // end try-catch

return in;
}
}

Để sử dụng được Class này các bạn phải sửa trong file
Android/Gradle Scripts/Build.gradle như sau:

176
apply plugin: 'com.android.application'

android {

compileSdkVersion 22

buildToolsVersion "22.0.0"

defaultConfig {

applicationId "com.example.ngb.google_form"

minSdkVersion 16

targetSdkVersion 22

versionCode 1

versionName "1.0"

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile('proguard-
android.txt'), 'proguard-rules.pro'

dependencies {

compile fileTree(include: ['*.jar'], dir: 'libs')

compile 'com.android.support:appcompat-v7:22.1.1'

compile 'com.squareup.okhttp:okhttp:2.0.0'

177
Tiến hành coding cho file MainActivity như sau:
package com.example.ngb.internet_control;

import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Switch;

public class MainActivity extends AppCompatActivity {


ImageView device;
Switch control;
int data1 = 0;
Button btn_control;
NetworkInfo networkInfo;
ConnectivityManager connMgr;
HttpRequest mReq = new HttpRequest();
LoadThingSpeak mLoadThingSpeak;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
networkInfo = connMgr.getActiveNetworkInfo();
setContentView(R.layout.activity_main);
device = (ImageView)
findViewById(R.id.imageView_Device);
control = (Switch) findViewById(R.id.switch_control);
btn_control=(Button)findViewById(R.id.button);
btn_control.setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
if (control.isChecked()) {
data1 = 1;
} else {
data1 = 0;
}
Thread t = new Thread(new Runnable() {
@Override
public void run() {
postdata();
}
});
t.start();
}
});
}

178
public void postdata() {
String post =
mReq.sendGet("http://api.thingspeak.com/update?key=U2HE231VIW3Q
AM5P&field1=" + data1);
}

private class LoadThingSpeak extends AsyncTask<String,


String, String> {
boolean isConnected = true;
long SLEEPTIME = 5000;//5s
long OPERATIONPERIOD = 10000;//10s
@Override
protected void onPreExecute() {
Log.d("TAG", "Bat dau LoadThingSpeak");
super.onPreExecute();
}
@Override
protected String doInBackground(String... args) {
Log.d("TAG", "doInBackground");
while ((!isCancelled()) &&
(networkInfo.isConnected())) {
SystemClock.sleep(SLEEPTIME);
String status_field1 = null;
status_field1 =
mReq.sendGet("http://api.thingspeak.com/channels/101557/fields/
2/last");
Log.d("ESP",status_field1);
publishProgress(status_field1);
}
return null;
}
@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
if (values[0] != "-1") {
if (values[0].equals("1")) {

device.setImageResource(R.drawable.device2);
}
if (values[0].equals("0")){

device.setImageResource(R.drawable.device1);
}
}
}
}

protected void onPause() {


super.onPause();
Log.d("TAG", "onPause Activity_sensor");
if(!mLoadThingSpeak.isCancelled()) {
mLoadThingSpeak.cancel(true);
}
}
@Override

179
protected void onResume() {
super.onResume();Log.d("TAG", "onResume
Activity_sensor");
mLoadThingSpeak= new LoadThingSpeak();
mLoadThingSpeak.execute();
}
}

Để hiểu rõ chương trình ta cần quan tâm một số vấn đề sau. Trước
tiên, tôi xin nói về việc gửi dữ liệu điều khiển từ android lên Thingspeak.
Dòng
“http://api.thingspeak.com/update?key=U2HE23xxxxxxxxxP&f
ield1=” là cú pháp để Thingspeak nhận dữ liệu mà ta gửi lên. Với
U2HE23xxxxxxxxxP là key mà ta nhận từ Thingspeak lúc khởi tạo các
channel. Để gửi cú pháp này bằng android ta dùng hàm GET được hỗ trợ
trong lớp Httprequest. Nên ta khai báo:
String post =
mReq.sendGet("http://api.thingspeak.com/update?key=U2HE231VI
W3QAM5P&field1=" + data1);
Việc gửi điều khiển thì chỉ cần đặt hàm này vào một Thead để tạo
một luồng mới chạy độc lập là xong.
Tiếp theo xin nói về việc nhận trạng thái từ Thingspeak để thay đổi
giao diện. Việc này phải được thực hiện một cách liên tục, cập nhật đến
đâu thì update giao diện đến đó. Vì vậy ta phải dùng một kỹ thuật là
AsyncTask (đồng bộ công việc). Khi tạo Asynctask sẽ có những hàm sau:
- onPreExecute() : khi Asynctask được kích hoạt nó sẽ chạy đầu
tiên, thông thường không phải làm gì trên nó.
- doInBackground(): đây là nơi ta cần thực hiện những hàm để làm
nhiệm vụ chính, nó chạy nền và trả kết quả liên tục vào hàm
onProgressUpdate để cập nhật giao diện.
- onProgressUpdate (): Dùng để cập nhật giao diện lúc runtime.
- onPostExecute(): Sau khi tiến trình kết thúc thì hàm này sẽ tự
động xảy ra. Ta có thể lấy được kết quả trả về sau khi thực hiện
tiến trình kết thúc ở đây.
Trong AsyncTask<Params, Progress, Result> có 3 đối số là các
Generic Type:
- Params: Là giá trị (biến) truyền vào doInBackground khi
chương trình được thực thi.
180
- Progress: Là giá trị (biến) được truyền vào
hàm onProgressUpdate để update giao diện.
- Result: Là biến dùng để lưu trữ kết quả trả về sau khi tiến trình
thực hiện xong.
Đây là nội dung bên trong DoInBackground
while ((!isCancelled()) && (networkInfo.isConnected())) {
SystemClock.sleep(SLEEPTIME);
String status_field1 = null;
status_field1 =
mReq.sendGet("http://api.thingspeak.com/channels/101557/fields/2/l
ast");
Log.d("ESP",status_field1);
publishProgress(status_field1);
}
Ta tạo một String status_fields để lấy nội dung từ Fields phản hồi
qua hàm GET của lớp Httprequest. Ở đây đặt tên nó là status_feild1. Sau
đó truyền hàm này đi qua hàm onProgressUpdate bằng hàm
publishProgress(status_field1)
Dòng lệnh while ((!isCancelled()) &&
(networkInfo.isConnected())), có nghĩa là ta đưa ra điều kiện là khi
AsyncTask không bị hủy và có kết nối mạng thì cứ quét lên Thingspeak
liên tục, với thời gian nghỉ trong SLEEPTIME.
Xét tiếp hàm onProgressUpdate:
if (values[0] != "-1") {
if (values[0].equals("1")) {
device.setImageResource(R.drawable.device2);
}
if (values[0].equals("0")){
device.setImageResource(R.drawable.device1);
}
khi nhận được nội dung từ DoInbackground() thì lưu vào biến value.
Ta kiểm tra nếu value khác -1 tức là có giá trị trả về từ Thingspeak. Còn nếu
value=-1 thì có nghĩa là Thingspeak không có giá trị.
Bộ điều khiển ta vẫn dùng Module Arduino Uno R3, kết nối với
module wifi ESP8266 và đối tượng điều khiển được giả lập là một led đơn.
Chú ý vấn đề cấp nguồn cho module ESP8266 như trình bày ở chương
trước và thêm điện trở hạ dòng cần thiết cho led.
181
Hình 8.5. Bộ điều khiển led qua internet sử dụng module wifi
ESP8266
Phần code cho Arduino như sau:
#include <SoftwareSerial.h>
boolean debug;
SoftwareSerial esp8266(10, 11);
#define TARGET_IP "184.106.153.149"

#define TARGET_PORT "80"

#define ID "pog11"
#define PASS "12355678"
String response = "";
String webpage1 = "";
int bat = 0, tat = 0;
void setup()
{
Serial.begin(9600);
esp8266.begin(38400);
debug = false;

182
pinMode(2, OUTPUT);
while (!sendData("AT+RST\r\n", 2000, "OK")) {}
while (!sendData("AT+CWMODE=1\r\n", 1000, "OK")) {}

String cmd = "AT+CWJAP=\"";


cmd += ID;
cmd += "\",\"";
cmd += PASS;
cmd += "\"";
while (!sendData( cmd + "\r\n", 3000, "OK")) {}
while (!sendData("AT+CIPMUX=1\r\n", 1000, "OK")) {}
}
void post(String data)
{
String webpage = "AT+CIPSTART=\"TCP\",\"";
webpage += TARGET_IP;
webpage += "\",80\r\n";
while (!sendData(webpage, 3000, "Linked")) {}
webpage1 = data + "\r\n";
String cipsend = "AT+CIPSEND=";
cipsend += webpage1.length();
cipsend += "\r\n";
while (!sendData(cipsend, 1000, ">")) {}
while (!sendData(webpage1, 15000, "Unlink")) {}
while (!sendData("AT+CIPCLOSE=0\r\n", 1500, "OK")) {}
}
void loop()
{
delay(1000);
post("GET
http://api.thingspeak.com/channels/101557/fields/1/last")
;
if (response.indexOf("+IPD,1:1") != NULL) {
bat++;
183
tat = 0;
digitalWrite(2, HIGH);
}
if (bat == 1)
{
post("GET
http://api.thingspeak.com/update?key=U2HE231VIW3QAM5P&fie
ld2=1");
}
if (response.indexOf("+IPD,1:0") != NULL) {
tat++;
bat = 0;
digitalWrite(2, LOW);
}
if (tat == 1)
{
post("GET
http://api.thingspeak.com/update?key=U2HE231VIW3QAM5P&fie
ld2=0");
}
}

boolean sendData(String command, const unsigned long


timeout, String answer)
{
boolean DEBUG;
esp8266.print(command);
unsigned long time1 = millis();

while ( (time1 + timeout) > millis())


{
while (esp8266.available())
{
char c = esp8266.read();
response += c;
184
}
}
Serial.println(response);
if (response.indexOf(answer) != NULL) {
return true;
}
return false;
}

Nội dung Arduino cũng như Android, nó sẽ load giá trị điều khiển
từ Fields1 về, điều khiển xong up lại Fields2. Với những lệnh AT thiết lập
ban đầu cơ bản như ModuleSIM900. Chú ý ở hàm gửi lệnh AT kết nối với
Internet có 3 bước sau:
- Kết nối tới trang web: AT+CIPSTART.
- Yêu cầu gửi dữ liệu lên web: AT+CIPSEND
- Gửi dữ liệu khi Esp trả về “>”. Sau khi gửi xong thì ngắt kết nối
AT+CIPCLOSE
Khi nhận được +IPD,1:0 thì 0 chính là giá trị điều khiển cần lấy ở
đây, ta quy ước là lệnh tắt thiết bị, tương tự lệnh bật +IPD,1:1.

185
TỔNG KẾT

Như vậy là chúng ta đã cơ bản đi qua hầu hết các vấn đề về điều
khiển thiết bị thông qua các ứng dụng Android và sử dụng kit vi điều khiển
Arduino UNO R3 làm bộ điều khiển. Tôi chắc rằng đến đây các bạn đã
hiểu được khá nhiều về vấn đề điều khiển thiết bị thông qua điện thoại di
động. Tất cả các chương, các ứng dụng tôi đều đơn giản hóa ở mức độ đọc
hiểu và có thể “vọc” ngay.
Để làm ra một hệ thống hoàn chỉnh, chạy ổn định, có thể thương mại
được đó lại là một vấn đề cần các bạn tiếp tục nghiên cứu và phát triển.
Nhưng với những gì quyển sách này mang lại, tôi hi vọng ngọn lửa đam
mê sáng tạo, đam mê nghiên cứu và biến các ước mơ về khoa học kỹ thuật
thành hiện thực luôn bùng cháy trong các bạn.
Lãnh vực IoT (Internet of Things) đã và đang trở thành một xu hướng
của thời đại, chúc các bạn có những thành công nhất định trong vùng đất
đầy mầu mỡ này. Cám ơn các bạn đã đọc quyển sách.
Trân trọng.
Tác giả
Ths. Nguyễn Văn Hiệp

186
TÀI LIỆU THAM KHẢO

[1] Trang hỗ trợ các nhà lập trình Android http://developer.android.com


[2] Trang học trực tuyến http://tutorialspoint.com/
[3] Diễn đàn lập trình Android http://stackoverflow.com/questions
[4] Cộng đồng Arduino Việt Nam http://arduino.vn/

187

You might also like