You are on page 1of 202

Lập trình Android và ứng dụng trong các hệ thống IoTs

( Android trong điều khiển, IoTs, WearOS)

Ths. Nguyễn Văn Hiệp – Ts. Trần Đức Thiện – Ts. Trần Mạnh Sơn

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


LỜI GIỚI THIỆU

Những năm 2000, điện thoại thông minh (smartphone) là một khái niệm rất xa xỉ. Người giàu thì
cũng chỉ có thể “sang chảnh” với các điện thoại “đập đá” đúng nghĩa. Nhưng chúng ta đã thật sự
chứng kiến sự thay đổi như vũ bão của thế giới smartphone. Rất nhiều hãng điện thoại mới nổi,
thành công mang đến cho người dùng sự tiếp cận dễ dàng hơn bao giờ hết. Điện thoại thông minh
giờ đã thông minh hơn, với sự tích hợp của AI (trí tuệ nhân tạo) người dùng như có thêm trợ lý
đắc lực hiểu mình hơn nữa. Smartphone ngày càng đa dạng, trãi dài trên các phân khúc từ cao cấp
đến bình dân. Giới trẻ hầu như ai cũng có smartphone, mọi thứ bắt đầu liên kết hầu hết với nhau
qua internet. Thế giới trở nên phẳng hơn, mọi tương tác liên quan đến đời sống tinh thần, vật chất
được số hóa và chia sẻ.
Với sự thống trị hiện tại của Android trong thế giới smartphone, cuộc cách mạng công nghiệp 4.0
đang tiến đến gõ cửa từng nhà, quyển sách mong muốn mang đến cho người đọc những nền tảng
cơ bản, cốt lốt của việc lập trình di động ứng dụng điều khiển, giám sát thiết bị.
Quyển sách này được viết dựa trên hai quyển Lập trình Android cơ bản, Lập trình Android trong
ứng dụng điều khiển của cùng tác giả, NXB Đại học Quốc Gia HCM. Chính vì vậy, một số khái
niệm cơ bản đã được bỏ bớt, hướng đến phần ứng dụng thực tế, các hệ thống giám sát điều khiển
thông qua thiết bị Android được cải tiến, chỉnh sửa, bổ sung.
Tác giả hi vọng sau khi đọc quyển sách này, người đọc có thể tự xây dựng một số hệ thống điều
khiển thông minh cơ bản phù hợp với nhu cầu cụ thể. Hoặc đơn giản là trang bị những kiến thức
để việc trải nghiệm các thiết bị smarthome (nhà thông minh) dễ dàng và thuận tiện hơn!
Chân thành cảm ơn sự ủng hộ của quý độc giả, mọi góp ý về nội dung vui lòng gởi về email:
hiepspkt@hmcute.edu.vn.
Thân ái!

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


MỤC LỤC
Lời giới thiệu ....................................................................................................................... 1
Chương 1 Định nghĩa vỀ Android ................................................................................... 9
Chương 2 Thiết kế Layout trong Android Studio .......................................................... 13
2.1 Đặt vấn đề ............................................................................................................ 13
2.2 Các loại Layout tiêu chuẩn trong Android:.......................................................... 13
2.3 Relative Layout: ................................................................................................... 14
2.4 Linear Layout: ...................................................................................................... 18
2.5 Table Layout: ....................................................................................................... 24
2.6 Grid Layout: ......................................................................................................... 26
2.7 Frame Layout: ...................................................................................................... 26
2.8 Constraint Layout:................................................................................................ 28
Chương 3 Các điều khiển giao diện người dùng ............................................................ 33
3.1 Đặt vấn đề ............................................................................................................ 33
3.2 Các đối tượng UI cơ bản và thuộc tính quan trọng:............................................. 33
3.2.1 Các thuộc tính quan trọng của một UI: ......................................................... 34
3.2.2 Các đối tượng UI cơ bản: .............................................................................. 37
3.3 Bài tập về UI Control: .......................................................................................... 38
Chương 4 Xây dựng ứng dụng IOTs sử dụng Firebase ................................................. 47
4.1 Đặt vấn đề ............................................................................................................ 47
4.2 Định nghĩa Firebase ............................................................................................. 47
4.2.1 Content Delivery Network ............................................................................ 49
4.2.2 Dữ liệu thời gian thực - Firebase Realtime Database ................................... 49
4.2.3 Cách thức hoạt động của cơ sở dữ liệu thời gian thực .................................. 50
4.3 Xây dựng một dự án đơn giản sử dụng Google Firebase .................................... 51
4.3.1 Bài toán 1: ..................................................................................................... 51
4.3.2 Bài toán 2: ..................................................................................................... 61
Chương 5 Điều Khiển Thiết Bị Qua Tin Nhắn Sms ...................................................... 73
5.1 Đặt vấn đề ............................................................................................................ 73
5.2 Phần mềm trên điện thoại Android ...................................................................... 74

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


5.2.1 Xây dựng giao diện bằng phần mềm SMS_Control ..................................... 74
5.2.2 Chương trình trên hoạt động như thế nào? ................................................... 79
5.3 Bộ điều khiển ....................................................................................................... 80
5.4 Kết nối phần cứng và chương trình Arduino ....................................................... 82
Chương 6 Sử dụng Google Maps trong ứng dụng giám sát, định vị ............................. 89
6.1 GPS là gì ?............................................................................................................ 89
6.1.1 Cách thức hoạt động của vệ tinh ................................................................... 89
6.1.2 Cách xác định vị trí theo kinh độ và vĩ độ .................................................... 90
6.2 Google Maps ........................................................................................................ 91
6.2.1 Xây dựng ứng dụng đơn giản giám sát thiết bị dùng Google Maps ............. 92
6.2.2 Viết chương trình cho ứng dụng xác định vị trí bằng Google maps ............. 93
6.2.3 Chương trình trên hoạt động như thế nào? ................................................. 102
6.3 Viết chương trình cho thiết bị nhận tin nhắn yêu cầu và gửi lại vị trí ............... 108
6.3.1 Thiết kế giao diện chính .............................................................................. 108
6.3.2 Yêu cầu thiết bị cấp quyền cho ứng dụng ................................................... 109
6.3.3 Phần code chính trong MainsActivity.java ................................................. 109
6.4 Mô phỏng ........................................................................................................... 113
Chương 7 ĐIỀU KHIỂN VÀ GIÁM SÁT THIẾT BỊ QUA BLUETOOTH .............. 116
7.1 Đặt vấn đề .......................................................................................................... 116
7.2 Phần mềm trên điện thoại Android .................................................................... 117
7.2.1 Xây dựng ứng dụng đơn giản ...................................................................... 117
7.2.2 Chương trình trên hoạt động như thế nào? ................................................. 123
7.3 Bộ điều khiển ..................................................................................................... 126
Chương 8 Ứng Dụng Công Nghệ Nhận Dạng Ký Tự Quang Học Trong Điều Khiển 131
8.1 Đặt vấn đề .......................................................................................................... 131
8.2 Định nghĩa về Công nghệ .................................................................................. 131
8.3 Ứng dụng công nghệ OCR điều khiển led 7 màu .............................................. 132
8.3.1 yêu cầu......................................................................................................... 132
8.3.2 Sơ đồ hệ thống ............................................................................................. 133
8.3.3 Thiết kế ứng dụng trên Android .................................................................. 133

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


8.3.4 Thiết kế bộ điều khiển ................................................................................. 142
Chương 9 Ứng dụng cảm biến gia tốc trong điều khiển ............................................. 146
9.1 Đặt vấn đề .......................................................................................................... 146
9.2 Phần mềm trên điện thoại Android .................................................................... 147
9.3 Bộ điều khiển ..................................................................................................... 156
Chương 10 Ứng dụng công nghệ nhận dạng giọng nói .............................................. 158
10.1 Đặt vấn đề ....................................................................................................... 158
10.2 Ứng dụng trên điện thoại Android .................................................................. 158
10.3 Bộ điều khiển .................................................................................................. 168
Chương 11 Sử Dụng Blynk Tạo Ứng Dụng Điều Khiển Với Google Assistant ........ 170
11.1 Đặt vấn đề ....................................................................................................... 170
11.2 Xây dựng ứng dụng với BLYNK ................................................................... 171
11.2.1 Giới thiệu về App Blynk ............................................................................. 171
11.2.2 Tạo mới dự án trên Blynk ........................................................................... 171
11.3 Đăng nhập và cài đặt trên IFTTT ................................................................... 176
11.4 Bộ điều khiển .................................................................................................. 184
Chương 12 Wear OS Và Ứng Dụng Điều Khiển ....................................................... 186
12.1 Đặt vấn đề ....................................................................................................... 186
12.2 Tạo một Database Firebase............................................................................. 187
12.3 Tạo một ứng dụng trên smartphone ................................................................ 191
12.4 Tạo một ứng dụng trên smartwatch ................................................................ 193
12.5 Mạch điều khiển Esp8266 đọc dữ liệu từ Firebase ........................................ 197

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


Hình ảnh
Hình 1.1 Chiếc điều thoại T-Mobile G1 .............................................................................. 9
Hình 1.2 Các phiên bản hệ điều hành Android và thời gian phát hành ............................. 10
Hình 2.1 Sơ đồ thừa kế giữa các thành phần giao diện trong Android ............................. 13
Hình 2.2 Thiết kế một giao diện sử dụng RelativeLayout ................................................ 15
Hình 2.3 Màn hình ứng dụng khi chạy máy ảo ................................................................. 17
Hình 2.4 Hình ảnh minh hoạ cho margin và padding ....................................................... 18
Hình 2.5 Kết quả màn hình sau thiết kế ............................................................................ 20
Hình 2.6 Kết quả hiển thị trên màn hình khi thiết kế LinearLayout dọc........................... 21
Hình 2.7 Thiết kế các Layout lồng vào nhau..................................................................... 23
Hình 2.8 Kết quả hiển thị trên máy ảo các Layout lồng vào nhau .................................... 24
Hình 2.9 Thiết kế màn hình đăng nhập dùng TableLayout ............................................... 24
Hình 2.10 Kết quả mô phỏng xây dựng màn hình đăng nhập dùng TableLayout ............ 26
Hình 2.11 Thiết kế các nút nhấn sử dụng FrameLayout. .................................................. 27
Hình 2.12 Sử dụng các Layout đã trình bày thiết kế giao diện máy tính đơn giản ........... 28
Hình 2.13 Thiết kế giao diện ứng dụng sử dụng ConstraintLayout .................................. 29
Hình 2.14 Kết quả màn hình ứng dụng khi chạy máy ảo .................................................. 31
Hình 3.1 Các sắp xếp các view, Viewgroup ...................................................................... 33
Hình 3.2 Chọn kích thước màn hình thiết kế trong Android Studio ................................. 34
Hình 3.3 Độ phân giải pixel trong Androidd ..................................................................... 35
Hình 3.4 Kích thước tính theo đơn vị dp ........................................................................... 35
Hình 3.5 Kích thước dp trong Android ............................................................................. 36
Hình 3.6 Ví dụ về kích thước sp ........................................................................................ 36
Hình 3.7 Biểu diễn của các đơn vị đo trên cùng một màn hình Nguồn Androidvn.org... 37
Hình 3.8 Giao diện ứng dụng tính chỉ số BMI .................................................................. 39
Hình 3.9 Giao diện thiết kế ứng dụng BMI ....................................................................... 42
Hình 3.10 Kết quả chạy trên máy tính ảo .......................................................................... 46
Hình 4.1 Firebase hỗ trợ nhiều nền tảng ........................................................................... 47
Hình 4.2 Phần cứng kết nối sử dụng NODMCU8266....................................................... 51
Hình 4.3 Vào mục setting .................................................................................................. 53
Hình 4.4 Vào mục Android SDK ...................................................................................... 54
Hình 4.5 Vào SDK Tools .................................................................................................. 54
Hình 4.6 Vào mục Tools\ Firebase .................................................................................... 55
Hình 4.7 Vào mục Realtime Database .............................................................................. 55
Hình 4.8 Vào mục Save and retrieve data ......................................................................... 56
Hình 4.9 Kết quả Database thêm một Child tên là LED ................................................... 56

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


Hình 4.10 Code trong mục Rules ...................................................................................... 57
Hình 4.11 Thông báo tại cửa sổ Firebase .......................................................................... 57
Hình 4.12 Kết quả chạy trên máy ảo a) khi Led sáng b) khi Led tắt................................. 60
Hình 4.13 Sơ đồ chân module NodeMCU 8266 ............................................................... 62
Hình 4.14 Ảnh thực tế DHT11 .......................................................................................... 63
Hình 4.15 Kết nối NodeMCU 8266 với DHT11 a) sơ đồ kết nối; b) kết nối trên hình thực
tế ........................................................................................................................................ 63
Hình 4.16 Phần cứng kết nối cơ bản cho hệ thống ............................................................ 64
Hình 4.17 Xây dựng ứng dụng trên Android Studio ......................................................... 64
Hình 4.18 Tạo một dự án Firebase .................................................................................... 67
Hình 4.19 Một dự án Firebase lưu trữ dữ liệu thời gian thực ........................................... 67
Hình 4.20 Hoạt động của Realtime Data Firebase ............................................................ 70
Hình 4.21 Lấy firebase_host .............................................................................................. 72
Hình 5.1 Hệ thống điều khiển thiết bị qua SMS................................................................ 73
Hình 5.2 Giao diện ứng dụng điều khiển SMS_Control ................................................... 74
Hình 5.3 SIM900 ............................................................................................................... 81
Hình 5.4 Board TDGGSM_900 ........................................................................................ 81
Hình 5.5 Board GPRS Shield-EFCom .............................................................................. 82
Hình 5.6 Sơ đồ kết nối phần cứng ..................................................................................... 83
Hình 5.7 Giao tiếp SIM900 qua hai chân khác chân Tx, Rx mặc định ............................. 83
Hình 6.1 Hệ thống định vị toàn cầu GPS .......................................................................... 90
Hình 6.2 Xác định vị trí bằng công thức lượng giác ......................................................... 90
Hình 6.3 Hình biểu tượng của ứng dụng Google Maps mới cập nhật .............................. 91
Hình 6.4 Giao diện Google Maps trên điện thoại.............................................................. 91
Hình 6.5 Theo dõi định vị thiết bị từ một thiết bị khác ..................................................... 92
Hình 6.6 Tạo một Projet mới ............................................................................................. 93
Hình 6.7 Các bước tiến hành xin API key ......................................................................... 94
Hình 6.8 Tạo 1 API key cho ứng dụng .............................................................................. 94
Hình 6.9 File Google_maps_api.xml................................................................................. 95
Hình 6.10 Vào trang web xin API key .............................................................................. 95
Hình 6.11 Tạo một API Key .............................................................................................. 96
Hình 6.12 API key sau khi đã tạo ...................................................................................... 96
Hình 6.13 Điền API key vào file .xml ............................................................................... 96
Hình 6.14 Thêm dịch vụ của Google vào chương trình .................................................... 97
Hình 6.15 Giao diện chính của ứng dụng xác định vị trí .................................................. 97
Hình 6.16 Giao diện chính của ứng dựng thông báo vị trí .............................................. 109
Hình 6.17 Các yêu cầu quyền cơ bản cho ứng dụng ....................................................... 109

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


Hình 6.18 Giao diện chính hiện thị vị trí ......................................................................... 114
Hình 6.19 Tin nhắn yêu cầu gửi tới và phản hồi bằng tin nhắn vị trí.............................. 114
Hình 6.20 Giao diện chính của ứng dụng theo dõi vị trí ................................................. 115
Hình 6.21 Giá trị được biểu thị và vị trí thiết bị được hiển thị trên bản đồ ..................... 115
Hình 7.1 Sơ đồ điều khiển thiết bị bằng Bluetooth ......................................................... 116
Hình 7.2 Giao diện điều khiển của ứng dụng .................................................................. 117
Hình 7.3 Giao diện khi đèn được bật ............................................................................... 118
Hình 7.4 Module Bluetooth HC05/06 ............................................................................. 126
Hình 7.5 Sơ đồ kết nối Ardunio Uno R3 với HC-05 ....................................................... 128
Hình 9.1 Cảm biến gia tốc trong điện thoại..................................................................... 146
Hình 9.2 Giao diện của ứng dụng .................................................................................... 147
Hình 9.3 Giao diện điều khiển ......................................................................................... 147
Hình 9.4 Sơ đồ kết nối phần cứng ................................................................................... 156
Hình 10.1 Giao diện ứng dụng Android .......................................................................... 159
Hình 10.2 Kết nối Ardunio với HC05 và ba led đơn ...................................................... 168
Hình 11.1 Sơ đồ hệ thống điều khiển với Google Assistant ........................................... 170
Hình 11.2 Giao diện đăng nhập Blynk ............................................................................ 172
Hình 11.3 Tạo một ứng dụng mới với Blynk .................................................................. 172
Hình 11.4 Chọn phần cứng cho dự án Blynk .................................................................. 173
Hình 11.5 Thông báo gởi mã Auth Token đến tài khoản liên kết ................................... 173
Hình 11.6 Hộp widget của Blynk .................................................................................... 174
Hình 11.7 Giao diện xây dựng các Button điều khiển .................................................... 174
Hình 11.8 Thiết lập các thuộc tính cho nút Button trong Blynk ..................................... 175
Hình 11.9 Thiết chân kết nối cho nút nhấn ..................................................................... 176
Hình 11.10 Khởi chạy Blynk ........................................................................................... 176
Hình 11.11 Tạo một applet trên IFTTT ........................................................................... 177
Hình 11.12 Tạo điều kiện This ........................................................................................ 178
Hình 11.13 Liên kết Google Assistant với IFTTT .......................................................... 178
Hình 11.14 Chọn loại trigger cho IFTTT ........................................................................ 179
Hình 11.15 Thiết lập các câu lệnh trigger cho sự kiện This ............................................ 180
Hình 11.16 Thiết lập sự kiện That ................................................................................... 180
Hình 11.17 Chọn dịch vụ Webhooks cho sự kiện That ................................................... 181
Hình 11.18 Chọn hành động cho webhooks .................................................................... 181
Hình 11.19 Thiết lập các thuộc tính cho hành động (action) .......................................... 182
Hình 11.20 Hoàn thành thiết lập IFTTT .......................................................................... 182
Hình 11.21 Thiết lập cho điều khiển tắt Led 1 ................................................................ 183
Hình 11.22 Kết quả sau khi hoàn thành thiết lập cho hai led .......................................... 183

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


Hình 11.23 Phần cứng kết nối NodeMCU ...................................................................... 184
Hình 12.1 Giao diện của ứng dụng Google Home .......................................................... 187
Hình 12.2 Tạo dự án Firebase mới .................................................................................. 188
Hình 12.3 Chép file .json và project Android ................................................................. 188
Hình 12.4 Hướng dẫn tích hợp Firebase vào Android project ........................................ 189
Hình 12.5 Chọn dự án Android để tích hợp Firebase ...................................................... 189
Hình 12.6 Thêm Package Name để Add dự án vào firebase ........................................... 190
Hình 12.7 Thiết lập cho phép đọc ghi dữ liệu lên DataFirebase ..................................... 190
Hình 12.8 Tạo dữ liệu cho DataFirebase ......................................................................... 191
Hình 12.9 Thêm các thư viện Firebase vào Gradle ......................................................... 191
Hình 12.10 Thêm thư viện vào Build.Gradle(app).......................................................... 192
Hình 12.11 Thiết kế giao diện cho ứng dụng trên điện thoại .......................................... 192
Hình 12.12 Xây dựng code cho file MainActivity.java .................................................. 193
Hình 12.13 Kết quả quan sát Firebase khi chạy ứng dụng .............................................. 193
Hình 12.14 Tạo ứng dụng cho đồng hồ thông minh........................................................ 194
Hình 12.15 Thiết lập ứng dụng Wear OS mới ................................................................ 194
Hình 12.16 Giao diện xây dựng ứng dụng Wear OS ...................................................... 195
Hình 12.17 Xem địa chỉ IP của đồng hồ thông minh ...................................................... 196
Hình 12.18 Vào cửa sổ command ‘Android Debugging bridge” .................................... 196
Hình 12.19 Dòng lệnh để kết nối đồng hồ thông minh với máy tính .............................. 196
Hình 12.20 Debugging thông qua wifi giữa máy tính và đồng hồ .................................. 197
Hình 12.21 Modul thu phát wifi ESP8266 NodeMCU Mini D1..................................... 197
Hình 12.22 Hướng dẫn lấy Authcode của dự án Firebase ............................................... 199

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


CHƯƠNG 1 DỊNH NGHĨA VỀ ANDROID
Android là hệ điều hành di động phổ biến nhất trên thế giới, cung cấp hàng tỷ thiết bị từ
điện thoại đến đồng hồ, máy tính bảng, TV và hơn thế nữa. Cho đến nay sự phủ sóng của
hệ điều hành Android rộng đến mức không ai là không biết đến nó. Đặc biệt, các thiết bị
di động từ giá rẻ đến cao cấp, các hãng khác nhau đều trang bị hệ điều hành Android cho
thiết bị của mình.
Ban đầu, Android được phát triển bởi Tổng công ty Android, với sự hỗ trợ tài chính từ
Google và sau này công ty Android được chính Google mua lại vào năm 2005. Android ra
mắt vào năm 2007 cùng với tuyên bố thành lập Liên minh thiết bị cầm tay mở (Open
Handset Alliance, viết tắt OHA), một hiệp hội gồm các công ty phần cứng, phần mềm, và
viễn thông với mục tiêu đẩy mạnh các tiêu chuẩn mở cho các thiết bị di động. Chiếc điện
thoại đầu tiên chạy Android được bán vào năm 2008, chính là T-Mobile G1 do HTC sản
xuất như trong hình 1.1.

Hình 1.1 Chiếc điều thoại T-Mobile G1

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


Android có mã nguồn mở được Google phát hành theo Giấy phép Apache. Giấy phép
Apache là một giấy phép phần mềm tự do của Quỹ Phần mềm Apache (Apache Software
Foundation - ASF). Giấy phép Apache bắt buộc phải có thông báo bản quyền và lời phủ
nhận. Tương tự như các giấy phép phần mềm tự do khác, Giấy phép Apache trao cho người
dùng phần mềm quyền tự do sử dụng phần mềm với bất kỳ mục đích nào như phân phối,
chỉnh sửa, và phân phối bản có sửa đổi của phần mềm, theo các điều khoản của giấy phép,
mà không lo lắng tới phí bản quyền. Chính mã nguồn mở cùng với một giấy phép không
có nhiều ràng buộc đã cho phép các nhà phát triển thiết bị, mạng di động và các lập trình
viên tự do điều chỉnh và phân phối Android. Ngoài ra, Android còn có một cộng đồng lập
trình viên đông đảo chuyên viết các ứng dụng để mở rộng chức năng của thiết bị bằng một
loại ngôn ngữ lập trình Java có sửa đổi.

Hình 1.2 Các phiên bản hệ điều hành Android và thời gian phát hành

Những yếu tố này đã giúp Android trở thành nền tảng điện thoại thông minh phổ biến nhất
thế giới, vượt qua Symbian OS vào quý 4 năm 2010, và được các công ty công nghệ lựa
chọn khi họ cần một hệ điều hành không nặng nề, có khả năng tinh chỉnh, và giá rẻ chạy
trên các thiết bị công nghệ cao thay vì xây dựng một hệ điều hành cho riêng mình. Do đó
Android đã được thiết kế để không chỉ chạy trên điện thoại và máy tính bảng mà còn xuất
hiện trên Tivi, máy chơi game và các thiết bị điện tử khác. Bản chất mở của Android cũng

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


khích lệ một đội ngũ đông đảo lập trình viên và những người đam mê sử dụng mã nguồn
mở để tạo ra những dự án do cộng đồng quản lý. Những dự án này bổ sung các tính năng
cao cấp cho những người dùng thích tìm tòi hoặc đưa Android vào các thiết bị ban đầu
chạy hệ điều hành khác.
Android chiếm 87,7% thị phần điện thoại thông minh trên toàn thế giới vào thời điểm quý
2 năm 2017, với tổng cộng 2 tỷ thiết bị đã được kích hoạt và 1,3 triệu lượt kích hoạt mỗi
ngày. Sự thành công của hệ điều hành cũng khiến nó trở thành mục tiêu trong các vụ kiện
liên quan đến bằng phát minh, góp mặt trong cái gọi là "cuộc chiến điện thoại thông minh"
giữa các công ty công nghệ. Điển hình có một thời gian dài SamSung và Apple liên tục
kiện tục các bên vi phạm bản quyền của nhau.1
Hệ điều hành Android liên tục được cập nhật, và đến nay nó trải qua nhiều phiên bản khác
nhau và sẽ còn tiếp tục. Hình đồ họa ghi lại các cột mốc phát triển chính của hệ điều hành
này được trình bày trong hình 1.2.
Một số nền tảng Android:

Android không chỉ được thiết kế để chạy trên điện thoại, máy tính bảng (tablet), thiết bị di
động có kích thước lớn (phable). Android chạy trên các thiết bị có đủ hình dạng và kích
thước, mang đến cho bạn cơ hội lớn để tiếp tục tương tác với người dùng. Và Android được
thiết kế chuyên dùng và hướng đến các đối tượng khác nhau.

Android TV là hệ điều hành được tùy biến xây dựng chạy trên các tivi thông minh. Thị
trường tivi thông minh giờ rất sôi động. Nội dung xem trực tiếp phong phú, chất lượng cao.
Chính vì vậy Android TV mong muốn mang đến những trải nghiệm mới cho người dùng.
Android TV tạo ứng dụng cho phép người dùng trải nghiệm nội dung của ứng dụng trên
màn hình lớn.

Android Auto là hệ điều hành được tùy biến cho các xe hơi. Nhằm mang đến các trải
nghiệm giải trí, công việc tuyệt vời. Các trải nghiệm dẫn đường, nghe nhạc, xem phim, ...
sẽ được nâng lên một tầm mới. Việc viết ứng dụng của bạn với Android Auto, bạn sẽ không
phải lo lắng sự khác biệt về phần cứng của thiết bị như độ phân giải màn hình, giao diện
phần mềm, nút bấm và điều khiển cảm ứng. Người dùng có thể truy cập ứng dụng của bạn
thông qua ứng dụng Android Auto trên điện thoại của họ. Hoặc, khi được kết nối với các
loại xe tương thích, các ứng dụng trên thiết bị cầm tay chạy Android 5.0 trở lên có thể giao
tiếp với trải nghiệm Android Auto.
Wear OS (trước đây là Android Wear) là hệ điều hành Android được tùy biến xây dựng
cho các thiết bị đeo. Đồng hồ thông minh giúp bạn luôn kết nối với sức khỏe. Bạn có thể
theo dõi các hoạt động hằng ngày như thông tin thân nhiệt, nhịp tim, bước chân đi, giờ ngủ,
đọc nhanh các tin nhắn, email, …

1
(Một số thông tin được lấy từ trang Bách khoa toàn thư mở wikipedia.org).

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


Google Chrome OS được thiết kế dùng trình duyệt web Google Chrome là giao diện người
dùng chính, và như vậy nó chủ yếu là để làm việc với các ứng dụng web. Giao diện người
dùng được đơn giản hóa tối đa, giống như trong trình duyệt Google Chrome. Do trình duyệt
web là ứng dụng duy nhất tồn tại trong thiết bị, Google Chrome OS nhắm vào những người
dùng dành hầu hết thời gian làm việc với máy tính của họ trên Internet. Lợi thế mạnh nhất
của Chrome OS chính là tốc độ khởi động, cũng vì là hệ điều hành trong một trình duyệt
web nên Chrome OS rất nhẹ và đơn giản. Tính năng tiện lợi nhất của Chrome OS là người
dùng có thể kết nối bất cứ ở đâu nếu có internet và Chrome OS, vì nó sử dụng tài khoản
cá nhân trên nền tảng đám mây nên dữ liệu, lịch sử của bạn sẽ không bị mất.
Android Things cho phép bạn tạo các thiết bị thông minh, được kết nối với nhiều ứng
dụng công nghiệp, người bán lẻ và công nghiệp khác nhau. Lợi thế của Android Things là
phát triển ứng dụng cho thiết bị của bạn bằng các công cụ phát triển Android, giao diện lập
trình ứng dụng (API) và tài nguyên cùng với API mới, cung cấp I/O và thư viện cấp thấp
cho các thành phần phổ biến như cảm biến nhiệt độ, bộ điều khiển hiển thị và hơn thế nữa.
Xây dựng các thiết bị trên các giải pháp phần cứng chìa khóa trao tay với Hệ thống mô-
đun được chứng nhận (SoM) và các bo mạch phát triển giúp đơn giản hóa nguyên mẫu cho
quy trình sản xuất. Google hỗ trợ cung cấp hình ảnh, bản cập nhật và bản sửa lỗi hệ thống
để bạn có thể tập trung vào việc tạo ra các sản phẩm hấp dẫn. Ngoài ra, bạn có thể đẩy các
bản cập nhật hệ thống này và bản cập nhật ứng dụng của riêng bạn lên thiết bị bằng Android
Things Console.

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


CHƯƠNG 2 THIẾT KẾ LAYOUT TRONG ANDROID
STUDIO
2.1 ĐẶT VẤN ĐỀ
Android Layout là một lớp điều khiển việc sắp xếp các thành phần con của nó xuất hiện
trên màn hình. Bất cứ thành phần nào đó được gọi là một View (hoặc thừa kế từ View) đều
có thể là con của một Layout. Tất cả các lớp Layout đều mở rộng từ lớp ViewGroup (mà
kế thừa từ View), do đó bạn cũng có thể tạo một lớp Layout tùy biến của mình, bằng cách
tạo một lớp mở rộng từ ViewGroup. Hình 2.1 minh họa sơ đồ thừa kế giữa các thành phần
giao diện trong Android.

Hình 2.1 Sơ đồ thừa kế giữa các thành phần giao diện trong Android

2.2 CÁC LOẠI LAYOUT TIÊU CHUẨN TRONG ANDROID:


Bảng 2.1 Các loại Layout tiêu chuẩn trong Android

Layout Mô tả
Relative Layout RelativeLayout là một ViewGroup nó hiển thị các View con
của nó theo vị trí có tương đối với nhau. Việc sắp xếp các
view trên màn hình tốn ít tài nguyên hơn, tuy nhiên do có sự
quan hệ qua lại nên khi tùy chỉnh các thành phần sẽ chú ý

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


hơn. Đây là Layout mặc định thay cho LinearLayout ở các
phiên bản kế tiếp của Android Studio
Linear Layout LinearLayout bố trí tất cả các thành phần con của nó theo
một hướng duy nhất, theo chiều dọc hoặc chiều ngang.
Những phiên bản Android Studio ban đầu chọn
LinearLayout làm layout mặc định.
Table Layout TableLayout là một view nó nhóm các view con thành các
dòng và các cột.

Grid Layout GridLayout sử dụng một mạng lưới các dòng mỏng và vô
hạn để tách khu vực bản vẽ của nó thành: các hàng, các cột,
và các ô (cell). Nó hỗ trợ cả việc bắc qua (span) các hàng và
các cột, mà cùng nhau cho phép một thành phần giao diện để
chiếm một phạm vi hình chữ nhật gồm nhiều ô.
Frame Layout FrameLayout là một đối tượng giữ chỗ ở trên màn hình mà
bạn có thể sử dụng để hiển thị một khung nhìn duy nhất.

Absolute Layout AbsoluteLayout làm bạn có thể chỉ định chính xác vị trí của
các view con trong nó. Sắp xếp các view con theo đúng tọa
độ x, y trong thành phần cha.
Constraint Layout Một constraint là phần mô tả làm thế nào để một View nên
được định vị trên màn hình tương đối với các phần tử khác
trong layout. Bạn có thể xác định một constraint cho một hay
nhiều mặt của một view bằng cách chế độ kết nối bất kỳ sau
đây : điểm neo nằm trên một View khác, một cạnh của
layout, một guideline vô hình. Đây là Layout mặc định trong
Android Studio hiện tại.
2.3 RELATIVE LAYOUT:
RelativeLayout là một ViewGroup có hiển thị các View con ở các vị trí tương đối. Vị trí
của mỗi View có thể được quy định liên quan đến các View anh em (như bên trái của hoặc
bên dưới một View khác) hoặc ở các vị trí tương đối với khu vực cha RelativeLayout
(chẳng hạn như sắp xếp ngay phía dưới, bên trái hoặc trung tâm).
RelativeLayout là một tiện ích rất mạnh mẽ cho thiết kế một giao diện người sử dụng vì nó
có thể loại bỏ các nhóm View lồng nhau và giữ cho hệ thống phân cấp bố trí của bạn bằng
phẳng, đồng thời cải thiện hiệu suất. Nếu bạn sử dụng một vài nhóm LinearLayout lồng
nhau, bạn có thể thay thế chúng bằng một RelativeLayout duy nhất.
Ví dụ 2.1 Thiết kế screen nhập ghi chú như sau:

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


Hình 2.2 Thiết kế một giao diện sử dụng RelativeLayout

Với hoạt động (activity) này thì chúng ta có thể sử dụng các layout như: TableLayout,
LinearLayout, RelativeLayout, ConstraintLayout để thiết kế. Ở phần này, tôi sử dụng
RelativeLayout để thiết kế ra giao diện như trên.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="@+id/linearlayoutmain"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<TextView
android:id="@+id/textView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:text="Ghi chú"

android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"/>

<EditText
android:id="@+id/editText2"
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_below="@+id/textView1"
android:hint="Nhập ghi chú vào đây"
android:inputType="textMultiLine">
<requestFocus/>
</EditText>

<Button
android:id="@+id/button1"

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


android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@+id/editText2"
android:text="Lưu"/>

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/button1"
android:layout_alignBottom="@+id/button1"
android:layout_alignParentRight="true"
android:text="Hủy"/>
</RelativeLayout>

Một số thuộc tính thường dùng khi sử dụng Relative Layout:


Bảng 2.2 Các thuộc tính thườn dùng trong relative layout

Tên thuộc tính Mô tả


android:layout_above Đặt tiện ích (widget) hiện tại nằm kế sau
widget có id được chỉ ra
Đặt widget này lên cùng dòng với
android:layout_alignBaseline
widget có id được chỉ ra
Canh sao cho đáy của widget hiện thời
android:layout_alignBottom
trùng với đáy của widget có id được chỉ
ra
android:layout_alignLeft Đặt cạnh trái của widget hiện thời trùng
với cạnh trái của widget có id được chỉ
ra
android:layout_alignParentBottom Nếu thiết lập là true thì widget hiện thời
sẽ được canh xuống đáy của widget
chứa nó
android:layout_alignParentLeft Nếu được thiết lập là true thì widget
hiện thời sẽ canh trái so với widget chứa

android:layout_alignParentRight Nếu được thiết lập là true thì widget
hiện thời sẽ canh phải so với widget
chứa nó
android:layout_alignParentTop Nếu được thiết lập là true thì widget
hiện thời sẽ canh lên đỉnh widget chứa

android:layout_alignRight Canh phải của widget hiện thời trùng
với cạnh phải của widget có id được chỉ
ra

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


android:layout_alignTop Canh đỉnh của widget hiện thời trùng
với đỉnh của widget có id được chỉ ra
android:layout_alignWithParentIfMissing Nếu thiết lập là true, thì widget sẽ được
canh theo widget chứa nó nếu các thuộc
tính canh của widget không có
android:layout_below Đặt widget hiện thời ngay sau widget có
id được chỉ ra
android:layout_centerHorizontal Nếu thiết lập là true thì widget hiện thời
sẽ được canh giữa theo chiều ngang
widget chứa nó
android:layout_centerInParent Nếu thiết lập là true thì widget hiện thời
sẽ được canh chính giữa theo chiều phải
trái và trên dưới so với widget chứa nó
android:layout_centerVertical Nếu thiết lập là true thì widget hiện thời
sẽ được canh chính giữa theo chiều dọc
widget chứa nó
android:layout_toLeftOf Đặt cạnh phải của widget hiện thời trùng
với cạnh trái của widget có id được chỉ
ra
android:layout_toRightOf Đặt cạnh trái của widget hiện thời trùng
với cạnh phải của widget có id được chỉ
ra
Ví dụ 2.1 Kết quả hiển thị trên máy ảo:

Hình 2.3 Màn hình ứng dụng khi chạy máy ảo

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


2.4 LINEAR LAYOUT:
LinearLayout là một ViewGroup nó sắp xếp các thành phần con theo một hướng duy nhất,
theo chiều dọc hoặc chiều ngang. Bạn có thể chỉ định hướng của nó theo thuộc tính:
android:orientation. Thuộc tính này quy định hướng sắp xếp của Layout, bạn sử dụng
"horizontal" đối với dòng, và "vertical" để sắp xếp theo cột. Mặc định là "horizontal".
Bảng 2.3 Các thuộc tính trong linear layout

Thuộc tính Mô tả
Layout_width Quy định độ rộng của widget
Layout_height Quy định độ cao của widget
layout_margintop Quy định khoảng cách bên trên của
widget
layout_marginbottom Quy định khoảng cách bên dưới của
widget
layout_marginleft Quy định khoảng cách bên trái của
widget
layout_marginright Quy định khoảng cách bên phải của
widget
layout_gravity Quy định vị trí của widget
layout_weight Quy định khoảng không gian layout
phân chia cho widget

Hình 2.4 Hình ảnh minh hoạ cho margin và padding

Để hiểu rõ hơn về LinearLayout chúng ta sẽ tìm hiểu các ví dụ dưới đây. Lưu ý: các ví dụ
chủ yếu phân tích các Layout do đó sẽ dùng phương pháp thiết kế giao diện từ file XML,
không hướng dẫn lập trình chức năng ở phần này.

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


Ví dụ 2.2 Thiết kế screen gồm 4 cột màu, có độ rộng bằng nhau.

Với yêu cầu như vậy, chúng ta có thể sử dụng các layout như: LinearLayout, TableLayout,
RelativeLayout để thiết kế. Ở đây, tôi sử dụng LinearLayout để giải quyết yêu cầu này.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:weightSum="100"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#aa0000"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#00aa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#0000aa"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#aaaa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
</LinearLayout>
Code trên ta thấy thuộc tính orientation được gán giá trị là horizontal để các widget trong
nó được sắp xếp theo chiều ngang.
Thuộc tính android:weightSum được sử dụng để tính tổng weight của các widget trong
LinearLayout. Thuộc tính android:layout_weight (của các widget nằm bên trong
LinearLayout) là độ rộng của widget này so với các widget khác, ta thấy ở code trên giá trị
của thuộc tính android:layout_weight ở cả bốn TextView là “25”, do đó độ rộng của mỗi
cột màu là như nhau và chiếm hết toàn bộ màn hình vì tổng của các android:layout_weight
bằng android:weightsum. Nếu bạn muốn các cột màu có độ rộng như nhau nhưng chỉ chiếm
một nửa màn hình, hãy tăng gấp đôi giá trị của android:weightsum = “200”. Hãy thử và
kiểm tra kết quá có đúng như bạn nghĩ không!

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


Lưu ý: giá trị của thuộc tính android:layout_weight không làm cho widget nhỏ hơn giá trị
được gán ở android:layout_width. Do đó, khi thiết kế độ rộng của widget bằng thuộc tính
android:layout_weight thì cần phải gán cho thuộc tính android:layout_width một giá trị
thật nhỏ hoặc là “wrap_content”.
Hai thuộc tính android:layout_width=“match_parent” và android:layout_height
=“match_parent” để LinerLayout chiếm toàn bộ không gian của widget cha (ở đây là màn
hình hiển thị). Ở các TextView cũng có thuộc tính android:layout_height
=“match_parent” để chiều cao của TextView chiếm toàn bộ chiều cao của cha nó (ở đây
là LinearLayout).
Kết quả hiển thị trên máy ảo được trình bày trong hình 2.5.

Hình 2.5 Kết quả màn hình sau thiết kế

Ví dụ 2.3 Thiết kế screen có bốn hàng màu, với hàng màu đỏ và vàng có độ cao gấp đôi
so với hai hàng màu còn lại

Code file activity_main.xml:


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="100"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<TextView

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


android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#aa0000"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00aa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0000aa"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#aaaa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
</LinearLayout>
Thuộc tính android:orientation của LinearLayout được gán giá trị là vertical do đó các
widget trong layout sẽ được sắp xếp theo chiều dọc. Các nội dung còn lại thì tương tự như
trên. Các bạn có thể thay đổi giá trị weightSum và các layout_weight để xem sự thay đổi.
Kết quả hiển thị trên máy ảo được trình bày trong hình 2.6.

Hình 2.6 Kết quả hiển thị trên màn hình khi thiết kế LinearLayout dọc
Ví dụ 2.4 Thiết kế screen là tổ hợp bốn hàng màu và bốn cột màu đã làm ở các ví dụ một
và ví dụ hai.

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


Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="200"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#aa0000"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#00aa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0000aa"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#aaaa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:weightSum="100"
android:layout_weight="100">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#aa0000"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#00aa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#0000aa"
android:gravity="center_horizontal"

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


android:layout_weight="25"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="#aaaa00"
android:gravity="center_horizontal"
android:layout_weight="25"/>
</LinearLayout>
</LinearLayout>

Horizontal

Vertical

Vertical

Hình 2.7 Thiết kế các Layout lồng vào nhau

Như đã nói ở trên, các ViewGroup có thể chứa cả View và ViewGroup bên trong nó, ở ví
dụ ta sẽ lồng các LinearLayout vào nhau để đáp ứng yêu cầu thiết kế, hình dưới phác thảo
việc lồng các LinearLayout lại với nhau. Kết quả hiển thị trên máy ảo được trình bày trong
hình 2.8.
Trong thực tế, thiết kế giao diện cho các ứng dụng thường phức tạp, chỉ sử dụng một Layout
để chưa các widget không để đáp ứng yêu cầu thiết kế. Do đó, giải pháp lồng các Layout
vào nhau sẽ giúp bạn giải quyết hầu hết các bài toán giao diện. Ngoài LinearLayout còn
một số Layout khác hỗ trợ thiết kế giao diện mà chúng ta sẽ thảo luận dưới đây.

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


Hình 2.8 Kết quả hiển thị trên máy ảo các Layout lồng vào nhau

2.5 TABLE LAYOUT:


TableLayout là một ViewGroup nó hiển thị các thành phần con trên các hàng và các cột.
TableLayout đặt vị trí các thành phần con của nó theo hàng và cột. Bộ chứa TableLayout
không hiển thị đường biên (border) cho các hàng, các cột, hoặc các ô của nó. Bảng sẽ có
nhiều cột nhiều hàng với nhiều ô. Một bảng có thể có các ô trống, nhưng các ô không thể
bắc qua (span) nhau theo chiều thẳng đứng, như cách có thể trong HTML.
Các đối tượng TableRow là các View con của một TableLayout (mỗi TableRow định nghĩa
một hàng duy nhất trong bảng). Mỗi hàng có 0 hoặc nhiều ô, mỗi ô trong số đó có thể chứa
bất kỳ loại View nào. Vì vậy, một dòng có thể chứa một vài loại View, như ImageView
hoặc TextView. Một ô cũng có thể chứa ViewGroup (ví dụ, bạn có thể cho một
TableLayout khác vào trong 1 ô).
Ví dụ 2.5 Thiết kế screen đăng nhập sử dụng TableLayout như sau:

Hình 2.9 Thiết kế màn hình đăng nhập dùng TableLayout

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


Để thiết kế như hình trên, ta có thể sử dụng các layout RelativeLayout, LinearLayout,
ConstraintLayout, TableLayout, GridLayout. Sau đây, tôi sẽ sử dụng TableLayout để thiết
kế giao diện cho activity này.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/tablelayout"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<TableRow android:layout_width="match_parent">
<TextView
android:layout_width="wrap_content"
android:text="User"/>
<EditText android:layout_width="match_parent"/>
</TableRow>

<TableRow android:layout_width="match_parent">
<TextView
android:layout_width="wrap_content"
android:text="Pass"/>
<EditText android:layout_width="match_parent"
android:inputType="textPassword"/>
</TableRow>

<TableRow android:layout_width="match_parent">
<Button
android:layout_width="wrap_content"
android:text="OK"/>
<Button
android:layout_width="wrap_content"
android:text="Thoát"/>
</TableRow>
</TableLayout>

Từ code trên ta thấy để tạo thêm các hàng cho bảng ta sẽ dùng thẻ
<TableRow></TableRow>, các widget ở mỗi hàng sẽ nằm trong cặp thẻ này. Số cột của
bảng phụ thuộc vào hàng có số widget lớn nhất. Bảng sử dụng trong ví dụ này có kích
thước là ba hàng và hai cột.
Thuộc tính android:StretchColumns=“*” cho phép căn đều độ rộng các cột trong bảng.
Ngoài ra, EditText để nhập mật khẩu sử dụng thuộc tính
android:inputType="textPassword" để hiển thị các dữ liệu được nhập vào ở dạng ký tự “*”.
Kết quả hiển thị trên máy ảo:

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


Hình 2.10 Kết quả mô phỏng xây dựng màn hình đăng nhập dùng TableLayout

2.6 GRID LAYOUT:


GridLayout sử dụng một mạng lưới các dòng mỏng và vô hạn để tách khu vực bản vẽ của
nó thành: các hàng, các cột, và các ô (cell). Nó hỗ trợ cả việc bắc qua (span) các hàng và
các cột, nghĩa là cho phép hợp nhất ô gần nhau thành một ô lớn (hình chữ nhật) để chứa
một View. Trong GridLayout, việc chỉ định kích thước và căn lề làm giống với
LinearLayout. Căn chỉnh/trọng lượng (Alignment/gravity) cũng làm việc giống như trọng
lượng (gravity) trong LinearLayout và sử dụng chung các hằng số: left, top, right, bottom,
center_horizontal, center_vertical, center, fill_horizontal, fill_vertical và fill. Không giống
như hầu hết các lưới ở các bộ công cụ khác, Android GridLayout không liên kết dữ liệu
với các hàng hoặc cột. Thay vào đó, tất cả mọi thứ được làm với Aligment (căn chỉnh) và
sự linh hoạt được liên kết với các thành phần tự thân của nó.
Sự linh hoạt của các cột được suy ra từ thuộc tính gravity của các thành phần bên trong các
cột. Nếu mọi thành phần định nghĩa một gravity, cột được coi là linh hoạt, nếu không cột
được coi là không linh hoạt.
2.7 FRAME LAYOUT:
FrameLayout là một đối tượng giữ chỗ ở trên màn hình mà bạn có thể sử dụng để hiển thị
một View đơn lẻ. Frame Layout là loại Layout cơ bản nhất. Đặc điểm của FrameLayout là
khi đặt các widget lên screen thì các widget này luôn nằm ở góc trái trên cùng, nó không
cho phép chúng ta thay đổi vị trí của các widget. Các widget đưa vào sau sẽ nằm chồng lên
các widget đưa vào trước theo thứ tự trong file XML.
Ví dụ 2.6 Thiết kế giao diện 3 nút nhấn nằm trong FrameLayout như hình sau:

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


Hình 2.11 Thiết kế các nút nhấn sử dụng FrameLayout.
Với yêu cầu thiết kế như trên, các layout có thể sử dụng như: FrameLayout,
ConstraintLayout, RelativeLayout. Ở đây, tôi sử dụng FrameLayout để thực hiện yêu cầu
trên.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/framelayout"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="288dp"
android:background="#000aa0"
android:text="Button1"/>
<Button
android:id="@+id/button2"
android:layout_width="198dp"
android:layout_height="127dp"
android:background="#00aaa0"
android:text="Button2"/>
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#ffffff"
android:text="Button3"/>

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


</FrameLayout>

Theo thứ tự code trong file xml, Button1 ở đầu tiên nên trên giao diện nó sẽ nằm dưới cùng,
Button2 nằm ở giữa và Button1 nằm trên cùng.
Bài tập tổng hợp, dựa vào các Layout đã tìm hiểu ở trên, hãy thiết kế Screen như hình dưới.

Hình 2.12 Sử dụng các Layout đã trình bày thiết kế giao diện máy tính đơn giản

2.8 CONSTRAINT LAYOUT:


Một constraint là phần mô tả làm thế nào để một View nên được định vị trên màn hình
tương đối với các phần tử khác trong layout. Bạn có thể xác định một constraint cho một
hay nhiều mặt của một view bằng các chế độ kết nối bất kỳ sau đây:
• Điểm neo nằm trên một View khác.
• Một cạnh của layout.
• Một guideline vô hình.
Bởi layout của từng View được định nghĩa bởi sự kết hợp với các phần tử khác nhau trong
Layout. Bạn có thể tạo một giao diện phức tạp với hệ thống cây nhiều nhánh (kiểu View
Hierachy). Mặc dù nó có khái niệm tương tự như RelativeLayout nhưng ConstraintLayout
là linh hoạt hơn và được thiết kế sử dụng hoàn toàn từ công cụ Layout Editor mới. Và trong
các phiên bản Android Studio mới, đây được chọn làm Layout mặc định khi thiết kế.
Các loại của constaint bạn có thể xác định như sau:
• Kết nối phía bên các cạnh của Layout
• Kết nối phía bên của một View sang bên tương ứng với Layout. ví dụ kết nối phía
trên của một View vào mép trên cùng của ConstraintLayout
• Kết nối phía bên cạnh của một View.

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


• Kết nối phía bên cạnh của một View đến phía đối diện của một View khác.
Ví dụ 2.7 Kết nối cạnh trên (top) của View A vào cạnh dưới (bottom) của View B, như
vậy thì A luôn luôn ở bên dưới B
Chỉnh thẳng hàng với một View. Canh thẳng hàng các cạnh của một view để các cạnh và
góc của view khác tương tự. Ví dụ sắp xếp bên trái của View A đến phía bên trái của View
B sao cho chúng được xếp chồng theo chiều dọc và canh trái.
Chỉnh cơ bản với một View: Căn đường cơ bản của văn bản nằm trong view với một đường
cơ bản của vản bản trong view khác. Sử dụng điều này để gắn kết các view theo chiều
ngang khi các văn bản trong view quan trọng hơn là căn các cạnh trong view.
Khi bạn thêm một constraint vào View bạn xem trong công cụ layout editor. Bạn có thể
kéo để đặt lại vị trí của View và thêm những constraint để đảm bảo bố trí của bạn đáp ứng
các kích cỡ màn hình khác nhau một cách thích hợp.
Ví dụ 2.8 Viết giao diện của ứng dụng tính chỉ số BMI (sẽ có ở chương sau)

Hình 2.13 Thiết kế giao diện ứng dụng sử dụng ConstraintLayout

Để thiết kế được giao diện như trên thì ta có thể sử dụng các Layout ở trên và ở phần này
tôi sẽ sử dụng ConstraintLayout để thiết kế. Các bạn sẽ thấy đây là layout rất tiện lợi và
đang được Google khuyến khích dùng.
Code file activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"

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


xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/constraintlayout"
tools:context="com.example.windows10.layoutsinandroid.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvyeucau"
android:text="Hãy nhập chiều cao và cân nặng của bạn"
android:textSize="25sp"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
app:layout_constraintTop_toTopOf="@id/constraintlayout"
app:layout_constraintLeft_toLeftOf="@id/constraintlayout"
app:layout_constraintRight_toRightOf="@id/constraintlayout"
tools:layout_editor_absoluteX="0dp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvcao"
android:textSize="20sp"
android:text="Chiều cao"
app:layout_constraintTop_toBottomOf="@id/tvyeucau"
android:layout_marginTop="20sp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/edcao"
android:hint="Nhập chiều cao"
app:layout_constraintTop_toBottomOf="@id/tvyeucau"
app:layout_constraintLeft_toRightOf="@id/tvcao"
app:layout_constraintBaseline_toBaselineOf="@id/tvcao"
android:layout_marginLeft="30sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/tvnang"
android:text="Cân nặng"
android:textSize="20sp"
app:layout_constraintTop_toBottomOf="@id/tvcao"
app:layout_constraintLeft_toRightOf="@id/constraintlayout"
android:layout_marginTop="20dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ednang"
android:hint="Nhập cân nặng"
app:layout_constraintTop_toBottomOf="@id/edcao"
app:layout_constraintLeft_toRightOf="@id/tvnang"
app:layout_constraintBaseline_toBaselineOf="@id/tvnang"
android:layout_marginLeft="30sp"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"

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


android:id="@+id/nutnhan"
android:text="Tiếp tục"
android:layout_marginTop="20dp"
app:layout_constraintTop_toBottomOf="@id/tvnang"
app:layout_constraintLeft_toLeftOf="@id/constraintlayout"
app:layout_constraintRight_toRightOf="@id/constraintlayout"/>
</android.support.constraint.ConstraintLayout>

Kết quả thực hiện trên máy ảo:

Hình 2.14 Kết quả màn hình ứng dụng khi chạy máy ảo

Giải thích về code thiết kế xml:


<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/ednang"
android:hint="Nhập cân nặng"
app:layout_constraintTop_toBottomOf="@id/edcao"
app:layout_constraintLeft_toRightOf="@id/tvnang"
app:layout_constraintBaseline_toBaselineOf="@id/tvnang"
android:layout_marginLeft="30sp"/>
Ở trong ConstraintLayout, chúng ta sẽ sử dụng các id của các đối tượng để căn chỉnh cho
nhau. Ở đây tôi sẽ giải thích về đối tượng EditText có id: “ednang” và các đối tượng
TextView, Button còn lại sẽ tương tự. Thuộc tính layout_constrainTop_toBottomOf =
“@id/edcao” sẽ căn chỉnh Top của EditText với Bottom của đối tượng có id là edcao.
Thuộc tính layout_constrainLeft_toRightOf = “@id/tvnang” sẽ căn chỉnh Left của
EditText với Right của đối tượng có id là tvnang. Thuộc tính
layout_constraintBaseline_toBaselineOf="@id/tvnang" sẽ đưa EditText nằm cùng hàng

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


với đối tượng có id là tvnang. Thuộc tính layout_marginLeft=“30sp” sẽ đưa EditText cách
xa bên trái so với đối tượng có id là tvnang 30sp.

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


CHƯƠNG 3 CÁC ĐIỀU KHIỂN GIAO DIỆN NGƯỜI
DÙNG
3.1 ĐẶT VẤN ĐỀ
Tất cả phần tử giao diện người (User Interface) trong một ứng dụng Android đều được xây
dựng bằng cách sử dụng các đối tượng View và ViewGroup. View là một đối tượng có
chức năng hiển thị trên màn hình mà người dùng có thể tương tác. ViewGroup là một đối
tượng có chức năng giữ hoăc chứa các đối tượng View (và ViewGroup) khác để định nghĩa
bố trí giao diện của ứng dụng. Android cung cấp một bộ sưu tập cả lớp con View và
ViewGroup cho bạn các cách điều khiển nhập liệu thông dụng (chẳng hạn như nút nhấn
Button và trường văn bản TextView) và các Layout khác nhau.
Giao diện người dùng (UI) thành phần trong ứng dụng của bạn được định nghĩa bằng cách
sử dụng một phân cấp các đối tượng View và ViewGroup như minh họa trong hình dưới
đây. Mỗi Viewgroup là một bộ chứa vô hình có chức năng tổ chức view, viewgroup con,
trong khi viewgroup con có thể là điều khiển nhập liệu (EditText) hoặc các widget khác để
vẽ một phần nào đó của UI. Cây phân cấp này có thể đơn giản hoặc phức tạp tùy nhu cầu
của bạn (nhưng đơn giản sẽ tốt cho hiệu năng).

Hình 3.1 Các sắp xếp các view, Viewgroup

3.2 CÁC ĐỐI TƯỢNG UI CƠ BẢN VÀ THUỘC TÍNH QUAN TRỌNG:


Một số View thường được sử dụng trong thiết kế giao diện ứng dụng như: TextView,
Button, RadioButton, CheckBox, EditText, Switch, ImageView, …
Các ViewGroup thường dùng chính là các Layout dùng để sắp xếp các widget khác bên
trong nó, một số Layout phổ biến như: LinearLayout, TableLayout, RelativeLayout,
GridLayout, ConstrainLayout, … đã được nói đến ở chương trước.

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


3.2.1 CÁC THUỘC TÍNH QUAN TRỌNG CỦA MỘT UI:
Thuộc tính chiều cao và chiều rộng để xác định kích thước của một UI Control:
layout_width, layout_height. Chúng ta có thể ghi:
• wrap_content: Vừa với nội dung mà UI đó chứa.
• match_parent: Vừa với ViewGroup đang chứa UI đó.
• Đơn vị đo: dp, sp, px, pt, inch.
Giải thích các đơn vị dùng trong thiết kế để các bạn có thể hiểu rõ hơn:
• Pixel (px) hay còn gọi điểm ảnh: Pixel, px hay có khi gọi là pel (xuất phát từ
“picture element”), chúng ta hay gọi là điểm ảnh, có dạng hình vuông. Một hình ảnh
bitmap mà bạn thấy trên màn hình là ma trận hai chiều (2D) của các pixel tạo nên
(hay ma trận của các hình vuông nhỏ). Mỗi pixel chiếm một vị trí trong ma trận và
chứa một phần của hình ảnh hiển thị. Pixel thường được dùng để nói về độ phân giải
(resolution) của thiết bị: ví dụ Samsung Galaxy S3 có màn hình độ phân giải 1280 x
720 (Height x Width). Điều này có nghĩa là chiều cao của màn hình Galaxy S3 là
1280 pixels, còn chiều rộng là 720 pixels. Thiết bị có độ phân giải càng cao thì màn
hình càng có nhiều pixels. Tuy nhiên điều này chưa nói lên được là màn hình sẽ hiển
thị ảnh mịn, đẹp hay không. Điều này tuỳ thuộc vào diện tích của màn hình, vì vậy
pixel không dùng làm đơn vị đo lường kích thước của màn hình thiết bị, nhưng lại
được dùng để đo kích thước của ảnh. Cùng một đối tượng khi thiết kế với kích thước
chọn theo px giống nhau, nhưng khi chạy trên các các màn hình có độ phân giải khác
nhau sẽ cho kích thước hiển thị khác nhau.

Hình 3.2 Chọn kích thước màn hình thiết kế trong Android Studio

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


Hình 3.3 Độ phân giải pixel trong Androidd

• dp, hay dip hay Density-independent Pixels, có khi được gọi là Device-
independent Pixels. Đây là một đơn vị đo chiều dài vật lý cũng giống như inch,
cm, mm… mà Google thường áp dụng để do kích thước màn hình của thiết bị. 160
dp = 1inch, điều này có nghĩa 1dp = 1/160 = 0.00625 inch. Một dp có thể chứa một
hay nhiều pixel. Ví dụ như màn hình có kích thước là 10 dp. Ở màn hình độ phân
giải thấp 1dp tương đương 1 pixel. Ở độ phân giải trung bình thì 1dp tương đương
4 pixels ….

Hình 3.4 Kích thước tính theo đơn vị dp

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


Hình 3.5 Kích thước dp trong Android

• DPI (Dots per inch) hay PPI – (Pixels per inch) là số điểm ảnh (pixels) trên một inch của
màn hình thiết bị, con số này càng lớn thì màn hình thiết bị hiển thị hình ảnh càng mịn và
đẹp. Dựa vào dpi người ta chia làm loại màn hình như sau: small: ldpi (120 dpi), normal:
mdpi (160 dpi), large: hdpi (240 dpi), x-large: xhdpi (320 dpi). Với mỗi loại này thì một dp
tương ứng với số lượng pixels khác nhau, được tính theo công thức:
px = dp * (dpi / 160)
Ví dụ với thiết bị có dpi là 320 thì với 10 dp ta có: 10 * (320/160) = 20 px , một dp tương đương
2 px.
• PT (Point): khái niệm pt tương tự như dp là một đơn vị đo kích thước thực, nhưng khác với
dp: 1 pt = 1/72 inch, trong khi 1 dp = 1/160 inch pt thường được dùng trong lập trình iOS.
• SP (Scale-independent Pixels) Cũng tương tự như dp, nhưng sp thường được dùng cho
font size của văn bản.

Hình 3.6 Ví dụ về kích thước sp

Sau đây là ví dụ ở màn hình normal screen mdpi (160dpi) cho các đơn vị đo dp, sp, px,
pt, inch

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


Hình 3.7 Biểu diễn của các đơn vị đo trên cùng một màn hình
Nguồn Androidvn.org

Thuộc tính id và text dùng để phân biệt UI này với UI khác:


• id: dùng để xác định địa chỉ của UI đó và trong toàn bộ ứng dụng không có bất cứ id
nào trùng nhau.
• Text: dùng để hiển thị tác dụng của UI do người dùng tuỳ chọn đặt tên để dễ phân
biệt.
Đây là các thuộc tính quan trọng của một UI không thể bỏ qua khi thiết kế giao diện.
Ngoài ra, mỗi UI khác nhau sẽ có các thuộc tính đặc trưng riêng các bạn hãy tự tìm
hiểu thêm nhé.
Khi lập trình, trong file .java (ví dụ: scr/MainActivity.java) tạo một đại diện cho đối tượng
Control này và “kết nối” nó từ layout, Ví dụ ta đặt tên cho đại diện của TextView là myText
và ta muốn buột nó vào TextView có id là text_id ở file xml của ta, ta sử dụng như sau:
TextView myText = (TextView) findViewById(R.id.text_id);

Trên đây ta ví dụ cho trường hợp Control của ta là một TextView, các đối tượng khác
làm tương tự.
3.2.2 CÁC ĐỐI TƯỢNG UI CƠ BẢN:

STT UI Control và mô tả


1 TextView: Điều khiển này được sử dụng để hiển thị văn bản (text) cho
người dùng

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


2 EditText: Điều khiển này định nghĩa là lớp con của TextView bao gồm các
khả năng chỉnh sửa
3 AutoCompleteTextView: Là một view tương tự như EditText ngoại trừ nó
hiển thị một danh sách được đề nghị để hoàn thành trong lúc bạn đang nhập.
4 Button: Là một nút nhấn có thể được nhấn, click bởi người dùng khi thực
thi một hoạt động.
5 ImageButton: Tương tự nút nhấn nhưng có hình ảnh.
6 CheckBox: Là tùy chọn có thể được thay đổi bởi người dùng. Bạn nên sử
dụng các checkboxes khi người dùng có thể tùy chọn một nhóm mà không
bắt buộc sự loại trừ lẫn nhau.
7 ToggleButton: Một nút nhấn on/off có đèn hiển thị
8 RadioButton: RadioButton có hai trạng thái: hoặc là được đánh
dấu(checked) hoặc không được đánh dấu (unchecked)
9 RadioGroup: RadioGroup được dùng để nhóm một hoặc nhiều
RadioButton với nhau
10 ProgressBar: ProgressBar cung cấp một phản hồi trực quan về các công
việc đang xử lý, ví dụ bạn đang thực hiện một công việc ở trạng thái nền.
11 ImageView: ImageView được dùng để hiển thị hình ảnh.
12 Switch: Switch tương tự như ToggleButton có hai trạng thái là check và
uncheck.
3.3 BÀI TẬP VỀ UI CONTROL:
Các UI kể trên rất quen thuộc trong thiết kế, bạn cũng không quá khó để làm quen. Vì đã
trình bày trong các tài liệu trước cũng như khá nhiều tài liệu từ trang web phát triển Android
nên tác giả không muốn trình bày lại, mà chỉ sơ lược qua. Để hiểu rõ ta xem một ví dụ như
sau đây:
Yêu cầu: Ta có một Activity cho nhập chiều cao, cân nặng vào. Sau đó, nó sẽ tính toán chỉ
số BMI và đưa ra kết luận về tình trạng của bạn trên một Activity khác.

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


Giao diện mong muốn theo yêu cầu:

Hình 3.8 Giao diện ứng dụng tính chỉ số BMI

Giải thích về yêu cầu: Người dùng nhập chiều cao và cân nặng vào hai EditText ở Activity
thứ nhất. Khi nhấn nút “Tiếp tục” thì sẽ chuyển qua Activity thứ hai. Sau đó tính toán chỉ
số BMI và hiển thị kết quả lên Activity thứ hai. Khi nhấn nút “OK” thì sẽ kết thúc và quay
lại Activity chính.
Các bước thực hiện như sau:
Bước 1: Tạo ứng dụng Android.
Bước 2: Thiết kế Layout cho Main Activity như yêu cầu gồm: ba TextView, hai
EditText, một Button. Chỉnh file res/layout/activity_main.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.windows10.bmicalculator.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:text="@string/tvrequest"/>
<LinearLayout
android:layout_width="match_parent"

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


android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="10">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="4"
android:textSize="30sp"
android:text="Chiều cao (m)"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="6"
android:hint="a.b (m)"
android:textSize="30sp"
android:id="@+id/chieucao"
android:inputType="numberDecimal"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="10">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="4"
android:textSize="30sp"
android:text="Cân nặng (kg)"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="6"
android:hint="c kg"
android:textSize="30sp"
android:id="@+id/cannang"
android:inputType="number"/>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:id="@+id/nutnhan"
android:text="Tiếp tục" />
</LinearLayout>
Bước 3: Tạo một Activity mới tên là Ketqua, nên tạo bằng Wizard. Và ta thiết kế layout
theo yêu cầu gồm hai TextView và một Button. Chỉnh file res\layout\activity_main2.xml
như sau:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"

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


tools:context="com.example.windows10.bmicalculator.Main2Activity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Kết quả"
android:textSize="30sp"
android:layout_gravity="center"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:id="@+id/ketqua"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OK"
android:layout_gravity="right"
android:id="@+id/ok"/>
</LinearLayout>

Bước 4: Định nghĩa các hằng số trong file res\value\string.xml như bên dưới. Để tiện
thiết kế layout thì ngay khi ta thiết kế layout cho MainActivity ta đã định nghĩa các hằng
số cho file String.xml rồi. Sau đó khi ta tạo Activity mới ta lại định nghĩa thêm. Việc này
sẽ giúp bạn thu gọn thuộc tính text trong các đối tượng.
<resources>
<string name="app_name">BMI Calculator</string>
<string name="tvrequest">Hãy nhập chiều cao và cân nặng của
bạn</string>
</resources>

Đến đây ta có giao diện của hai Activity như sau:

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


Hình 3.9 Giao diện thiết kế ứng dụng BMI
Bước 5: Khai báo Activity ketqua và Intent trong fileres\AndroidManifest.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.windows10.bmicalculator">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER"
/>
</intent-filter>
</activity>
<activity
android:name=".Main2Activity"
android:label="@string/app_name">
<intent-filter>
<action
android:name="android.intent.action.Main2Activity"/>

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

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


</manifest>

Bước 6: Viết code cho MainActivity. Chỉnh sửa file MainActivity.java như sau (Chú ý
các dòng chú thích).
package com.example.windows10.bmicalculator;

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

public class MainActivity extends AppCompatActivity {


EditText chieucao,cannang;
Button btn;
Double w,h;
Boolean a,b;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
anhxa();
}

@Override
protected void onResume() {
super.onResume();
//xử lý sự kiện nút nhấn
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(isEmpty(chieucao))
{
Toast.makeText(MainActivity.this,"Hãy nhập chiều
cao!",Toast.LENGTH_LONG).show();
a = false;
}
else
{
h =
Double.parseDouble(chieucao.getText().toString());
a = true;
}
if(isEmpty(cannang))
{
Toast.makeText(MainActivity.this,"Hãy nhập cân
nặng!",Toast.LENGTH_LONG).show();
b = false;
}
else
{

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


w = Double.parseDouble(cannang.getText().toString());
b = true;
}
if(a&b)
{
//Khai báo intent mới liên kết activity 2
Intent activity_ketqua = new
Intent(MainActivity.this, Main2Activity.class);
//Khai báo bundle để chứa các dữ liệu cần gửi
Bundle thongso = new Bundle();
thongso.putDouble("cannang", w);
thongso.putDouble("chieucao", h);
activity_ketqua.putExtra("dulieu", thongso);
//Mở activity 2
startActivity(activity_ketqua);
}
}
});
}
@Override
protected void onPause() {
super.onPause();
}
public void anhxa()
{
//ánh xạ các đối tượng từ file xml
chieucao = (EditText)findViewById(R.id.chieucao);
cannang = (EditText)findViewById(R.id.cannang);
btn = (Button)findViewById(R.id.nutnhan);
}
//kiem tra edit text đã được nhập dữ liệu hay chưa
private boolean isEmpty(EditText etText) {
if (etText.getText().toString().trim().length() > 0)
return false;
return true;
}
}

Bước 7: Viết code cho Main2Activity. Chỉnh sửa file Main2Activity.java như sau (Chú ý
các dòng chú thích).
package com.example.windows10.bmicalculator;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import org.w3c.dom.Text;

public class Main2Activity extends AppCompatActivity {


TextView ketqua;
Button btn_ok;
Double BMI;

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


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

@Override
protected void onResume() {
super.onResume();
Intent caller = getIntent();
Bundle dulieunhan = caller.getBundleExtra("dulieu");
Double chieucao=dulieunhan.getDouble("chieucao");
Double cannang=dulieunhan.getDouble("cannang");
//Tính chỉ số BMI theo tiêu chuẩn tổ chức y tế thế giới
BMI=cannang/(chieucao*chieucao);
BMI= (double) Math.round(BMI*100)/100;
String kq;
if(BMI<16) {kq="Bạn thuộc dạng đại gia có nghĩa là da bọc xương
đấy";}
else if(BMI<17) {kq="Bạn suy dinh dưỡng cấp độ 2 rồi, ăn như hạm
vào!";}
else if(BMI<18.5) {kq="Bạn suy dinh dưỡng rồi á, đề nghị ăn bồi
bổ nhiều nhiều";}
else if(BMI<24.9) {kq="Chiều cao và cân nặng bình thường, đề nghị
duy trì";}
else if(BMI<29) {kq="Bạn hơi bị nhiều ký, bắt đầu béo phì, giảm
cân gấp";}
else if(BMI<35) {kq="Mức độ béo phì của bạn bắt đầu nghiêm trọng,
quyết liệt giảm cân hơn nữa";}
else kq="Bạn có thể lăn rồi, hết phương cứu chữa";
StringBuffer tket=new StringBuffer();
tket.append("Chỉ số BMI của bạn là:
").append(BMI).append("\n").append(kq);
//hiển thị kết quả lên textview
ketqua.setText(tket);
//xử lý sự kiện nút nhấn
btn_ok.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//kết thúc activity 2
finish();
}
});
}
public void anhxa()
{
ketqua = (TextView)findViewById(R.id.ketqua);
btn_ok = (Button)findViewById(R.id.ok);
}
}

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


Kết quả sau khi chạy mô phỏng trên máy ảo:

Hình 3.10 Kết quả chạy trên máy tính ảo

Việc liên kết hai Activity với nhau đã được trình bày trong các quyển sách trước của cùng
tác giả. Có những cách thực hiện đơn giản hơn ứng cho những trường hợp cụ thể.

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


CHƯƠNG 4 XÂY DỰNG ỨNG DỤNG IOTS SỬ
DỤNG FIREBASE
4.1 ĐẶT VẤN ĐỀ
Hiện tại các ứng dụng IOTs (Internet of Things) rất phổ biến trong lãnh vực điều khiển,
giám sát. Chúng ta hoàn toàn có thể tự xây dựng một vài ứng dụng điều khiển các thiết bị
đơn giản qua internet, các thư viện công cụ hỗ trợ hiện tại rất đa dạng và mạnh mẽ, đáp
ứng gần hết các nhu cầu của người dùng. Ngoài ra, hiện tại hệ sinh thái điều khiển nhà
thông minh của Google cực kỳ rộng rãi, bản thân Google liên lục phát triển và cho ra các
sản phẩm mới như Google Home, Google Home Mini, Google Hub, ...Các thiết bị tương
thích với Google Home của các hãng khác thì phải nói là “nhiều như nấm”, chẳng hạn
Xiaomi với đèn Yeelight, Robot hút bụi, máy lạnh thông minh, quạt Mi Fan, thiết bị đo
nhiệt độ, độ ẩm thông minh...Điều đó để ta thấy rằng, chúng ta hoàn toàn có thể tự mua các
thiết bị về xây dựng hệ thống nhà thông minh. Việc điều khiển, giám sát các thiết bị từ xa
qua internet trở nên dễ dàng hơn bao giờ hết. Ở chương này, tác giả hướng đến người đọc
xây dựng một số ứng dụng IOTs cho riêng mình. Các ứng dụng được xây dựng ở mức nền
tảng, và đơn giản. Cuối cùng, tác giả chọn Firebase làm dịch vụ liên kết cơ sở dữ liệu.
4.2 ĐỊNH NGHĨA FIREBASE
Đó là một dịch vụ cơ sở dữ liệu thời gian thực hoạt động trên nền tảng đám mây được cung
cấp bởi Google nhằm giúp các lập trình phát triển nhanh các ứng dụng bằng cách đơn giản
hóa các thao tác với cơ sở dữ liệu. Ngoài ra, Firebase còn cung cấp cho bạn các công cụ để
phát triển các ứng dụng chất lượng cao, phát triển cơ sở người dùng của bạn và kiếm thêm
tiền. Chúng bao gồm các yếu tố cần thiết để bạn có thể kiếm tiền từ doanh nghiệp của mình
và tập trung vào người dùng của mình. Firebase giúp bạn xây dựng ứng dụng dễ dàng, cải
thiện chất lượng ứng dụng và hỗ trợ phát triển doanh nghiệp nhanh hơn.

Hình 4.1 Firebase hỗ trợ nhiều nền tảng

Bảng 4.1 Một số chức năng chính của Firebase:

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


Chức năng Mô tả
Cấu hình từ xa Tùy chỉnh cách ứng dụng của bạn hiển thị cho mỗi người dùng.
Thay đổi giao diện, triển khai dần dần các tính năng, chạy thử
nghiệm, cung cấp nội dung tùy chỉnh cho một số người dùng
nhất định hoặc thực hiện các cập nhật khác mà không cần triển
khai một phiên bản mới từ bảng điều khiển Firebase. Theo dõi
tác động của những thay đổi của bạn và điều chỉnh trong vài
phút.
Cơ sở dữ liệu thời gian Cơ sở dữ liệu thời gian thực là cơ sở dữ liệu ban đầu của
thực Firebase. Đây là một giải pháp hiệu quả, độ trễ thấp cho các ứng
dụng di động yêu cầu trạng thái được đồng bộ hóa giữa các máy
khách trong thời gian thực. Google khuyên dùng Cloud
Firestore thay vì Cơ sở dữ liệu thời gian thực cho hầu hết các
nhà phát triển khi bắt đầu một dự án mới.
Đám mây lửa (Cloud Lưu trữ và đồng bộ dữ liệu giữa người dùng và thiết bị - ở quy
FireStore) mô toàn cầu - bằng cách sử dụng cơ sở dữ liệu NoQuery (không
truy vấn) được lưu trữ trên đám mây. Cloud Firestore cung cấp
cho bạn đồng bộ hóa trực tiếp và hỗ trợ ngoại tuyến cùng với
các truy vấn dữ liệu hiệu quả. Sự tích hợp của nó với các sản
phẩm Firebase khác cho phép bạn xây dựng các ứng dụng thực
sự không có máy chủ.
Giám sát hiệu suất Bắt đầu nhanh để theo dõi hiệu suất
Hàm đám mây Mở rộng ứng dụng của bạn với mã phụ trợ tùy chỉnh mà không
cần quản lý và mở rộng quy mô máy chủ của riêng bạn. Các
chức năng có thể được kích hoạt bởi các sự kiện, được phát ra
từ các sản phẩm Firebase, dịch vụ Google Cloud hoặc bên thứ
ba, sử dụng webhooks.
Lập chỉ mục ứng dụng Bắt đầu nhanh để lập chỉ mục ứng dụng
Liên kết động Sử dụng Liên kết động để cung cấp trải nghiệm người dùng tùy
chỉnh cho iOS, Android và web. Bạn có thể sử dụng chúng để
tăng sức mạnh cho web di động để thúc đẩy chuyển đổi ứng
dụng gốc, chia sẻ người dùng sang người dùng, các chiến dịch
tiếp thị và xã hội, v.v. Liên kết động cung cấp cho bạn các bản
phân phối bạn cần để hiểu rõ hơn về sự phát triển di động của
bạn.
Lưu trữ Đơn giản hóa việc lưu trữ web của bạn với các công cụ được tạo
riêng cho các ứng dụng web hiện đại. Khi bạn tải lên tài sản web
của mình, Google sẽ tự động đẩy chúng ra CDN(1) toàn cầu của
chúng tôi và cấp cho họ chứng chỉ SSL miễn phí để người dùng
của bạn có được trải nghiệm an toàn, đáng tin cậy, độ trễ thấp,
bất kể họ ở đâu.

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


Mời Bắt đầu nhanh cho lời mời
Phân tích Phân tích các phân bổ và hành vi của người dùng trong một bảng
điều khiển duy nhất để đưa ra quyết định sáng suốt về lộ trình
sản phẩm của bạn. Có được thông tin chuyên sâu về thời gian
thực từ các báo cáo hoặc xuất dữ liệu sự kiện thô của bạn sang
Google BigQuery để phân tích tùy chỉnh.
Quảng cáo Bắt đầu nhanh cho AdMob
Tin nhắn trên đám mây Gửi tin nhắn và thông báo cho người dùng trên các nền tảng của
Android Android, iOS và web trực tuyến miễn phí. Tin nhắn có
thể được gửi đến các thiết bị đơn lẻ, nhóm thiết bị hoặc chủ đề
cụ thể hoặc phân khúc người dùng. Firebase Cloud Messaging
(FCM) mở rộng đến cả những ứng dụng lớn nhất, cung cấp hàng
trăm tỷ tin nhắn mỗi ngày.
Xác thực Mở rộng ứng dụng của bạn với mã phụ trợ tùy chỉnh mà không
cần quản lý và mở rộng quy mô máy chủ của riêng bạn. Các
chức năng có thể được kích hoạt bởi các sự kiện, được phát ra
từ các sản phẩm Firebase, dịch vụ Google Cloud hoặc bên thứ
ba, sử dụng webhooks.
4.2.1 CONTENT DELIVERY NETWORK
Content Delivery Network (CDN), là một mạng gồm nhiều máy chủ được đặt ở các vị trí
chiến lược trên toàn thế giới với mục đích phân phối nội dung số cho người dùng cuối
nhanh nhất có thể. Khi người dùng thực hiện một yêu cầu tới máy chủ CDN gần nhất, điều
đó giúp giảm đáng kể độ trễ mạng (latency). CDN cho phép tất cả người dùng, không thành
vấn đề là vị trí địa lý của họ ở đâu, có được tốc độ tải nội dung nhanh hơn và điều đó chắc
chắn cải thiện trải nghiệm người dùng.
4.2.2 DỮ LIỆU THỜI GIAN THỰC - FIREBASE REALTIME DATABASE
Cơ sở dữ liệu thời gian thực Firebase là một cơ sở dữ liệu được lưu trữ trên đám mây. Dữ
liệu được lưu trữ dưới dạng JSON (JavaScript Object Notation) và được đồng bộ hóa trong
thời gian thực cho mọi máy khách được kết nối. Khi bạn xây dựng các ứng dụng đa nền
tảng với SDK iOS, Android và JavaScript của Google, tất cả các khách hàng của bạn sẽ
chia sẻ một phiên bản Cơ sở dữ liệu thời gian thực và tự động nhận các bản cập nhật với
dữ liệu mới nhất. Các tính năng của cơ sở dữ liệu thời gian thực được trình bày như sau:
Thời gian thực: Thay vì các yêu cầu HTTP thông thường, Cơ sở dữ liệu thời gian thực
Firebase sử dụng đồng bộ hóa dữ liệu trong mỗi lần thay đổi dữ liệu, mọi thiết bị được kết
nối sẽ nhận được cập nhật đó trong vòng một phần nghìn giây. Cung cấp trải nghiệm hợp
tác và nhập vai mà không cần suy nghĩ về mã mạng.
Ngoại tuyến: Các ứng dụng Firebase vẫn phản hồi ngay cả khi ngoại tuyến vì SDK cơ sở
dữ liệu thời gian thực Firebase vẫn lưu dữ liệu của bạn vào đĩa. Khi kết nối được thiết lập
lại, thiết bị khách sẽ nhận được bất kỳ thay đổi nào đã bỏ lỡ, đồng bộ hóa với trạng thái
máy chủ hiện tại.

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


Truy cập từ thiết bị khách: Cơ sở dữ liệu thời gian thực Firebase có thể được truy cập
trực tiếp từ thiết bị di động hoặc trình duyệt web; không cần máy chủ ứng dụng. Xác thực
dữ liệu và bảo mật có sẵn thông qua Quy tắc bảo mật cơ sở dữ liệu thời gian thực Firebase,
quy tắc dựa trên biểu thức được thực thi khi dữ liệu được đọc hoặc ghi.
Quy mô trên nhiều cơ sở dữ liệu: Với cơ sở dữ liệu thời gian thực Firebase bạn có thể hỗ
trợ nhu cầu dữ liệu của ứng dụng theo tỷ lệ bằng cách chia dữ liệu của bạn qua nhiều phiên
bản cơ sở dữ liệu trong cùng một dự án Firebase. Hợp lý hóa xác thực với xác thực Firebase
trong dự án của bạn và xác thực người dùng trên các phiên bản cơ sở dữ liệu của bạn. Kiểm
soát truy cập dữ liệu trong mỗi cơ sở dữ liệu với Quy tắc cơ sở dữ liệu thời gian thực
Firebase tùy chỉnh cho từng phiên bản cơ sở dữ liệu
4.2.3 CÁCH THỨC HOẠT ĐỘNG CỦA CƠ SỞ DỮ LIỆU THỜI GIAN THỰC
Cơ sở dữ liệu thời gian thực Firebase cho phép bạn xây dựng các ứng dụng hợp tác, phong
phú bằng cách cho phép truy cập an toàn vào cơ sở dữ liệu trực tiếp từ mã phía máy khách.
Dữ liệu được duy trì cục bộ và ngay cả khi ngoại tuyến, các sự kiện thời gian thực vẫn tiếp
tục phát sinh, mang đến cho người dùng cuối trải nghiệm phản hồi.Khi thiết bị lấy lại kết
nối, cơ sở dữ liệu thời gian thực sẽ đồng bộ hóa các thay đổi dữ liệu cục bộ với các cập
nhật từ xa xảy ra trong khi máy khách ngoại tuyến, tự động hợp nhất mọi xung đột. Ngoài
ra nó còn cung cấp ngôn ngữ quy tắc dựa trên biểu thức linh hoạt, được gọi là quy tắc bảo
mật cơ sở dữ liệu thời gian thực Firebase, để xác định cách cấu trúc dữ liệu của bạn và khi
nào dữ liệu có thể được đọc hoặc ghi vào. Khi được tích hợp với xác thực Firebase, nhà
phát triển có thể xác định ai có quyền truy cập vào dữ liệu nào và cách họ có thể truy cập
dữ liệu đó.
Vì cơ sở dữ liệu thời gian thực là một cơ sở dữ liệu NoQuery do đó có các tối ưu hóa và
chức năng khác so với cơ sở dữ liệu quan hệ. API cơ sở dữ liệu thời gian thực được thiết
kế để chỉ cho phép các hoạt động có thể được thực hiện nhanh chóng. Điều này cho phép
bạn xây dựng trải nghiệm thời gian thực tuyệt vời có thể phục vụ hàng triệu người dùng
mà không ảnh hưởng đến khả năng đáp ứng. Do đó, điều quan trọng là phải suy nghĩ về
cách người dùng cần truy cập dữ liệu của bạn và sau đó cấu trúc dữ liệu phù hợp.

Bảng 4.2 Các bước thực hiện


Tích hợp SDK cơ sở dữ Nhanh chóng bao gồm các khách hàng thông qua Gradle,
liệu thời gian thực CocoaPods hoặc tập lệnh bao gồm.
Firebase
Tạo tài liệu tham khảo Tham chiếu dữ liệu JSON của bạn, chẳng hạn như "người
cơ sở dữ liệu thời gian dùng / người dùng: 1234 / phone_number" để đặt dữ liệu
thực hoặc đăng ký thay đổi dữ liệu.
Đặt dữ liệu và lắng nghe Sử dụng các tài liệu tham khảo này để ghi dữ liệu hoặc đăng
thay đổi ký thay đổi.
Kích hoạt tính năng bền Cho phép dữ liệu được ghi vào đĩa cục bộ của thiết bị để có
bỉ ngoại tuyến thể khả dụng khi ngoại tuyến.

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


Sử dụng Quy tắc bảo mật cơ sở dữ liệu thời gian thực
Bảo mật dữ liệu của bạn
Firebase để bảo mật dữ liệu của bạn.

Tóm lại ta hãy yên tâm sử dụng Google FireBase vì các lợi ích cốt lỗi sau:
• Triển khai ứng dụng cực nhanh: Firebase cung cấp cho bạn khá nhiều các API, hỗ trợ
đa nền tảng giúp bạn tiết kiệm thời gian quản lý cũng như đồng bộ dữ liệu cung cấp
hosting, hỗ trợ xác thực người dùng thì việc triển khai ứng dụng sẽ giảm được rất nhiều
thời gian phát triển.
• Bảo mật: Với việc sử dụng các kết nối thông qua giao thức bảo mật SSL hoạt động
trên nền tảng cloud đồng thời cho phép phân quyền người dùng database bằng cú pháp
javascipt cũng nâng cao hơn nhiều độ bảo mật cho ứng dụng của bạn.
• Sự ổn định: Firebase hoạt động dựa trên nền tảng cloud cung cấp bởi Google do đó
hãy yên tập về việc một ngày đẹp trời nào đó server ngừng hoạt động hay như DDOS
hoặc là tốc độ kết nối như rùa bò. Một điều đáng lưu ý nữa đó là do hoạt động trên nền
tảng Cloud vì vậy việc nâng cấp hay bảo trì server cũng diễn ra rất đơn giản mà không
cần phải dừng server.
4.3 XÂY DỰNG MỘT DỰ ÁN ĐƠN GIẢN SỬ DỤNG GOOGLE FIREBASE
4.3.1 BÀI TOÁN 1:
Điều khiển một đèn từ xa qua mạng internet sử dụng cơ sở dữ liệu thời gian thực của
FireBase. Phần cứng kết nối sử dụng NODMCU8266 khá đơn giản trong Hình 4.2. Chi
tiết về NODMCU8266 sẽ trình bày bên dưới ở bài tập tiếp theo.

Hình 4.2 Phần cứng kết nối sử dụng NODMCU8266

Tiếp theo ta viết một ứng dụng có tên là DieuKhienLedQuaFire với giao diện như sau:

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


Code mẫu file activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title"
android:textSize="25sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.063" />
<ImageView
android:id="@+id/imageView"
android:layout_width="200sp"
android:layout_height="200sp"
android:layout_marginStart="8dp"
android:layout_marginTop="104dp"
android:layout_marginEnd="8dp"
android:adjustViewBounds="true"
android:contentDescription="@string/denled"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.333"
app:layout_constraintStart_toStartOf="@+id/textView"

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


app:layout_constraintTop_toBottomOf="@+id/textView"
app:srcCompat="@drawable/lampoff" />
</androidx.constraintlayout.widget.ConstraintLayout>

Lưu ý: Ta phải có hai file hình ảnh lampon và lampoff được chép trong thư mục Drawable
của ứng dụng. Hai hình này thể hiện trạng thái led tắt và mở. Các bạn có thể tự vẽ lấy bằng
ứng dụng Paint hoặc có thể sưu tầm trên mạng.
Bước tiếp theo là ta sẽ liên kết ứng dụng vừa tạo với Firebase. Trước tiên bạn cần update
các phiên bản và thư viện cần thiết theo các chỉ dẫn bên dưới. Sau đó vào mục Setting:

Hình 4.3 Vào mục setting


Vào mục Android SDK cập nhật các Flatforms mới nhất

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


Hình 4.4 Vào mục Android SDK

Vào SDK Tools để cài đặt các Tool đánh dấu như hình bên dưới

Hình 4.5 Vào SDK Tools

Tiến hành liên kết ứng dụng với Firebase một cách tự động. Các bạn có thể làm thủ công,
tuy nhiên công việc phức tạp hơn và khá rắc rối. Các phiên bản Android Studio sau này,
việc liên kết với Firebase khá đơn giản, do Google tích hợp và hỗ trợ sẵn.
Đầu tiên bạn vào mục Tools\ Firebase từ ứng dụng của bạn đang mở

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


Hình 4.6 Vào mục Tools\ Firebase

Cửa sổ Firebase mở ra bên phải ứng dụng của bạn. Từ đây bạn có thể làm theo các hướng
dẫn của Google. Các hàm mẫu và chỉ dẫn cũng khá chi tiết. Nếu bạn muốn dùng Firebase
tạo cơ sở dữ liệu thời gian thực, bạn nhấn vào mục Realtime Database

Hình 4.7 Vào mục Realtime Database

Sau đó bạn nhấn tiếp vào mục Save and retrieve data. Bạn lần lượt thực hiện các bước tiếp
theo

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


Hình 4.8 Vào mục Save and retrieve data

Lúc này nếu trên Firebase bạn có project sẵn rồi bạn có thể thực hiện liên kết, hoặc bạn có
thể tạo mới nếu chưa có. Ở đây tôi tạo một Project mới tên là DieuKhienLedquaFirebase,
sau đó tôi vào mục Database thêm một Child tên là LED và tôi cho trạng thái mặc định ban
đầu là 0.

Hình 4.9 Kết quả Database thêm một Child tên là LED

Lưu ý: Bạn có thể đọc ghi dữ liệu lên Database của Firebase bạn vào mục Rules đặt các
thuộc tính read/write thành true như hình bên dưới.

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


Hình 4.10 Code trong mục Rules

Sau khi thực hiện xong việc liên kế ứng dụng với Firebase thì bạn sẽ thấy thông báo như
sau tại cửa sổ Firebase trong ứng dụng

Hình 4.11 Thông báo tại cửa sổ Firebase

Sử dụng các hàm Gởi và nhận dữ liệu từ Firebase như sau:


FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("LED");

Trong đó, tham số trong hàm getReference là tên của Child mà mình muốn gởi và nhận
giá trị (đã tạo trên Realtime Database). Ở đây ta dùng “LED” bởi vì trên Firebase ta đã tạo
một Child tên là “LED”.
Sử dụng hàm setValue để gởi dữ liệu Firebase:
myRef.setValue(1);

Để đọc giá trị Child cụ thể từ Database của Firebase ta dùng hàm bên dưới:
// Read from the database

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


mydata=FirebaseDatabase.getInstance().getReference();
mydata.child("tên_child").addValueEventListener(new ValueEventListener(){
@Override
public void onDataChange(@NonNull DataSnapshot tên_biến_trả_về){
//do somethings

}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}

Nhận giá trị từ Firebase khi có giá trị thay đổi:


myRef.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
String value = dataSnapshot.getValue(String.class);
});
Tiếp theo ta sẽ viết code Java cho ứng dụng tại file MainActivity.java. Cụ thể yêu cầu ở
đây là mỗi lần ta nhấn vào ImageView hình bóng đèn nó sẽ đảo trạng thái đèn, và hình trên
ImageView cũng thay đổi theo. Ví dụ mặc định ImageView hiển thị hình đèn tắt, tương
ứng với trạng thái đèn tắt. Khi ta nhấn vào hình này, nó sẽ điều khiển đèn sáng, hình
ImageView sẽ chuyển sang hình đèn sáng. Trạng thái đèn sáng/ tắt sẽ được cập nhật lên
Firebase thông qua Child tên là “LED”.
Ở chiều ngược lại nếu ta thay đổi giá trị Child “LED” trên firebase từ máy tính chẳng hạn,
thì hình trên ImageView cũng được hồi tiếp và cập nhật tương ứng.
package com.example.dieukhienledquafirebase;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
public class MainActivity extends AppCompatActivity {
DatabaseReference mydata;
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myRef = database.getReference("LED");
ImageView img_led;
Boolean tt_led=false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
img_led= findViewById(R.id.imageView);
img_led.setOnClickListener(new View.OnClickListener() {
@Override

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


public void onClick(View view) {
tt_led = !tt_led;
if(tt_led==true) {
myRef.setValue(1);
img_led.setImageResource(R.drawable.lampon);
}
else {
myRef.setValue(0);
img_led.setImageResource(R.drawable.lampoff);
}
}
});
// [START read_message]
// Read from the database\
mydata=FirebaseDatabase.getInstance().getReference();
mydata.child("LED").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot ttled_online) {
if(ttled_online.getValue().toString().equals("1")) {
img_led.setImageResource(R.drawable.lampon);
tt_led=true;
}
else {
img_led.setImageResource(R.drawable.lampoff);
tt_led=false;
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
}}
Chạy mô phỏng bằng máy ảo, ta thấy khi nhấn vào ImageView hình sẽ đảo trạng thái, dữ
liệu LED trên Firebase cũng đổi theo. Ngược lại ta thay đổi dữ liệu trên Firebase thì hình
trên ImageView cũng sẽ được cập nhật tương ứng.

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


(a)

(b)

Hình 4.12 Kết quả chạy trên máy ảo a) khi Led sáng b) khi Led tắt
Bước tiếp theo là phần cứng NODMCU8266 sẽ nhận giá trị “LED” từ firebase và điều
khiển đèn Led sáng tắt tương ứng.
Đoạn code viết cho NodMCU 8266 trên Arduino như sau:
#include <ESP8266WiFi.h>

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


#include <FirebaseArduino.h>
#define LED 3 //định nghĩa chân kết nối Led
// lấy đường link trong Project Firebase của bạn để vào “ “
#define FIREBASE_HOST "dieukhienledquafirebase.firebaseio.com"
//#define FIREBASE_AUTH "aabbcc" //nếu có thiết lập trên firebase điền thông
tin vào, không bỏ qua
# Thiết lập các thông số trong “ “ phù hợp với mạng wifi của bạn
#define WIFI_SSID "HCMUTE"
#define WIFI_PASSWORD "vnhcmute"
int state = 0;
void setup() {
Serial.begin(9600);
pinMode(LED, OUTPUT);
digitalWrite(LED, state);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
Serial.print("connecting");
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(1000); }
Serial.println();
Serial.print("connected: ");
Serial.println(WiFi.localIP());
Firebase.begin(FIREBASE_HOST);
//Firebase.begin(FIREBASE_HOST, FIREBASE_AUTH); nếu có thiết lập
Firebase_Auth
}
void loop() {
state =Firebase.getInt("LED");
delay(200);
digitalWrite(LED,state);
Serial.println(state);
}

4.3.2 BÀI TOÁN 2:


Ta có hai module cảm biến đo nhiệt độ, độ ẩm DHT11, hai đối tượng điều khiển là quạt và
đèn. Yêu cầu thiết kế là làm sao trên ứng dụng Android có thể hiển thị thông tin nhiệt độ
và trạng thái đèn, quạt. Từ điện thoại ta có điều khiển quạt đèn này ở bất cứ nơi nào có kết
nối mạng.
Phần cứng của hệ thống:
Ở đây tác giả dùng module ESP 8266 NodeMCU cho bộ xử lý giao tiếp hai module DHT11,
điều khiển giám sát đèn, quạt. Đồng thời module ESP8266 NodeMCU cũng thực hiện việc
kết nối, truyền nhận dữ liệu qua wifi.

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


Hình 4.13 Sơ đồ chân module NodeMCU 8266

NodeMCU là một nền tảng IoTs mã nguồn mở. Nó bao gồm firmware chạy trên ESP8266
Wi-Fi SoC từ Espressif Systems, và phần cứng là dựa trên các mô-đun ESP-12. NodeMCU
8266 được sử dụng rất rộng rãi trong các ứng dụng IoTs bởi nó vừa đóng vai trò là một
chip xử lý bao gồm luôn việc kết nối nối mạng. Các chân IO được thiết kế hầu hết phù hợp
và đáp ứng được các nhu cầu người dùng cơ bản. Hơn nữa cùng với mã nguồn mở của
người dùng, kết hợp với cộng đồng Ardunio lớn mạnh, NodeMCU luôn là sự lựa chọn cho
sinh viên bắt đầu với các ứng dụng điều khiển, giám sát từ xa.
Khi Arduino.cc bắt đầu phát triển các bo mạch MCU mới dựa trên các bộ xử lý không phải
là AVR như ARM / SAM MCU và được sử dụng trong Arduino Due, họ cần phải chỉnh
sửa Arduino IDE. Mục đích để hỗ trợ các công cụ thay thế cho phép Arduino C / C ++ biên
dịch các bộ xử lý mới này. Một số người đam mê ESP8266 đã phát triển lõi Arduino cho
SoC WiFi ESP8266, thường được gọi là "Lõi ESP8266 cho Arduino IDE". Điều này đã trở
thành một nền tảng phát triển phần mềm hàng đầu cho các mô-đun và kit phát triển dựa
trên ESP8266 khác nhau, bao gồm cả NodeMCU.
Module cảm biến nhiệt độ, độ ẩm DHT11: Cảm biến nhiệt độ và độ ẩm DHT11 có đầu
ra tín hiệu số được hiệu chỉnh. Nó được tích hợp với một vi điều khiển 8 bit hiệu suất cao.
Công nghệ của nó đảm bảo độ tin cậy và ổn định lâu dài tuyệt vời. Nó có chất lượng tốt,
đáp ứng nhanh, khả năng chống nhiễu và hiệu suất cao.
Mỗi cảm biến DHT11 có tính năng hiệu chuẩn cực kỳ chính xác. Giao tiếp truyền dữ liệu
số trên một dây nên khá thuận tiện trong việc tích hợp và mở rộng. Kích thước nhỏ, công
suất thấp, khoảng cách truyền tín hiệu lên tới 20 mét, cho phép nhiều ứng dụng và thậm
chí là những ứng dụng đòi hỏi khắt khe nhất. Sản phẩm đóng gói đưa ra 4 chân, trong đó
có một chân giả (NC).

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


Hình 4.14 Ảnh thực tế DHT11

Các đặc điểm chính của nó bao gồm:


• Điệp áp vận hành: 3.5v đến 5.5v.
• Dòng điện tiêu thụ: 0.3 mA (khi đang thực hiện việc đo lường) và 60uA (khi đang ở
chế độ standby).
• Dữ liệu ngõ ra số.
• Phạm vi nhiệt độ đo từ 00C đến 500C.
• Phạm vi độ ẩm đo từ 20% đến 90%.
• Độ phân giải: Cả nhiệt độ và độ ẩm đều là 16 bit.
• Độ chính xác: ±1°C and ±1%
Kết nối giữa vi xử lý với module DHT11

(a) (b)
Hình 4.15 Kết nối NodeMCU 8266 với DHT11 a) sơ đồ kết nối; b) kết nối trên hình thực tế

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


Định dạng dữ liệu được sử dụng cho truyền thông và đồng bộ giữa MCU và DHT11 là 40
bit gồm phần nguyên, phần thập phân và cảm biến sẽ gởi những bit dữ liệu trọng số cao
trước. Cụ thể định dạng như sau: 8 bit phần nguyên dữ liệu RH (dữ liệu độ ẩm) + 8 bit phần
thập phân dữ liệu RH + 8 bit phần nguyên dữ liệu T (dữ liệu nhiệt độ) + 8 bit phần thập
phân dữ liệu T + 8 bit checksum (dùng kiểm tra lỗi).
Phần cứng kết nối cơ bản cho hệ thống cần xây dựng là

Hình 4.16 Phần cứng kết nối cơ bản cho hệ thống


Xây dựng phần mềm
Bước 1: Xây dựng một ứng dụng Android với giao diện đơn giản như sau:

Hình 4.17 Xây dựng ứng dụng trên Android Studio

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


Đoạn code file activity_main.xml như sau:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/txt_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:text="@string/tv_title"
android:textColor="#F44336"
android:textSize="20sp"
app:layout_constraintBottom_toTopOf="@+id/lamp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView6"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="11dp"
android:text="@string/tv_nd1_title"
app:layout_constraintBaseline_toBaselineOf="@+id/temp1"
app:layout_constraintEnd_toEndOf="@+id/fan" />
<TextView
android:id="@+id/textView7"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tv_title_nd2"
app:layout_constraintBaseline_toBaselineOf="@+id/temp2"
app:layout_constraintStart_toStartOf="@+id/textView6" />
<TextView
android:id="@+id/temp1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
android:text="@string/nd1"
app:layout_constraintStart_toStartOf="@+id/temp2"
app:layout_constraintTop_toBottomOf="@+id/lamp" />
<TextView
android:id="@+id/temp2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="28dp"
android:text="@string/nd2"
app:layout_constraintStart_toEndOf="@+id/textView7"

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


app:layout_constraintTop_toBottomOf="@+id/temp1" />
<ImageButton
android:id="@+id/fan"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="2dp"
android:layout_marginEnd="3dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
app:layout_constraintBottom_toBottomOf="@+id/lamp"
app:layout_constraintEnd_toStartOf="@+id/lamp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@+id/lamp"
app:srcCompat="@drawable/fanoff"
android:contentDescription="@string/quat" />
<ImageButton
android:id="@+id/lamp"
android:layout_width="0dp"
android:layout_height="185dp"
android:layout_marginTop="154dp"
android:layout_marginEnd="2dp"
android:adjustViewBounds="true"
android:scaleType="fitCenter"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/fan"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/lampoff"
android:contentDescription="@string/den" />
</androidx.constraintlayout.widget.ConstraintLayout>
Các biến khai báo trong file Strings.xml
<resources>
<string name="app_name">Giam sat den quat nhiet do qua Firebase</string>
<string name="tv_title">Giám sát đèn quạt nhiệt độ từ xa</string>
<string name="tv_nd1_title">Nhiệt độ 1</string>
<string name="tv_title_nd2">Nhiệt độ 2</string>
<string name="nd1">ND1</string>
<string name="nd2">ND2</string>
<string name="quat">quat</string>
<string name="den">den</string>
</resources>
Như ta đã nói trước đó, ta sẽ dùng Firebase làm cơ sở dữ liệu cho ứng dụng. Việc đầu tiên
là ta sẽ liên kết ứng dụng Android của ta với FireBase.
Từ ứng dụng đang mở ta vào Tools nhấn vào FireBase để thực hiện việc liên kết vào dự án
Firebase một cách tự động như hướng dẫn ở bài tập 1 trước đó. Đây là cách làm nhanh và
ít sai số nhất so với các cách làm thủ công khác mà Google hỗ trợ người dùng. Nếu bạn có
sẵn dự án Firebase thì liên kết tới, không thì có thể tạo mới luôn lúc này một cách dễ dàng.

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


Hình 4.18 Tạo một dự án Firebase

Theo các bước hướng dẫn, tạo một dự án Firebase lưu trữ dữ liệu thời gian thực với các
mục như sau:

Hình 4.19 Một dự án Firebase lưu trữ dữ liệu thời gian thực

Sau khi đã thiết kế xong phần giao diện ứng dụng Android, tạo cơ sở dữ liệu thời gian thực
trên Firebase và liên kết chúng xong ta tiến hành viết code cho ứng dụng tại File
Main_Activity.java như sau:
package com.example.giamsatdenquatnhietdoquafirebase;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;

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


import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
public class MainActivity extends AppCompatActivity {
private TextView result_T1;
private TextView result_T2;
private ImageView img_fan;
private ImageView img_lamp;
boolean fan_status=false;
boolean lamp_status=false;
DatabaseReference mdata_T1;
DatabaseReference mdata_T2;
DatabaseReference mdatafan;
DatabaseReference mdatalamp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//ánh xạ
result_T1=findViewById(R.id.temp1);
result_T2=findViewById(R.id.temp2);
img_fan=findViewById(R.id.fan);
img_lamp=findViewById(R.id.lamp);
img_fan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
fan_status=!fan_status;
fan_onoff();
}
});
img_lamp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
lamp_status=!lamp_status;
lamp_onoff();
}
});
feedback();
}
private void fan_onoff()
{
mdatafan=FirebaseDatabase.getInstance().getReference();
if(fan_status)
{ img_fan.setImageResource(R.drawable.fanon);
mdatafan.child("Quat").setValue(1);
}
else
{
img_fan.setImageResource(R.drawable.fanoff);
mdatafan.child("Quat").setValue(0);
}
}

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


private void lamp_onoff()
{
mdatalamp=FirebaseDatabase.getInstance().getReference();
if(lamp_status)
{ img_lamp.setImageResource(R.drawable.lampon);
mdatafan.child("Den").setValue(1);
}
else
{
img_lamp.setImageResource(R.drawable.lampoff);
mdatafan.child("Den").setValue(0);
}
}
private void feedback()
{ //feedback the lamp
mdatalamp=FirebaseDatabase.getInstance().getReference();
mdatalamp.child("Den").addValueEventListener(new ValueEventListener()
{
@Override
public void onDataChange(@NonNull DataSnapshot dulieuden) {
if(dulieuden.getValue().toString().equals("1")) {
lamp_status=true;
img_lamp.setImageResource(R.drawable.lampon);
}

if(dulieuden.getValue().toString().equals("0")){
lamp_status=false;
img_lamp.setImageResource(R.drawable.lampoff);
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
//feedback fan
mdatafan=FirebaseDatabase.getInstance().getReference();
mdatafan.child("Quat").addValueEventListener(new ValueEventListener()
{
@Override
public void onDataChange(@NonNull DataSnapshot dulieuquat) {
if(dulieuquat.getValue().toString().equals("1")){
fan_status=true;
img_fan.setImageResource(R.drawable.fanon);
}
if(dulieuquat.getValue().toString().equals("0")){
fan_status=false;
img_fan.setImageResource(R.drawable.fanoff);
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
//feedback temp DHT1
mdata_T1=FirebaseDatabase.getInstance().getReference();
mdata_T1.child("Nhiet do DHT1").addValueEventListener(new
ValueEventListener() {

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


@Override
public void onDataChange(@NonNull DataSnapshot nhietdo1) {
result_T1.setText(nhietdo1.getValue().toString());
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
//feeback temp DHT2
mdata_T2=FirebaseDatabase.getInstance().getReference();
mdata_T1.child("Nhiet do DHT2").addValueEventListener(new
ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot nhietdo2) {
result_T2.setText(nhietdo2.getValue().toString());
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
}
}
Tới đây ta có thể chạy máy ảo để kiểm tra ứng dụng, kiểm tra hoạt động của Realtime Data
Firebase.

Hình 4.20 Hoạt động của Realtime Data Firebase

Nhấn vào các biểu tượng quạt/đèn để bật tắt, ta thấy trạng thái của nó trên Firebase cũng
thay đổi theo gần như tức thời. Và ngược lại khi bạn thay đổi từ Firebase thì trên ứng dụng
cũng có sự thay đổi tương ứng. Đặc biệt quan tâm khi thay đổi các giá trị nhiệt độ trên
Firebase thì bạn thấy nhiệt độ trên ứng dụng cũng thay đổi theo.

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


Bước 2: Xây dựng phần mềm cho ESP8266 bằng Ardunio
Như vậy cơ bản bạn đã hoàn thành 70% yêu cầu của việc xây dựng hệ thống điều khiển
đèn quạt nhiệt độ từ xa rồi. Tiếp theo ta tiến hành viết code cho ESP8266. Bạn hãy hình
dung như sau: Từ ứng dụng khi bạn điều khiển đèn/ quạt ở bất kỳ nơi nào có kết nối mạng,
thông tin của sự thay đổi này sẽ cập nhật lên Firebase. Việc của ESP8266 là kết nối đến
mạng, lấy thông tin dữ liệu này để điều khiển đèn quạt. Tương tự ở chiều ngược lại, dữ liệu
nhiệt độ của các cảm biến DHT11 được ESP8266 đọc sẽ chuyển lên Firebase, từ ứng dụng
của bạn do đã có liên kết thời gian thực với Firebase nên nó sẽ cập nhật các giá trị này trên
màn hình ứng dụng.
Các thư viện bằng sử dụng, các bạn down các thư viện này về và chép vào thư mục Ardunio
cho ứng dụng của mình, sau đó tiến hành code cho ESP như sau:
• Thư viện Arduino Json 5.x https://github.com/bblanchon/ArduinoJson
• Firebase: https://github.com/FirebaseExtended/firebase-arduino
#include <FirebaseArduino.h>
#include <ESP8266WiFi.h>
#include "DHT.h"
#define WIFISSID "HCMUTE" //tên mạng wifi thực tế bạn đang dùng kết
//nối cho ESP
#define WIFIPASSWORD "vnhcmute" //password của mạng wifi
#define FIREBASE_HOST "giamsat-thiet-bi-qua-firebase.firebaseio.com"
//khai báo Firebase host đúng dự án Firebase của bạn ở mục database
#define DHTTYPE DHT11
#define DHT1PIN 0
#define DHT2PIN 2
#define lamp 14
#define fan 12
DHT dht1(DHT1PIN, DHTTYPE); //khai bao kieu du lieu cho DHT
DHT dht2(DHT2PIN, DHTTYPE);
int ttlamp; //trạng thái đèn
int ttfan; //trạng thái quạt
void setup() {
Serial.begin(9600);
WiFi.disconnect();
WiFi.begin(WIFISSID,WIFIPASSWORD);
Serial.print("connecting....");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.println();
Serial.print("connected:");
Serial.println(WIFISSID);
Firebase.begin("FIREBASE_HOST");
dht1.begin();
dht2.begin();
pinMode(lamp,OUTPUT);
pinMode(fan,OUTPUT);
}
void loop() {
//gui du lieu len

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


float nd1 = dht1.readTemperature();
float nd2 = dht2.readTemperature();
Firebase.setInt("Nhiet do DHT1",nd1);
Firebase.setInt("Nhiet do DHT2",nd2);
//nhan du lieu ve
ttlamp=Firebase.getInt("lamp");
ttfan=Firebase.getInt("fan");
if(ttlamp==1)
digitalWrite(lamp,HIGH);
if(ttlamp==0)
digitalWrite(lamp,LOW);
if(ttfan==1)
digitalWrite(fan,HIGH);
if(ttfan==0)
digitalWrite(fan,LOW);
delay(1000);
}

Firebase_host ta lấy ở đây

Hình 4.21 Lấy firebase_host

Sau khi xây dựng xong hệ thống cả phần cứng và phần mềm, bạn có thể kiểm tra hoạt động
của nó. Hiển nhiên ở đây chúng ta chỉ xây dựng ứng dụng ở mức độ đơn giản nhất. Rất
nhiều trường hợp trong thực tế được bỏ qua. Cốt lỗi của vấn đề là làm sao để bạn có thể
điều khiển, giám sát các thiết bị từ xa.

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


CHƯƠNG 5 ĐIỀU KHIỂN THIẾT BỊ QUA TIN NHẮN
SMS
5.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. Như chúng ta đã biết, 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 nó phải là một thuê bao có thể nhận tin nhắn SMS. Thông thường, trong điều
khiển 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 phải gởi thông tin phản hồi. Trong trường hợp này người
ta thường sử 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ả sẽ trình bày cách 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ả sẽ đề 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ểm khác nhau là ở 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ả sẽ sử 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
Phần mềm điều SIM900 Thiết bị 1
khiển chạy trên
thiết bị Android
SMS Thiết bị 2
(Điều khiển và
Vi xử lý
giám sát thông
Thiết bị 3
qua SMS)

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


Hình 5.1 Hệ thống điều khiển thiết bị qua SMS
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ần mềm
được xây dựng để điều khiển. Ví dụ như 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 với số
điện thoại cài đặt), vi xử lý sẽ kiểm tra xem đây có đúng là 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

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


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. Lúc này
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.
Lưu ý: Phần này 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.
5.2 PHẦN MỀM TRÊN ĐIỆN THOẠI ANDROID
Mục tiêu ở đây là 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 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 cũng sẽ xử lý được
việc nhận tin nhắn phản hồi để chúng ta biết được trạng thái của thiết bị sau khi đã thực thi
lệnh điều khiển.
5.2.1 XÂY DỰNG GIAO DIỆN BẰNG PHẦN MỀM SMS_CONTROL
Giao diện ứng dụng được xây dựng đơn giản như sau:

Hình 5.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 hiệu chỉnh như sau:

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


Ở đâ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"
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.

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


<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 chúng ta cho phép (permission) việc gởi và nhận tin nhắn SMS nên phải
thêm các cho phép trong file app/manifests/AndroidManifest.xml. Chú ý các dòng uses-
permission
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sony.sms_control" >
<uses-permission
android:name="android.permission.SEND_SMS">
</uses-permission>
<uses-permission
android:name="android.permission.RECEIVE_SMS">
</uses-permission>
<uses-permission
android:name="android.permission.READ_SMS">
</uses-permission>
<uses-permission
android:name="android.permission.broadcast_sms">
</uses-permission>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Sau đó ta vào file MainActivity.java để viết code cho ứng dụng thực hiện yêu cầu. Ở đây
tác giả có sử dụng các dòng chú thích để giải thích các đoạn code trong chương trình. Tuy
nhiên phần bên dưới sẽ giải thích các vấn đề này một cách cụ thể hơn.
package com.example.sony.sms_control;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

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


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);
//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++)

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


{
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
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;
}
@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);
}
}

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


5.2.2 CHƯƠNG TRÌNH TRÊN HOẠT ĐỘNG NHƯ THẾ NÀO?
Đầu tiên xin nói về việc xử lý hai nút nhấn. Đơn giản là nếu đ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 giả sử 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”.
Tôi viết một hàm sendSMS (số thuê bao, nội dung) để thực hiện việc gởi tin nhắn SMS.
Công việc khá đơn giản vì chúng ta được hỗ trợ bởi các hàm có sẵn và chỉ cần xây dựng
chú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à chúng ta cần nhận được các thông tin phản hồi, chẳng hạn khi 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 nhằm để người dùng biết bộ điều khiển đã được thực thi xong lệnh.
Đố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 Activity
điều khiển của mình. Ở đây tôi chỉ đơn giản muốn là khi thiết bị đã tắt hoặc mở Led thì nó
sẽ gởi tin nhắn phản hồi về số điện thoại của chúng ta. Và ứng dụng của chúng 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 thì nó mới
kiểm tra nội dung. Nếu nội dung trả về là “1234 ON1” thì ứng dụng sẽ xuất ra thông báo
“The light is on”, còn nếu nội dung là “1234 OFF1” thì thông báo 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 cần kiểm tra xem có đúng số thuê bao trên không rồi mới kiểm tra
nội dung tin nhắn. Lệnh bên dưới giúp kiểm tra 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"))

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


{
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 chỉ 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 muốn xử lý bất cứ khi nào tin nhắn đến, không quan tâm đến việc ứ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ả.
5.3 BỘ ĐIỀU KHIỂN
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. Tất nhiên,
không thể trình bày tất cả những điều đó ở đây. Tác giả chỉ gợi mở cho bạn hướng đi, vì
phần chính của quyển sách này là các ứng dụng trên Android.
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 phép 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.
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 và tự thiết kế mọi thứ để tránh các thiết kế “thừa” thì bạn nê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 module 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ợ bốn dãy tần số.

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


Hình 5.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 5.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.

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


Hình 5.5 Board GPRS Shield-EFCom

5.4 KẾT NỐI PHẦN CỨNG VÀ CHƯƠNG TRÌNH ARDUINO


Lưu ý: Ở đây là 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 đề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. Vì vậy, mong muốn của quyển sách là trình bày cách sử 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. Ngôn ngữ lập trình các board Ardunio khá dễ hiểu đối với các bạn đã từng lập
trình vi xử lý.
Trở lại phần board mạch điều khiển. Chúng ta cần module SIM900 để 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 5.6). Vi điều khiển có hỗ trợ phần cứng
truyền dữ liệu UART chuẩn qua các chân 0; 1 nên việc sử dụng hai chân này để giao tiếp với
module SIM900 là rất hợp lý, code thu được sẽ tối ưu hơn. Tuy nhiên ở đây tôi muốn trình bày
một cách làm 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.

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


Hình 5.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 chúng ta lại nhận đượ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 mở rộng module kết nối dễ
dàng trong trường hợp cần nhiều hơn một cổng UART. Thứ hai là 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.
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 5.7), ta có phần cứng kết nối sẽ sử dụng như sau:

Hình 5.7 Giao tiếp SIM900 qua hai chân khác chân Tx, Rx mặc định
Lưu ý: Chúng ta hoàn toàn có thể làm theo phần cứng như hình 3.6, khi đó code cho
Ardunio chỉ cần chỉnh lại cho phù hợp.
Đối tượng điều khiển của chúng ta trong mạch khá quen thuộc và đơn giản: một led đơn
3mm. Trên board Arduino có các chân I/O dùng cho mục đích xuất nhập. Ta chọn ngẫu
nhiên chân số 5 kết nối đến led đơn. Các bạn chú ý nên dùng một điện trở hạn dòng cho

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


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.
Ngoài ra các bạn lưu ý phần lập trình cần 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.
#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
//thoạ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 tinnhắ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()
{
receive_uart();
delay(200);
xu_ly_sms();

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


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(inttime)
{
Unsigned long tnow =millis();
while ((millis() – tnow) <time) {
if (SIM900.available()) {
tempchar = (char)SIM900.read();
if (tempchar ==‘\r’) {
Serial.print(“/R/”);
}
elseif (tempchar ==‘\n’) {
Serial.println(“/N/”);
}
elseSerial.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;
}
}

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


//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;
}
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 ý: trong khung dữ liệu tin nhắn đến chứa cả số thuê bao
và thông điệp tin nhắn. Và việc thực hiện giao tiếp giữa vi điều khiển và module SIM được
thực hiện theo chuẩn UART. Bạn hoàn toàn có thể viết chương trình ngắt cổng nối tiếp
UART để xử lý việc nhận tin nhắn đến cho đơn giản. Ở đây chúng ta xây dựng một hàm
con cho việc nhận tin nhắn vì không dùng các chân Tx, Rx chuẩn của Arduino, và khi
dùng hàm con thì buộc phải thực hiện phương thức hỏi vòng (polling) để kiểm tra xem có
tin nhắn đến hay không, nghĩa là hàm con này phải được lặp lại liên tục. Như vậy, điều này
chỉ phù hợp với các hệ thống xử lý đơn giản, khối lượng xử lý ít.
Chúng ta xem xét hàm con nhận dữ liệu bên dưới:
void receive_uart()//hàm nhận tin nhắn và lưu vào mảng //buff1
{
while ((SIM900.available() == 0)) {}
while (SIM900.available() > 0)

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


{
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 thoại bằng
cách tìm 12 ký tự bắt đầu bằng +84. Tìm trong nội dung tin nhắn có dãy ký tự là 1234 ON1
hay 1234 OFF1 không? Nếu có thì ta sẽ cho led sáng hoặc tắt tương ứng (tác động lên
chân I/O kết nối led đơn), và phản hồi về số điện thoại điều khiển bằng tin nhắn The light
is ON hay The light is OFF tương ứng với trạng thái led đơn đã điều khiển.
void xu_ly_sms() //xử lý chuỗi vừa nhận
{
//tìm vị trí số điện thoại bắt đầu bằng “+8”
for(i=0;i<k;i++)
{
if((buff1[i]=='+')&&(buff1[i+1]=='8')){
index=i;
break;
}
}
//tách số điện thoại từ mảng buff1 chứa vào mảng sdt
j=0;
for(i=index;i<(index+12);i++){
sdt[j]=buff1[i];
j++;
}
ktsdt=true;
for(i=0;i<13;i++)
{
if(sdt[i]!=number[i])
{
ktsdt=false;
break;
}
}
if(ktsdt==true){
if(strstr(buff1,"1234 ON1")!=NULL){
digitalWrite(13,HIGH);
sendSMS("the light is ON");
}
if(strstr(buff1,"1234 OFF1")!=NULL){
digitalWrite(13,LOW);
sendSMS("the light is OFF");
}
}
}

Hàm gửi tin nhắn: dựa vào các lệnh AT của Module SIM để ta viết hàm này. Bạn hoàn
toàn có thể dựa vào các lệnh bên dưới để gởi tin nhắn tới số điện thoại để bỏ qua công đoạn
tìm hiểu tập lệnh AT, nhưng nhớ phải thay thế số điện thoại của thiết bị phù hợp.
void sendSMS(String message)
{
SIM900.print(“AT+CSCS=\”GSM\”\r\n”); //lệnh AT để gửi tin

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


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 dụng Android trên, khi gửi tin nhắn điều khiển tắt mở thiết bị
thì Arduino sẽ thực hiện bật tắt đèn và trả lời lại bằng tin nhắn tương ứng để thông báo
trạng thái.

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


CHƯƠNG 6 SỬ DỤNG GOOGLE MAPS TRONG ỨNG
DỤNG GIÁM SÁT, ĐỊNH VỊ
Sử dụng dịch vụ Google Maps giúp ích cho chúng ta rất nhiều trong việc xây dựng các ứng
dụng tìm kiếm địa chỉ, xác định vị trí đối tượng hoặc tìm đường đi. Ngoài ra, một trong
những ứng dụng rất phổ biến đối với lĩnh vực điện tử đó là điều khiển và giám sát đối tượng
thông qua vị trí của đối tượng. Dựa vào tọa độ nhận được từ thiết bị chuyên dụng gắn trên
đối tượng di chuyển truyền về, chúng ta có thể biết chính xác vị trí của đối tượng trên bản
đồ và tốc độ di chuyển của đối tượng dựa vào đó có thể đưa ra các lệnh điều khiển phù
hợp. Một trong những ứng dụng thực tế phổ biến các bạn hay sử dụng dịch vụ định vị của
bản đồ đó là ứng dụng gọi xe Grab, Uber. Ngày nay các ứng dụng định vị ngày đa dạng
như giám sát xe buýt, giám sát xe cá nhân, chỉ đường và đo tốc độ trên các camera hành
trình. Ứng dụng định vị còn được dùng trong việc theo dõi sức khỏe, an toàn cho người
già, trẻ nhỏ, ... Chương này chúng ta sẽ cùng tìm hiểu về cách thiết kế ứng dụng để hiển
thị bản đồ và xác định vị trí của một hoặc nhiều đối tượng trên bản đồ dựa vào dịch vụ
Google Maps.
6.1 GPS LÀ GÌ ?
GPS (Global Positioning System) là hệ thống xác định vị trí dựa trên vị trí của các vệ tinh
nhân tạo, do Bộ Quốc phòng Hoa Kỳ thiết kế, xây dựng, vận hành và quản lý. Trong cùng
một thời điểm, tọa độ của một điểm trên mặt đất sẽ được xác định nếu xác định được
khoảng cách từ điểm đó đến ít nhất ba vệ tinh.
6.1.1 CÁCH THỨC HOẠT ĐỘNG CỦA VỆ TINH
Có tổng cộng 26 vệ tinh định vị được Mỹ đưa lên quỹ đạo không gian. Các vệ tinh GPS
bay vòng quanh Trái Đất hai lần trong một ngày theo một quỹ đạo rất chính xác và phát tín
hiệu có thông tin xuống Trái Đất. Các máy thu GPS nhận thông tin này và bằng phép tính
lượng giác tính được chính xác vị trí của người dùng.
Máy thu phải nhận được tín hiệu của ít nhất ba vệ tinh để tính ra vị trí hai chiều (kinh độ
và vĩ độ) và để theo dõi được chuyển động, sau đó máy thu có thể tính được vị trí ba chiều
(kinh độ, vĩ độ và độ cao). Một khi vị trí người dùng đã tính được thì máy thu GPS có thể
tính các thông tin khác, như tốc độ, hướng chuyển động, bám sát di chuyển, khoảng hành
trình, quãng cách tới điểm đến, thời gian Mặt Trời mọc, lặn và nhiều thứ khác nữa.
Các máy thu GPS ngày nay cực kì chính xác, nhờ vào thiết kế nhiều kênh hoạt động song
song của chúng. Giống như các thiết của Garmin, nó có tổng cộng 12 kênh song song, có
khả năng khóa định vị các vệ tinh, di trì kết nối bền, giúp định vị tốt khi thiết bị đi qua vùng
cây cối rậm rạp, tòa nhà dày đặc...Hiện tại khi các thiết GPS đặt ngoài trời trống thì khả
năng định vị rất tốt, độ chính xác có thể đạt được dưới 3m.

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


Vệ tinh GPS

Tín hiệu GPS

Trạm nhận
Tín hiệu GPS tín hiệu
GPS

Thông tin hỗ
Thiết bị cầm tay có trợ
GPS và GSM Trung tâm Dịch vụ hỗ
Trạm gốc dịch vụ di trợ GPS
động

Trạm gốc

Hình 6.1 Hệ thống định vị toàn cầu GPS

6.1.2 CÁCH XÁC ĐỊNH VỊ TRÍ THEO KINH ĐỘ VÀ VĨ ĐỘ


Việc xác định vị trí khá phải sử dụng khá nhiều công thức lượng giác phức tạp. Các bạn có
thể tham khảo thêm chi tiết ở trang www.courses.psu.edu. Ở đây ta chỉ nói về ý tưởng đơn
giản như sau: trên bản đồ có 3 điểm cố định A, B, C. Dữ liệu GPS cho bạn biết khoảng
cách lần lượt từ điểm A, B, C đến nơi bạn đứng là x, y, z. Điểm giao nhau của ba vòng tròn
có tâm là A, B, C với bán kính lần lượt x, y, z chính là vị trí của bạn.

Hình 6.2 Xác định vị trí bằng công thức lượng giác

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


Như vậy để xác định được vị trí của bạn cần thông tin ít nhất của ba vệ tinh. Để đưa ra vị
trí chính xác, rất nhiều thiết bị GPS kết nối tới ít nhất là 4 vệ tinh. Đó là lý do vì sao đôi
khi để tìm ra vị trí chính xác của bạn, hệ thống GPS lại mất nhiều thời gian tới vậy.
6.2 GOOGLE MAPS
Google Maps là một dịch vụ bản đồ số được Google phát triển với mục đích thay thế cho
các loại bản đồ giấy thông thường. Để sử dụng Google Maps một cách chính xác nhất bạn
cần GPS - hệ thống định vị toàn cầu giúp bạn có thể biết rõ vị trí hiện tại của bản thân và
thông qua GPS con người có thể dễ dàng xác định được phương hướng và đường đi một
cách nhanh chóng nhất có thể. Hiện tại Google Maps có mặc trên hầu hết các smartphone.
Dịch vụ Google Maps không chỉ mỗi định vị. Google Maps mạnh mẽ ở điều hướng, chỉ
đường. Và hiện tại các dịch vụ quảng cáo địa điểm, địa chỉ kinh doanh, đánh giá địa điểm
đang được rất ưa chuộng. Google Maps được sự đóng góp liên tục từ công đồng người
dùng từ việc tạo địa điểm, đánh giá, bổ sung hình ảnh, video, ....

Hình 6.3 Hình biểu tượng của ứng dụng Google Maps mới cập nhật

Hình 6.4 Giao diện Google Maps trên điện thoại

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


6.2.1 XÂY DỰNG ỨNG DỤNG ĐƠN GIẢN GIÁM SÁT THIẾT BỊ DÙNG GOOGLE MAPS
Các yêu cầu đặt ra là làm sao để nhúng bản đồ vào ứng dụng của chúng ta, sử dụng nó để
định vị thiết bị. Trong thực tế các ứng dụng dùng Maps hiện rất nhiều, ví dụ như các ứng
dụng gọi xe Grab, GoViet, Vinasun, ... Các ứng dụng giao hàng, mua thức ăn dùm là những
phần mềm quen thuộc với hầu hết các bạn trẻ dùng điện thoại thông minh. Một ứng dụng
khác bây giờ cũng khá được quan tâm là ứng dụng chống trộm, giám sát xe. Chúng ta có
một chiếc xe máy chẳng hạn, chúng ta có thể giao cho người thân, bạn bè mượn, trong một
số trường hợp, chúng ta có nhu cầu chống trộm, theo dõi hành trình xe. Khi đó ta cần thiết
kế một mạch điều khiển tích hợp module sim có GPS. Khi cần thông tin định vị xe, hoặc
điều khiển xe từ xa, ta có thể gởi tin nhắn SMS để yêu cầu. Trên điện thoại Android, ta viết
ứng dụng đọc thông tin tọa độ hiển thị lên bản đồ Maps, thiết kế các nút nhấn điều khiển
để gởi tin nhắn điều khiển cho xe.
Ở phần trình bày này, tôi bỏ qua việc thiết kế board mạch điều khiển trên xe. Tôi xây dựng
hai ứng dụng chạy trên hai điện thoại Android (hoặc hai thiết bị giả lập). Trên điện thoại
giám sát ta xây dựng một ứng dụng theo dõi hành trình của điện thoại kia bằng cách gởi
tin nhắn yêu cầu thông tin vị trí và hiển thị vị trí điện thoại kia lên bản đồ. Còn ở điện thoại
bị giám sát ta cũng viết một ứng dụng, khi nhận được đúng tin nhắn yêu cầu thì gởi thông
tin vị trí cho điện thoại yêu cầu qua tin nhắn SMS. Mục tiêu sau khi xây dựng hai dụng này
xong, ta hiểu được hoạt động của Google Maps, hiểu cách nhúng Maps vào một Activity
trên ứng dụng. Đồng thời qua đây ta ôn lại cách xử lý gởi và nhận tin nhắn SMS. Với yêu
cầu trên, đầu tiên ta phải hiểu biết các nhúng Maps vào ứng dụng, sử dụng Maps xác định
vị trí dựa vào dữ liệu kinh độ, vĩ độ.

Hình 6.5 Theo dõi định vị thiết bị từ một thiết bị khác

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


6.2.2 VIẾT CHƯƠNG TRÌNH CHO ỨNG DỤNG XÁC ĐỊNH VỊ TRÍ BẰNG GOOGLE MAPS
Bước 1: Khởi tạo Project mới
Đầu tiên, chạy chương trình Android Studio, ta chọn phần New Project, sau đó chọn vào
Google Maps Activity

Hình 6.6 Tạo một Projet mới

Bước 2: Xin key API của Google Maps


Để được phép sử dụng Google Maps trong chương trình ta viết thì việc đầu tiên ta phải xin
được API key của Google. Các bước thực hiện như sau:
• Truy cập vào trang web Google Cloud Platform Console
(https://console.cloud.google.com/)
• Click vào menu và chọn APIs & Services > Credentials như trong hình

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


Hình 6.7 Các bước tiến hành xin API key
• Trong trang Credentials , click Create credentials > API key. API key created sẽ
hiển thị ra API key mà bạn vừa tạo.

Hình 6.8 Tạo 1 API key cho ứng dụng


• Vào phần mềm Android Studio, Vào file google_maps_api.xml ta điền dòng key vừa
lưu ở trên vào chỗ “YOUR API KEYS”

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


Cách đơn giản hơn để xin API key cho ứng dụng Google Maps đó là sau khi bạn tạo một
ứng dụng Google theo mẫu sẵn như trên, bạn nhìn vào file Google_maps_api.xml vừa tạo.
Trong file có hướng dẫn chi tiết các bước, bạn chỉ nhấn vào đường link đã cho trong file
để lấy API key và sau khi có API key bạn điền thay thế cho dòng chữ “Your_Key_here”
trong chính file này. Các bước chi tiết theo các hình minh họa bên dưới.

Hình 6.9 File Google_maps_api.xml

Hình 6.10 Vào trang web xin API key

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


Hình 6.11 Tạo một API Key

Hình 6.12 API key sau khi đã tạo

Hình 6.13 Điền API key vào file .xml


Bước 3: Thêm dịch vụ Google vào ứng dụng

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


Trong phần mềm Android Studio ta chọn File > Project Structure hoặc bấm Ctrl + Alt+
Shift + S. Sau đó tìm đến phần Dependencies > chọn vào mục
com.google.android.gms::play-service-maps bấm OK để thêm ứng dụng Google Maps
vào chương trình đang viết.

Hình 6.14 Thêm dịch vụ của Google vào chương trình

Bước 4: Thiết kế giao diện chính

Hình 6.15 Giao diện chính của ứng dụng xác định vị trí
Với ứng dụng này ta xây dựng giao diện gồm một <fragment> chứa Google Maps. Ta thêm
hai editText để nhập cũng như hiển thị kinh độ, vĩ độ. Người dùng có thể nhập kinh độ, vĩ
độ vào hai ô này và nhấn nút “Tìm”, khi đó vị trí tương ứng sẽ hiển thị trên Maps. Khi bạn
nhấn vào nút “Yêu cầu” thì thiết bị sẽ gởi một tin nhắn yêu cầu đến thiết bị cần định vị.
Trong bài này thực ra là ta gởi tin nhắn yêu cầu đến số điện thoại của thiết bị ta cần giám

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


sát. Khi thiết bị nhận được tin nhắn đúng cú pháp, nó sẽ gởi ngược về thông tin định vị
GPS. Từ thông tin này ta sẽ trích ra kinh độ và vĩ độ hiển thị trên hai EditText kinh độ, vĩ
độ. Khi đó ta nhấn tiếp nút nhấn Tìm thì nó sẽ tìm và định vị trên Google Maps. Thật ra
các thao tác có vẻ hơi nhiều và không hoàn toàn tự động mục đích để phân tích các bước
và để bạn dễ dàng nắm bắt được nội dung hơn.
Bước 5: Yêu cầu thiết bị cấp quyền cho ứng dụng
Để ứng dụng có thể hoạt động ta phải yêu cầu điện thoại cho phép ứng dụng sử dụng các
quyền cơ bản để ứng dụng có thể hoạt động.
Vào AndroidMandifest.xml ta thêm vào các đoạn code sau để cấp quyền, bao gồm quyền
truy cập vị trí, quyền truy cập internet, quyền truy cập tin nhắn SMS.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" ></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.SEND_SMS"></uses-
permission>
<uses-permission
android:name="android.permission.ACCESS_NETWORK_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-
permission>
<uses-permission android:name="android.permission.BROADCAST_SMS"
tools:ignore="ProtectedPermissions" />
Đây là các quyền như yêu cầu vị trí, truy cập Internet, nhận biết và đọc tin nhắn tới ngay
trong ứng dụng.
Bước 6: Viết code chính trong file MapsActivity.java
Yêu cầu của ứng dụng là hiển thị ứng dụng bản đồ trên Activity. Lưu ý mỗi phiên bản
Android Studio cập nhật sẽ có một vài sự thay đổi, do đó việc sao chép toàn bộ code vào
có thể phát sinh một số lỗ cần chỉnh sửa để phù hợp.
package com.example.map;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;

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


import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MapsActivity extends FragmentActivity implements
OnMapReadyCallback {
private GoogleMap mMap;
private Button btn_search, btn_send;
private EditText text_kinhdo, text_vido;
BroadcastReceiver receiver =null;
String sms_kinhdo = "", sms_vido = "";
String sms = "";
String address;
String body="";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
btn_send=(Button)findViewById(R.id.btn_send);
btn_search=(Button)findViewById(R.id.btn_search);
text_kinhdo=(EditText)findViewById(R.id.txt_kinhdo);
text_vido=(EditText)findViewById(R.id.txt_vido);
IntentFilter filter_sms = new
IntentFilter("android.provider.Telephony.SMS_RECEIVED");
// Obtain the SupportMapFragment and get notified when the map is
ready to be used.
SupportMapFragment mapFragment = (SupportMapFragment)
getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
int permission_INTERNET = ContextCompat.checkSelfPermission(this,
Manifest.permission.INTERNET);
int permission_RECEIVE_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.RECEIVE_SMS);
int permission_SEND_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.SEND_SMS);
int permission_ACCESS_COARSE_LOCATION =
ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION);
if(permission_INTERNET != PackageManager.PERMISSION_GRANTED
|| permission_SEND_SMS != PackageManager.PERMISSION_GRANTED
|| permission_RECEIVE_SMS !=
PackageManager.PERMISSION_GRANTED
|| permission_ACCESS_COARSE_LOCATION !=
PackageManager.PERMISSION_GRANTED){
makeRequest();
}
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{

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


String messageToSend = "SEND";
String number = "+84xxxxxxxxxx";
SmsManager.getDefault().sendTextMessage(number, null,
messageToSend, null,null);
}
catch(Exception e){
Toast.makeText(getApplicationContext(), " yêu cầu thất
bại.",Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(), "Yêu cầu vị
trí.",Toast.LENGTH_LONG).show();
}
});
btn_search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"Tìm vị trí theo kinh
độ, vĩ đô.",Toast.LENGTH_LONG).show();

String chuoi1 = text_kinhdo.getText().toString();


String chuoi2 = text_vido.getText().toString();
String qtr_trong= "";
final double kinhdo;
final double vido;
if ((chuoi1.equals(qtr_trong))&&(chuoi2.equals(qtr_trong)))
{
vido= 10.851214;
kinhdo=106.772012;
LatLng sydney = new LatLng(vido, kinhdo);

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("home"));
}
else
{
kinhdo=Float.parseFloat(chuoi1);
vido=Float.parseFloat(chuoi2);
LatLng sydney = new LatLng(vido, kinhdo);

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("vị trí hiện tại"));
}
}
});
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String sms_extra= "pdus";
Bundle bundle= intent.getExtras();
Object[] smsArr = (Object[]) bundle.get(sms_extra);
for ( int i= 0; i< smsArr.length; i++){
SmsMessage smsMessage= SmsMessage.createFromPdu((byte[])
smsArr[i]);
body = smsMessage.getMessageBody();
address= smsMessage.getDisplayOriginatingAddress();

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


sms= "ban_co_tin_nhan_tu:\n"+ address +
"\nNoi_dung_la:\n"+ body;
}
if (address.equals("+84xxxxxxxxx"))
{
String[] words =sms.split("\\s");
for (String w:words){
System.out.println(w);
};
sms_kinhdo=words[3];
sms_vido=words[4];
text_kinhdo.setText(sms_kinhdo);
text_vido.setText(sms_vido);
}
};
};
registerReceiver(receiver, filter_sms);
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the
camera. In this case,
* we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will
be prompted to install
x`x * it inside the SupportMapFragment. This method will only be
triggered once the user has
* installed Google Play services and returned to the app.
*/
@Override public void onDestroy(){
super.onDestroy();
unregisterReceiver(receiver);
}

@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;

// Add a marker in Sydney and move the camera


LatLng sydney = new LatLng(10.851214, 106.772012);
mMap.addMarker(new MarkerOptions().position(sydney).title("HOME"));
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
}
protected void makeRequest() {
ActivityCompat.requestPermissions(this, new
String[]{Manifest.permission.INTERNET,
Manifest.permission.RECEIVE_MMS,
Manifest.permission.SEND_SMS,
Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
}
}

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


6.2.3 CHƯƠNG TRÌNH TRÊN HOẠT ĐỘNG NHƯ THẾ NÀO?
Trong MapsActivity.java đã khai báo sẵn biến mMap, ta khai báo thêm các biến cần dùng
như biến sms_kinhdo, sms_ vido kiểu chuỗi, biến receiver kiểu BroadcastReceiver giao
tiếp với tin nhắn, các biến text_kinhdo, text_vido, btn_search, btn_send là các biến riêng
để ánh xạ với các đối tượng của trang giao diện chính (main activity). Ngoài ra còn có các
biến như sms là biến của tin nhắn gửi tới, biến adrress là địa chỉ (số điện thoại) của tin
nhắn gửi vị trí tới, body là biến nội dung tin nhắn nhằm xác định kinh độ, vĩ độ.
private GoogleMap mMap;
private Button btn_search, btn_send;
private EditText text_kinhdo, text_vido;
BroadcastReceiver receiver =null;
String sms_kinhdo = "", sms_vido = "";
String sms = "";
String address;
String body="";
Ánh xạ các biến từ giao diện chính của ứng dụng. Hàm getMapAsysnc sẽ hiển thị Map lên
Activy chính . Biến filter_sms là bộ lọc tin nhắn. Các dòng lệnh kế tiếp là để kiểm tra các
quyền của thiết bị đã được cho phép hay chưa nếu chưa thì gửi lại yêu cầu cho phép ( hàm
makeRequest).
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
btn_send=(Button)findViewById(R.id.btn_send);
btn_search=(Button)findViewById(R.id.btn_search);
text_kinhdo=(EditText)findViewById(R.id.txt_kinhdo);
text_vido=(EditText)findViewById(R.id.txt_vido);
IntentFilter filter_sms = new
IntentFilter("android.provider.Telephony.SMS_RECEIVED");
// Obtain the SupportMapFragment and get notified when the map is ready
to be used.
SupportMapFragment mapFragment = (SupportMapFragment)
getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);

int permission_INTERNET = ContextCompat.checkSelfPermission(this,


Manifest.permission.INTERNET);
int permission_RECEIVE_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.RECEIVE_SMS);
int permission_SEND_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.SEND_SMS);
int permission_ACCESS_COARSE_LOCATION =
ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION);
if(permission_INTERNET != PackageManager.PERMISSION_GRANTED
|| permission_SEND_SMS != PackageManager.PERMISSION_GRANTED
|| permission_RECEIVE_SMS != PackageManager.PERMISSION_GRANTED

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


|| permission_ACCESS_COARSE_LOCATION !=
PackageManager.PERMISSION_GRANTED){
makeRequest();

Xây dựng hàm cho trình xử lý sự kiện nhấn nút “Yêu cầu”.
Khi nhấn nút “Yêu cầu”, ứng dụng sẽ lập tức gửi tin nhắn đến thiết bị cần xác định vị trí,
yêu cầu thiết bị đó gửi lại tin nhắn có chưa kinh độ và vĩ độ. Lưu ý số điện thoại của thiết
bị ta sẽ gởi tin nhắn đến, cú pháp tin nhắn ta có thể tự quy ước và làm theo. Lệnh Toast
hiển thị trạng thái của việc yêu cầu lên giao diện chính.
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
String messageToSend = "SEND"; //nội dung tin nhắn yêu cầu
String number = "+84xxxxxxxxx"; //số điện thoại của thiết bị.
SmsManager.getDefault().sendTextMessage(number, null,
messageToSend, null,null);
}
catch(Exception e){
Toast.makeText(getApplicationContext(), " yêu cầu thất
bại.",Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(), "Yêu cầu vị
trí.",Toast.LENGTH_LONG).show();
}
});
Trong đó, number là số điện thoại của thiết bị cần xác định vị trí, messageToSend là nội
dung của tin nhắn yêu cầu. Câu lệnh SmsManager.getDefault().sendTextMessage là câu
lệnh gửi tin nhắn một cách tự động mà người dùng không cần phải thao tác.
Hàm nút nhấn tìm kiếm :
btn_search.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"Tìm vị trí theo kinh độ, vĩ
đô.",Toast.LENGTH_LONG).show();
String chuoi1 = text_kinhdo.getText().toString();
String chuoi2 = text_vido.getText().toString();
String qtr_trong= "";
final double kinhdo;
final double vido;
if ((chuoi1.equals(qtr_trong))&&(chuoi2.equals(qtr_trong)))
{
vido= 10.851214;
kinhdo=106.772012;
LatLng sydney = new LatLng(vido, kinhdo);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("home"));
}
else
{

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


kinhdo=Float.parseFloat(chuoi1);
vido=Float.parseFloat(chuoi2);
LatLng sydney = new LatLng(vido, kinhdo);
mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new MarkerOptions().position(sydney).title("vị trí
hiện tại"));
}
}
});
Khi bấm hàm Toast sẽ hiện thị câu lệnh cho người dùng biết ứng dụng đã được bấm vào
nút tìm kiếm. Nếu như giá trị kinh độ và vĩ độ còn bỏ trống trên màn hình (tức là chưa có
giá trị được nhận vào) thì giá trị của nó sẽ được trả về giá trị mặc định của hai biến kinhdo
và vido cho trước. Ngược lại, khi kinh độ và vĩ độ nhận được giá trị thì giá trị đó sẽ được
gán cho biến kinhdo và vido. Hai biến này chính là giá trị con của hàm LatLing – đây là
hàm có sẵn của ứng dụng Google Maps. Khi đó bản đồ sẽ dịch chuyển tới vị trí như trên
và có một mũi tên đánh dấu ngay vị trí đó.
Ta dùng nhận tin nhắn thông qua đối tượng BroadcastReceiver có tên là receiver. Việc gởi
và nhận dữ liệu qua tin nhắn SMS bạn có thể tham khảo chương điều khiển đối tượng qua
tin nhắn SMS. Trong hàm BroadcastReceiver ta tiến hành:
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String sms_extra= "pdus";
Bundle bundle= intent.getExtras();
Object[] smsArr = (Object[]) bundle.get(sms_extra);
for ( int i= 0; i< smsArr.length; i++){
SmsMessage smsMessage= SmsMessage.createFromPdu((byte[])
smsArr[i]);
body = smsMessage.getMessageBody();
address= smsMessage.getDisplayOriginatingAddress();
sms= "ban_co_tin_nhan_tu:\n"+ address + "\nNoi_dung_la:\n"+
body;
}
if (address.equals("+84xxxxxxxxxx"))
{
String[] words =sms.split("\\s");
for (String w:words){
System.out.println(w);
}
sms_kinhdo=words[3];
sms_vido=words[4];
text_kinhdo.setText(sms_kinhdo);
text_vido.setText(sms_vido);
}
};
};
registerReceiver(receiver, filter_sms);
}
Khai báo biến sms_extra kiểu chuỗi có nội dung “pdus” để nhận gói tin nhắn. Sau đó khai
báo đóng gói bundle để nhận gói dữ liệu. Khai báo biến body và address đển lưu nội dung

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


tin nhắn và địa chỉ. Dùng vòng lặp for để kiểm tra và phân tích tin nhắn được gửi tới. Nội
dung tin nhắn được lưu vào biến body. Cuối cùng là lấy địa chỉ số điện thoại của thiết bị
gửi tin nhắn lưu vào biến address.
Tiếp theo kiểm tra số điện thoại gửi tin nhắn có phải là số điện thoại ta cần định vị. Tin
nhắn đến sẽ chứa hai thông số là kinh độ và vĩ độ cách nhau bằng dấu cách. Biến sms là
biến tin nhắn sẽ có cấu trúc như sau:
“ ban_co_tin_nhan_tu:

address

Noi_dung_la:

body”

Trên thiết bị android của ta sẽ nhận được và tiến hành tách làm 2 biến phân biệt bởi dấu
cách (khoảng trắng). Biến thứ nhất là words [3] tương tứng với kinh độ và biến 2 là words
[4] là vĩ độ. Sau đó ta tiến hành chuyển đổi kiểu dữ liệu cho 2 biến và cập nhật lại bản đồ
bằng cách nhấn nút tìm kiếm ngay lập tức thì nó sẽ thực hiện lại hàm nút nhấn và bản đồ
sẽ được cập nhật lại vị trí. Dòng lệnh registerReceiver được dùng để kiểm tra mỗi khi có
tin nhắn tới.
Toàn bộ file MapsActivity.java
package com.example.map;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
public class MapsActivity extends FragmentActivity implements
OnMapReadyCallback {
private GoogleMap mMap;
private Button btn_search, btn_send;

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


private EditText text_kinhdo, text_vido;
BroadcastReceiver receiver =null;
String sms_kinhdo = "", sms_vido = "";
String sms = "";
String address;
String body="";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
btn_send=(Button)findViewById(R.id.btn_send);
btn_search=(Button)findViewById(R.id.btn_search);
text_kinhdo=(EditText)findViewById(R.id.txt_kinhdo);
text_vido=(EditText)findViewById(R.id.txt_vido);
IntentFilter filter_sms = new
IntentFilter("android.provider.Telephony.SMS_RECEIVED");
// Obtain the SupportMapFragment and get notified when the map is
ready to be used.
SupportMapFragment mapFragment = (SupportMapFragment)
getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
int permission_INTERNET = ContextCompat.checkSelfPermission(this,
Manifest.permission.INTERNET);
int permission_RECEIVE_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.RECEIVE_SMS);
int permission_SEND_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.SEND_SMS);
int permission_ACCESS_COARSE_LOCATION =
ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION);
if(permission_INTERNET != PackageManager.PERMISSION_GRANTED
|| permission_SEND_SMS != PackageManager.PERMISSION_GRANTED
|| permission_RECEIVE_SMS !=
PackageManager.PERMISSION_GRANTED
|| permission_ACCESS_COARSE_LOCATION !=
PackageManager.PERMISSION_GRANTED){
makeRequest();
}
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try{
String messageToSend = "SEND";
String number = "+849668708xx";
SmsManager.getDefault().sendTextMessage(number, null,
messageToSend, null,null);
}
catch(Exception e){
Toast.makeText(getApplicationContext(), " yêu cầu thất
bại.",Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(), "Yêu cầu vị
trí.",Toast.LENGTH_LONG).show();
}
});
btn_search.setOnClickListener(new View.OnClickListener() {

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


@Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(),"Tìm vị trí theo kinh
độ, vĩ đô.",Toast.LENGTH_LONG).show();
String chuoi1 = text_kinhdo.getText().toString();
String chuoi2 = text_vido.getText().toString();
String qtr_trong= "";
final double kinhdo;
final double vido;
if ((chuoi1.equals(qtr_trong))&&(chuoi2.equals(qtr_trong)))
{
vido= 10.851214;
kinhdo=106.772012;
LatLng sydney = new LatLng(vido, kinhdo);

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("home"));
}
else
{
kinhdo=Float.parseFloat(chuoi1);
vido=Float.parseFloat(chuoi2);
LatLng sydney = new LatLng(vido, kinhdo);

mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(sydney,18));
mMap.addMarker(new
MarkerOptions().position(sydney).title("vị trí hiện tại"));
}
}
});
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String sms_extra= "pdus";
Bundle bundle= intent.getExtras();
Object[] smsArr = (Object[]) bundle.get(sms_extra);
for ( int i= 0; i< smsArr.length; i++){
SmsMessage smsMessage= SmsMessage.createFromPdu((byte[])
smsArr[i]);
body = smsMessage.getMessageBody();
address= smsMessage.getDisplayOriginatingAddress();
sms= "ban_co_tin_nhan_tu:\n"+ address +
"\nNoi_dung_la:\n"+ body;
}
if (address.equals("+84966870820"))
{
String[] words =sms.split("\\s");
for (String w:words){
System.out.println(w);
};
sms_kinhdo=words[3];
sms_vido=words[4];
text_kinhdo.setText(sms_kinhdo);
text_vido.setText(sms_vido);
}
};

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


};
registerReceiver(receiver, filter_sms);
}
/**
* Manipulates the map once available.
* This callback is triggered when the map is ready to be used.
* This is where we can add markers or lines, add listeners or move the
camera. In this case,
* we just add a marker near Sydney, Australia.
* If Google Play services is not installed on the device, the user will
be prompted to install
x`x * it inside the SupportMapFragment. This method will only be
triggered once the user has
* installed Google Play services and returned to the app.
*/
@Override public void onDestroy(){
super.onDestroy();
unregisterReceiver(receiver);
}
@Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
// Add a marker in Sydney and move the camera
LatLng sydney = new LatLng(10.851214, 106.772012);
mMap.addMarker(new MarkerOptions().position(sydney).title("HOME"));
mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney));
}
protected void makeRequest() {
ActivityCompat.requestPermissions(this, new
String[]{Manifest.permission.INTERNET,
Manifest.permission.RECEIVE_MMS,
Manifest.permission.SEND_SMS,
Manifest.permission.ACCESS_COARSE_LOCATION}, 1);
}
}

6.3 VIẾT CHƯƠNG TRÌNH CHO THIẾT BỊ NHẬN TIN NHẮN YÊU CẦU
VÀ GỬI LẠI VỊ TRÍ
Thực tế ta xây dựng một thiết bị có thể nhận, gởi tin nhắn SMS và có thể định vị GPS.
Thông thường ta sẽ dùng Module Sim để làm việc này. Khi đó nếu nó nhận được tin nhắn
từ số điện thoại yêu cầu, nội dung đúng cú pháp quy định nó sẽ gởi ngược lại tin nhắn chứa
thông tin vị trí. Từ đó ứng dụng yêu cầu sẽ định vị và hiển thị trên google Maps như phần
đã nói ở trên. Nhưng vì chương tin nhắn SMS tác giả đã trình bày riêng, các bạn có thể
tham khảo và kết hợp với bài này. Ở đây, tác giả xây dựng một ứng dụng tiếp theo, ứng
dụng này sẽ cài trên một điện thoại cần định vị. Khi có tin nhắn yêu cầu xác định vị trí, nó
sẽ lấy thông tin vị trí và gởi ngược lại số điện thoại yêu cầu. Vì ứng dụng này chỉ cần lấy
dữ liệu trong máy là GPS và phần nào đó là công cụ Network hỗ trợ việc xác định vị trí
nên chúng ta không cần phải xin API mà tới thẳng phần viết ứng dụng.

6.3.1 THIẾT KẾ GIAO DIỆN CHÍNH


Giao diện chính đơn giản chỉ hiện thỉ giá trị vị trí kinh độ và vĩ độ của thiết bị.

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


Hình 6.16 Giao diện chính của ứng dựng thông báo vị trí

6.3.2 YÊU CẦU THIẾT BỊ CẤP QUYỀN CHO ỨNG DỤNG


Tương tự như ứng dụng xác định vị trí, ứng dụng gửi vị trí cũng cần có các quyền cơ
bản như yêu cầu vị trí, gửi tin nhắn, truy cập Internet, nhận biết và đọc tin nhắn tới ngay
trong ứng dụng.

Hình 6.17 Các yêu cầu quyền cơ bản cho ứng dụng

6.3.3 PHẦN CODE CHINH TRONG MAINSACTIVITY.JAVA


Hàm Locationlistener là hàm lắng nghe về vị trí, biến locationManager được khai báo dạng
protected là có thể truy cập bên trong hoặc cả bên ngoài gói (package). Ở đây, chúng ta
vẫn có hàm BroadcastReceiver để quan sát sự thay đổi của thiết bị dùng để phát hiện được
tin nhắn. Biến txtlat là biến thể hiện giá trị kinh độ và vĩ độ lên giao diện chính theo kiểu
TextView.
public class MainActivity extends Activity implements LocationListener {
public String kd;
public String vd;

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


protected LocationManager locationManager;
BroadcastReceiver receiver =null;
String address;
String body="";
String sms="";
TextView txtLat;
Tiếp theo, chúng ta tiếp tục ánh xạ các biến và kiểm tra quyền của ứng dụng

protected void onCreate(Bundle savedInstanceState) {


super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtLat = (TextView) findViewById(R.id.textview1);
IntentFilter filter_sms = new
IntentFilter("android.provider.Telephony.SMS_RECEIVED");
int permission_INTERNET = ContextCompat.checkSelfPermission(this,
Manifest.permission.INTERNET);
int permission_SEND_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.SEND_SMS);
int permission_RECEIVE_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.RECEIVE_SMS);
int permission_ACCESS_COARSE_LOCATION =
ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION);
if(permission_INTERNET != PackageManager.PERMISSION_GRANTED
|| permission_RECEIVE_SMS != PackageManager.PERMISSION_GRANTED
|| permission_SEND_SMS != PackageManager.PERMISSION_GRANTED
|| permission_ACCESS_COARSE_LOCATION !=
PackageManager.PERMISSION_GRANTED) {
makeRequest();
}
locationManager =
(LocationManager)getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0,
this);
Biến locationManager là biến quản lý vị trí được khai báo ở trên. getSystermService nhằm
mục đích lấy dịch vụ của hệ thống mà ở đây là dịch vụ vị trí. requestLocationUpdates dùng
để yêu cầu cập nhật lại vị trí của thiết bị cụ thể là qua GPS.
public void onLocationChanged(final Location location) {
txtLat = (TextView) findViewById(R.id.textview1);
txtLat.setText("Kinh độ:" + location.getLongitude()+"\n Vĩ độ:" +
location.getLatitude());
kd= Double.toString(location.getLongitude());
vd= Double.toString(location.getLatitude());
Hàm onlocationChanged dùng để biểu thị sự thay đổi của giá trị kinh độ và vĩ độ. Giá trị
thay đổi này sẽ thay đổi và cập nhật liên tục, sau đó sẽ được hiển thị lên TextView của giao
diện chính với cấu trúc là “Kinh độ : giá trị của kinh độ Vĩ độ: giá trị của vĩ độ ”. kd và
vd là giá trị lần lượt của kinh độ và vĩ độ được chuyển từ dạng Double sang chuỗi (String).
Mục đích của việc này là để đưa được giá trị này vào tin nhắn gửi trả về ứng dụng xác định
vị trí thiết bị
receiver = new BroadcastReceiver() {
@Override

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


public void onReceive(Context context, Intent intent) {
String sms_extra = "pdus";
Bundle bundle = intent.getExtras();
Object[] smsArr = (Object[]) bundle.get(sms_extra);
for ( int i= 0; i< smsArr.length; i++) {
SmsMessage smsMessage = SmsMessage.createFromPdu((byte[])
smsArr[i]);
body = smsMessage.getMessageBody();
address = smsMessage.getDisplayOriginatingAddress();
}
if (address.equals("+84xxxxxx")&& body.equals("SEND"))
{
try{
sms = kd+" "+vd ;
String number = "+84xxxxxxx";
SmsManager.getDefault().sendTextMessage(number, null, sms,
null,null);
}
catch(Exception e){
Toast.makeText(getApplicationContext(), " Gửi vị trí thất
bại.",Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(), "Đã gửi vị
trí.",Toast.LENGTH_LONG).show();
}
}
};
registerReceiver(receiver, filter_sms);
Sau khi nhận được tin nhắn, bộ lọc Filter sẽ tiến hành cắt các phần của tin nhắn yêu cầu
thành address tức số điện thoại body tức nội dụng tin nhắn. Sau đó kiểm tra nếu đúng số
điện thoại và nội dung trùng với lệnh yêu cầu mà ta đã đặt trước thì ứng dụng sẽ gửi tin
nhắn kèm vị trí phản hồi ngược lại tới ứng dụng xác định vị trí sms là nội dung gồm giá trị
kinh độ và vĩ độ ở dạng chuỗi, number là số điện thoại nhận.
Toàn bộ phần code của file .java
package com.example.device;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;

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


import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements LocationListener {
public String kd;
public String vd;
protected LocationManager locationManager;
BroadcastReceiver receiver =null;
String address;
String body="";
String sms="";
TextView txtLat;
@RequiresApi(api = Build.VERSION_CODES.M)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
txtLat = (TextView) findViewById(R.id.textview1);
IntentFilter filter_sms = new
IntentFilter("android.provider.Telephony.SMS_RECEIVED");
int permission_INTERNET = ContextCompat.checkSelfPermission(this,
Manifest.permission.INTERNET);
int permission_SEND_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.SEND_SMS);
int permission_RECEIVE_SMS = ContextCompat.checkSelfPermission(this,
Manifest.permission.RECEIVE_SMS);
int permission_ACCESS_COARSE_LOCATION =
ContextCompat.checkSelfPermission(this,
Manifest.permission.ACCESS_COARSE_LOCATION);
if(permission_INTERNET != PackageManager.PERMISSION_GRANTED
|| permission_RECEIVE_SMS !=
PackageManager.PERMISSION_GRANTED
|| permission_SEND_SMS != PackageManager.PERMISSION_GRANTED
|| permission_ACCESS_COARSE_LOCATION !=
PackageManager.PERMISSION_GRANTED) {
makeRequest();
}
locationManager = (LocationManager)
getSystemService(Context.LOCATION_SERVICE);
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
0, 0, this);
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String sms_extra = "pdus";
Bundle bundle = intent.getExtras();
Object[] smsArr = (Object[]) bundle.get(sms_extra);
for ( int i= 0; i< smsArr.length; i++) {
SmsMessage smsMessage =
SmsMessage.createFromPdu((byte[]) smsArr[i]);
body = smsMessage.getMessageBody();
address = smsMessage.getDisplayOriginatingAddress();
}
if (address.equals("+84868610997")&& body.equals("SEND"))
{
try{
sms = kd+" "+vd ;

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


String number = "84868610997";
SmsManager.getDefault().sendTextMessage(number,
null, sms, null,null);
}
catch(Exception e){
Toast.makeText(getApplicationContext(), " Gửi vị
trí thất bại.",Toast.LENGTH_LONG).show();
}
Toast.makeText(getApplicationContext(), "Đã gửi vị
trí.",Toast.LENGTH_LONG).show();
}
}
};
registerReceiver(receiver, filter_sms);
}
@Override
public void onLocationChanged(final Location location) {
txtLat = (TextView) findViewById(R.id.textview1);
txtLat.setText("Kinh độ:" + location.getLongitude()+"\n Vĩ độ:" +
location.getLatitude());
kd= Double.toString(location.getLongitude());
vd= Double.toString(location.getLatitude());
}
@Override
public void onProviderDisabled(String provider) {
Log.d("Latitude","disable");
}
@Override
public void onProviderEnabled(String provider) {
Log.d("Latitude","enable");
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.d("Latitude","status");
}

protected void makeRequest() {


ActivityCompat.requestPermissions(this, new
String[]{Manifest.permission.INTERNET,
Manifest.permission.RECEIVE_MMS,
Manifest.permission.SEND_SMS,
Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
}

6.4 MÔ PHỎNG
Phía ứng dụng của thiết bị cần theo dõi:

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


Hình 6.18 Giao diện chính hiện thị vị trí

Sau khi nhận được tin nhắn yêu cầu, ứng dụng sẽ tự động gửi tin nhắn kèm vị trí phản hồi

Hình 6.19 Tin nhắn yêu cầu gửi tới và phản hồi bằng tin nhắn vị trí

Phía ứng dụng theo dõi vị trí

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


Hình 6.20 Giao diện chính của ứng dụng theo dõi vị trí

Sau khi gửi yêu cầu ứng dụng sẽ nhận được tin nhắn phản hồi kèm với vị trí gồm 2 thành
phần là kinh độ và vĩ độ sẽ được hiện thị ra trên giao diện. Người dùng sẽ nhấn nút “Tìm”
để Google Maps dẫn tới vị trí đó trên bản đồ

Hình 6.21 Giá trị được biểu thị và vị trí thiết bị được hiển thị trên bản đồ

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


CHƯƠNG 7 ĐIỀU KHIỂN VÀ GIÁM SÁT THIẾT BỊ
QUA BLUETOOTH
7.1 ĐẶT VẤN ĐỀ
Với mục đích điều khiển và giám sát thiết bị trong phạm vi gần, ví dụ như Smart Home thì
việc sử dụ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 phí. Chúng ta đã biết
hầu các smartphone đề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.48GHz 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,…

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

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

Hình 7.1 Sơ đồ điều khiển thiết bị bằng Bluetooth

Trong chương này, chúng ta sẽ thiết kế một hệ thống đơn giản gồm một phần mềm chạy
trên điện thoại Android có các nút nhấn điều khiển đóng mở thiết bị, khi người dùng tác
động trên phần mềm, các lệnh điều khiển sẽ gởi đến bộ điều khiển thông qua giao thức
Bluetooth. Bộ điều khiển được thiết kế có khả năng kết nối Bluetooth với điện thoại. Khi
nhận được lệnh điều khiển từ điện thoại nó sẽ điều khiển thiết bị đóng mở. Thiết bị điều
khiển ở đây chính là các led cho trực quan và đơn giản. Khi điều khiển xong các thiết bị
thì nó phản hồi trở lại điện thoại để cập nhật trạng thái thiết bị. Tương tự như khi điều khiển

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


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. Mô hình của hệ thống điều
khiển và giám sát thiết bị đơn giản được trình bày ở hình 7.1. Nếu chúng ta gửi một lệnh
điều khiển từ ứng dụng Android sang bộ điều khiển thiết bị, ví dụ ứng dụng có các nút điều
khiển ON, OFF thì khi nhấn vào nút nhấn ON thì ứng dụ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 bị 1 và gửi lại một thông báo thông qua
Bluetooth để ứng dụng nhận biết đã gửi thành công.
7.2 PHẦN MỀM TRÊN ĐIỆN THOẠI ANDROID
7.2.1 XÂY DỰNG ỨNG DỤNG ĐƠN GIẢN
Như đã trình bày ở trên, mục đích của ứng dụng này là chỉ mô phỏng đơn giản việc điều
khiển thiết bị, khi nhấn vào ImageView hình bóng đèn thì sẽ gửi một ký tự “lệnh” đến bộ
điều khiển thông qua Bluetooth, và 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 dụ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
lại một chuỗi với nội dung là “ON”. Ứng dụng nhận lạ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 lại khi
đèn đang sáng, nhấn vào ImageView thì ứng dụ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 “OFF”. Ứng dụng nhận phản hồi và đổi biểu
tượng ImageView thành bóng đèn đang tắt.

Hình 7.2 Giao diện điều khiển của ứng dụng

Khi khởi động ứng dụng, giao diện sẽ như trên, ấn vào Scan để quét các thiết bị
Bluetooth xung quanh. Danh sách các thiết bị được hiển thị ra, sau đó nhấn vào thiết bị
muốn ghép nối, đánh mật khẩu để kết nối với thiết bị đó. Khi kết nối hoàn tất thì việc điều
khiển đơn giản chỉ là thao tác chạm vào ImageView bóng đèn trên màn hình. Ở đây chúng
ta dùng một ImageView là hình một bóng đèn sáng và một bóng tắt. Nếu bóng đèn đang

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


tắt mà chúng 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 mà chúng ta chạm vào thì nó sẽ gởi
lệnh điều khiển đèn tắt.

Hình 7.3 Giao diện 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 trạng thái của thiết bị; ListView để hiện các thiết bị Bluetooth mà ứng dụng tìm
được; Button Scan để dò tìm Bluetooth của các thiết bị khác.
Giao diện được thiết kế có nội dung như sau:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivity"
android:orientation="vertical"
android:weightSum="100"
android:id="@+id/layout_me">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"

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


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 cho phép (permission) việc gởi và nhận qua Bluetooth nên ta phải thêm các
cho phép trong file app/manifests/AndroidManifest.xml. Chú ý các dòng uses-permission
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ngb.bluetooth_control" >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Sau đó vào file MainActivity.java để viết code cho ứng dụng thực hiện yêu cầu. Ở đây tác
giả cũng có sử dụng các dòng chú thích để giải thích các đoạn code trong chương trình.
Phần giải thích chi tiết hơn sẽ được trình bày phía dưới.

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


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;
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à giaodiệ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);
//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

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


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

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


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

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


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

7.2.2 CHƯƠNG TRÌNH TRÊN HOẠT ĐỘNG NHƯ THẾ NÀO?


Đầu tiên chúng ta nói về việc sử dụng gói Bluetooth API trong Android Studio như thế
nào:
Để 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

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


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 dụng khởi động, yêu cầu kích hoạt tính năng Bluetooth của thiết bị 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 bị chưa bật Bluetooth thì bật Bluetooth bằng Intent
Intent turnOnIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
StartActivityForResult(turnOnIntent, 1);

Nếu thiết bị đã bật Bluetooth thì xuất 1 thông báo ra Toast "Bluetooth is already on".
Tiếp đến là việc xử lý nút Scan để quét các thiết bị đang hoạt động:
btn_scan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
find();
}
});

Tạo một lắng nghe cho nút Scan, khi nhấn vào thì gọi hàm find();
Đây là hàm dùng để quét các thiết bị đang hoạt động trong phạm vi có thể kết nối của
Bluetooth
public void find() {
if (myBluetoothAdapter.isDiscovering()) {
myBluetoothAdapter.cancelDiscovery();
}
else {
BTArrayAdapter.clear();
myBluetoothAdapter.startDiscovery();
registerReceiver(bReceiver, new
IntentFilter(BluetoothDevice.ACTION_FOUND));
}
}
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(bReceiver);
}

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


Tiếp theo là lấy tên của thiết bị lưu vào Arraylist ID, lấy địa chỉ của thiết bị 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();
Adress.add(device.getAddress());
}
}
};

Dùng Array adapter để hiển thị tên của các thiết bị trong Arraylist ID.
BTArrayAdapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, ID);
list_device.setAdapter(BTArrayAdapter);

Khi nhấn vào tên của một trong những thiết bị quét được thì kết nối với thiết bị đó thông
qua một lắng nghe được đăng ký.
list_device.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long
id) {
BtAddress = Adress.get(position);
Connect();
} });

Trong lắng nghe này tôi gọi một hàm Connect để kết nối tới thiết bị mong muốn.
public void Connect() {
Log.d(TAG, BtAddress);
BluetoothDevice device = myBluetoothAdapter.getRemoteDevice(BtAddress);
Log.d(TAG, "Connecting to ... " + device);
myBluetoothAdapter.cancelDiscovery();
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
btSocket.connect();
Log.d(TAG, "Connection made.");
} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log.d(TAG, "Unable to end the connection");
}
Log.d(TAG, "Socket creation failed");
}}

Vậy là hoàn thành các bước cơ bản để kết nối với một thiết bị Bluetooth quét được.
Khi muốn điều khiển thiết bị chỉ cần chạm vào ImageView thì ứng dụng sẽ gửi lệnh điều
khiển nhờ vào một lắng nghe đã được đăng ký.
img_light.setOnClickListener(new View.OnClickListener() {
@Override

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


public void onClick(View v) {
if(light_status) { writeData("0");}

else writeData("1");
beginListenForData();
}
});

Trong lắng nghe này tôi gọi hai hàm con khác. Hàm writeData() để gửi ký tự đến bộ điều
khiển. Tùy thuộc vào trạng thái đèn mà dữ liệu tôi gửi sẽ khác nhau. Nếu đèn đang sáng
(light_status=true) thì tôi sẽ gởi số “0” để ra lệnh tắt đèn và ngược lại. Hàm
beginListenForData() để nhận dữ liệu phản hồi từ bộ điều khiển.
private void writeData(String data) {
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.d(TAG, "Bug BEFORE Sending stuff", e);
}
String message = data;
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.d(TAG, "Bug while sending stuff", e);
String msg = "Sự cố! Vui lòng thực hiện kết nối lại";
}}

Trong hàm beginListenForData thì tôi dùng một biến dạng String là Data để lưu ký tự nhận
về, nếu bộ điều khiển trả về là “ON” thì thay đổi ImageView là biểu tượng bóng đèn sáng
và đặt biến trạng thái đèn light_status là true. Ngược lại nếu dữ liệu trả về là “OFF” thì ta
thay đổi ImageView là biểu tượng đèn tắt và đặt biến trạng thái đèn light_status là false.
Tất cả những hàm trên đã được xây dựng sẵn và các bạn có thể lấy để làm tài nguyên cho
những ứng dụng tương tự.
7.3 BỘ ĐIỀU KHIỂN
Chức năng của bộ điều khiển là nhận tín hiệu điều khiển từ ứng dụng sau khi điều khiển
thiết bị xong sẽ gửi lại phản hồi về điện thoại, tất cả đều thông qua Bluetooth.
Đề tài này tôi chọn Module Bluetooth HC05/HC06 là thiết bị để giao tiếp Bluetooth giữa
điện thoại và bộ điều khiển.

Hình 7.4 Module Bluetooth HC05/06

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


Đây là module Bluetooth phổ biến và thông dụ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ế để có 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.
Đặ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 phần này được 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, giao thức đã được hỗ trợ bởi phần
cứng. Ta có sơ đồ kết nối như hình 7.5.

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


Hình 7.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 phải tìm hiểu tập lệnh AT mới điều khiển Module SIM được. Còn với board HC-
05 mọi thứ trở nên đơn giản hơn vì việc nhận dữ liệu và gởi dữ liệu hoàn toàn qua cổng
nối tiếp chuẩn. Ardunio hỗ trợ việc giao tiếp này khá nhiều qua các hàm Serial. Sau khi
nhận dữ liệu qua cổng UART ta nên sử dụng ngắt cho các ứng dụng phức tạp để giảm thời
gian hỏi vòng và thực hiện quá trình xử lý ngay khi có tín hiệu từ Bluetooth gởi đến. Ứng
dụng ở đây khá đơn giản nên ta chỉ sử dụng chương trình con cho việc nhận dữ liệu từ cổng
nối tiếp.
Yêu cầu của chương trình là khi nhận dữ liệu điều khiển từ điện thoại gởi xuống thông qua
giao thức Bluetooth truyền đến HC-05, dữ liệu được chuyển đến cổng nối tiếp của vi điều
khiển, vi điều khiển sẽ phân tích lệnh điều khiển để tắt/mở đèn led. Sau khi điều khiển led
xong, vi điều khiển sẽ phản hồi trạng thái led về phần mềm chạy trên điện thoại bằng cách
gởi một dữ liệu theo quy định đến HC-05 thông qua cổng nối tiếp. Hoạt động giao tiếp vi
điều khiển với thiết bị thông qua Bluetooth sử dụng module HC-05/06 được dùng khá phổ
biến bởi mức độ tiện dụng và đơn giản của nó. Các bạn nên nắm vững các hàm điều khiển
cơ bản để phục vụ cho việc nghiên cứu các chương tiếp theo.
Lưu ý: Nhiều bạn không chuyên về lãnh vực điện tử có thể thắc mắc là tại sao tác giả hay
chọn đối tượng điều khiển là led đơn mà không phải là một đèn dây tốc 220VAC, động cơ,
quạt…? Xin nhắc lại: chúng ta chọn led đơn là để đơn giản tối đa phần cứng, các bạn có
thể kiểm nghiệm ngay hoạt động một cách trực quan, và với board Ardunio mà bạn mua
về thì việc kết nối thêm led đơn là điều cực kỳ đơn giản, gọn nhẹ. Việc giao tiếp với các
thiết bị điện 220VAC, 24VDC,… chỉ khác ở phần công suất, đối với các sinh viên chuyên
ngành điện tử thì việc này khá dễ dàng. Còn đối với các bạn không chuyên ngành điện tử,
các bạn cũng có thể tìm hiểu cách giao tiếp thông qua transistor, relay, triac,.. bằng cách
tìm kiếm tài liệu trên “Google”. Các bạn có thể tìm được những kiến thức rất bổ ích từ
cộng đồng và yên tâm rằng quá trình thực hiện rất đơn giản. Như vậy, chúng ta đã giải

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


quyết xong câu hỏi về vấn đề thiết bị và tiếp tục 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 sẽ giúp các bạn 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
}
}
}
//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ó tín hiệu điều khiển từ điện thoại gửi đến Module Hc-05 thì dùng Arduino đọ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 học Sư Phạm Kỹ Thuật TPHCM Trang 129


(điều khiển led sáng) và gửi lại một chuỗi dữ liệu phản hồi qua Bluetooth là “ON”. Ngược
lạ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
có thể tiếp tục xây dựng từ đây.

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


CHƯƠNG 8 ỨNG DỤNG CÔNG NGHỆ OCR
TRONG ĐIỀU KHIỂN
8.1 ĐẶT VẤN ĐỀ
Công nghệ OCR (nhận dạng kí tự quang học) tuy đã ra đời từ rất lâu nhưng cho đến
ngày nay thì nó mới được quan tâm và phát triển. Các ứng dụng của công nghệ OCR ngày
càng trở nên phổ biến và chuẩn xác. Ví dụ ta đọc một trang giấy hay, ta muốn sao chép nội
dung của trang giấy đó lại. Thay vì bạn phải ngồi đọc và gõ lại từng câu chữ. Nếu ứng dụng
công nghệ OCR ta có thể dễ dàng sao chép nội dung lại theo kiểu định dạng văn bản mà
không cần đọc và gõ chữ lại như cách truyền thống. Công nghệ OCR ngày càng trở nên
thông minh và mở rộng khả năng nhận dạng được nhiều ngôn ngữ, nhiều font chữ viết khác
nhau, thậm chí nhận dạng tốt cả chữ viết tay.
Một vấn đề thực tế khá hay đặt ra như sau: Nếu bạn là người sử dụng tiếng Việt, khi đi
du lịch sang Nhật Bản. Khi bạn đi tàu điện, dạo phố, mua sắm... bạn muốn đọc các bảng
chỉ dẫn, thông tin nhưng bạn không biết tiếng Nhật thì sao? Bạn có thể dùng các phần mềm
tự điển nhưng vấn đề là bạn loay hoay mãi vẫn không biết cách nào để nhập đoạn chữ tiếng
Nhật cần tra cứu vào được. Thật khó khăn phải không? Khi đó Google Len hoặc Google
translate đã ứng dụng công nghệ OCR cho các ứng dụng của mình. Bạn không cần phải
nhập dữ liệu cần tra cứu, bạn chỉ cần đưa camera của điện thoại lên để các ứng dụng đọc
hoặc nhập thông tin tiếng Nhật cần tra cứu giúp bạn. Thêm một vài ứng dụng thực tế của
công nghệ OCR đó là việc nhận dạng biển số xe tự động ở các bãi giữ xe thông minh, các
phần mềm dẫn đường đọc biển báo tốc độ, chuyển chữ viết tay thành chữ đánh máy,...
Trong chương này, tác giả trình bày một ứng dụng liên quan đến công nghệ OCR là nhận
dạng các từ màu sắc như đỏ, xanh, vàng để rồi làm sáng các đèn led màu tương ứng.
8.2 ĐỊNH NGHĨA VỀ CÔNG NGHỆ
Công nghệ OCR (Optical Character Recognition) là công nghệ nhận dạng kí tự quang học,
được tạo ra để chuyển các hình ảnh của chữ viết tay, chứ đánh máy thành văn bản tài liệu.
OCR được xây dựng từ lĩnh vực nghiên cứu về nhận dạng mẫu, trí tuệ nhân tạo và thị giác
máy tính. Công nghệ OCR vẫn đang tiếp tục nghiên cứu cải tiến nhằm tăng cường sự chuẩn
xác, mở rộng phạm vi đối tượng nhận dạng. Tuy nhiên nó vẫn song song được ứng dụng
trong thực tế và đã mang lại rất nhiều kết quả thiết thực.
Nhận dạng ký tự quang học ban đầu dùng ống kính để nhận dữ liệu đầu vào. Hiện tại các
ứng dụng Google Len hoặc Google Translate đang có chức năng này. Việc nhận dạng diễn
ra tức thì, không cần chụp hình ảnh lưu lại. Tuy nhiên trong thực tế rất nhiều dữ liệu đầu
vào là file hình ảnh (từ camera hoặc máy scan), việc nhận dạng từ các file này là nhận dạng
các ký tự số. Hiện nay thuật ngữ nhận dạng ký tự quang học OCR được mở rộng và bao
gồm luôn ý nghĩa nhận dạng ký tự số.

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


Hình 8.1 Giới thiệu về công nghệ OCR
Hệ thống nhận dạng được huấn luyện với các mẫu của các ký tự cụ thể. Về cơ bản, hệ
thống nhận dạng tương đối chính xác đối với hầu hết các font chữ hiện nay. Một số hệ
thống thông minh hơn còn có khả năng tái tạo lại các định dạng của tài liệu gần giống với
bản gốc bao gồm: hình ảnh, các cột, bảng biểu, các thành phần không phải là văn bản. Như
vậy công nghệ OCR hiện tại được ứng dụng rất tốt trong việc số hóa các tài liệu cũ, giúp
việc nhập liệu trở nên nhanh chóng hơn, đỡ sai sót hơn.
8.3 ỨNG DỤNG CÔNG NGHỆ OCR ĐIỀU KHIỂN LED 7 MÀU
8.3.1 YÊU CẦU
Trong thực tế ta có thể sử dụng công nghệ OCR để điều khiển xe thông minh thông qua
việc đọc các bảng báo, chỉ dẫn. Các ứng dụng điều khiển cũng khá đa dạng. Ở phạm vi
chương này, tác giả cố gắng đưa ra một ứng dụng đơn giản nhất. Thông qua ứng dụng này
độc giả hiểu được công nghệ OCR là gì, trong lập trình di động ta nhúng nó vào ứng dụng
của mình ra sao? Bài toán đặt ra là chúng ta sẽ viết một ứng dụng chạy trên thiết bị Android.
Ứng dụng này sẽ điều khiển camera điện thoại chụp lại một bức ảnh chứa các ký tự (có thể
viết tay hoặc đánh máy) là các màu sắc: đỏ (red), cam (organce), xanh dương (blue),...Từ
bức ảnh chụp được, chúng ta sẽ ứng dụng công nghệ OCR thông qua các hàm mã nguồn
mở của Google (được tích hợp trong Android Studio) để tách ra các ký tự trong hình. Từ
ký tự màu sắc nhận dạng được ta sẽ điều khiển đèn led bảy màu sáng đúng màu tương ứng.

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


8.3.2 SƠ ĐỒ HỆ THỐNG

Hình 8.2 Sơ đồ hệ thống ứng dụng công nghệ OCR trong điều khiển led màu
Việc nhận dạng ký tự từ hình ảnh chụp được sau đó gởi lệnh điều khiển hoặc thông tin
xuống cho NodeMCU có thể thực hiện qua kết nối Bluetooth ở phạm vi dưới 10-20m. Ở
đây tác giả dùng Firebase làm cơ sở dữ liệu trực tuyến để kết nối với NodeMCU vì bản
thân ESP8266 có sẵn kết nối wifi, thuận tiện trong việc mở rộng điều khiển từ xa qua mạng
internet.
8.3.3 THIẾT KẾ ỨNG DỤNG TRÊN ANDROID
Ứng dụng Android thực hiện công việc khá đơn giản là có nút nhấn để mở Camera của
điện thoại lên. Sau đó nhấn nút chụp ảnh. Khi ảnh được chụp thì nó sẽ dùng công cụ nhận
dạng của Google để tách các ký tự từ hình ảnh chụp được. Nếu các ký tự nhận dạng nằm
trong hệ thống các từ điều khiển do ta quy ước bao gồm: red, blue, green, cyan, purple,...thì
nó sẽ thay đổi giá trị của một biến trên Firebase (thông qua mạng internet) mà ứng dụng
đã liên kết đến. Đồng thời nó sẽ hiển thị ký tự nhận dạng được trên giao diện ứng dụng.
Đầu tiên ta xây dựng ứng dụng với giao diện đơn giản gồm một imageview chứa hình ảnh
camera chụp được, một textview hiển thị thông báo chụp ảnh và kết quả ký tự nhận dạng,
một nút nhấn để mở camera chụp ảnh.

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


Hình 8.3 Giao diện ứng dụng OCR điều khiển led
Code của file activity_main.xml như sau
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/imageView"
android:layout_width="354dp"
android:layout_height="270dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.491"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.123"
tools:srcCompat="@tools:sample/backgrounds/scenic"
android:contentDescription="@string/hinhcamera" />
<TextView
android:id="@+id/textView"
android:layout_width="355dp"
android:layout_height="82dp"
android:text="@string/textview"
android:textSize="24sp"
android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />

<Button
android:id="@+id/button"
android:layout_width="match_parent"

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


android:layout_height="wrap_content"
android:text="@string/open_camera"
android:textSize="28sp"
android:textColor="#FF9800"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
Các giá trị biến liên kết trong file strings.xml
<resources>
<string name="app_name">OCR dieu khien Led</string>
<string name="hinhcamera">TODO</string>
<string name="textview"> OPEN CAMERA Button, Get text from an image!</string>
<string name="open_camera">OPEN CAMERA</string>
</resources>
Sau đó ta liên kết ứng dụng với Firebase (xem lại chương 4: Xây dựng ứng dụng IOTs sử
dụng Firebase)

Hình 8.4 Liên kết ứng dụng OCR với Firebase


Tạo dự án Firebase mới liên kết với ứng dụng. Trong dữ liệu thời gian thực ta tạo một biến
là “LED” với giá trị mặc định ban đầu là “111” (tương ứng với việc điều khiển led tắt).

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


Hình 8.5 Tạo biến dữ liệu thời gian thực trên Firebase
Lưu ý vào Android SDK update các nền tảng mới nhất

Hình 8.6 Giao diện Android SDK


Cài đặt đầy đủ các thư viện, kiểm tra file build.gradle (:app) với các nội dung như sau (lưu
ý các phiên bản và thư viện có thể thay đổi tùy theo phiên bản của Android Studio và SDK
platform bạn đang dùng)
apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.example.ocrdieukhienled"
minSdkVersion 28
targetSdkVersion 30
versionCode 1

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


versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-
rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
implementation 'com.google.firebase:firebase-dynamic-links:19.1.0'
implementation 'com.google.firebase:firebase-database:19.3.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
Viết code cho ứng dụng tại file MainActivity.java như sau:
package com.example.ocrdieukhienled;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.graphics.Bitmap;
import android.provider.MediaStore;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.firebase.FirebaseApp;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
import com.google.firebase.ml.vision.text.FirebaseVisionText;
import com.google.firebase.ml.vision.text.FirebaseVisionTextRecognizer;
import java.util.List;
import java.util.Objects;
public class MainActivity extends AppCompatActivity {
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference myref = database.getReference("LED");
Button chooseImage;
ImageView image;
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
chooseImage = findViewById(R.id.button);
image = findViewById(R.id.imageView);
text = findViewById(R.id.textView);
chooseImage.setOnClickListener(new View.OnClickListener() {
@Override

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


public void onClick(View v) {
setChooseImage();
}});
}
public void setChooseImage(){
// Tạo một Intent không tường minh,
// yêu cầu hệ thống mở Camera chuẩn bị chụp hình.
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Start Activity chụp hình, và chờ đợi kết quả trả về.
this.startActivityForResult(intent, 1);
}
//nếu chụp ảnh và ảnh trả về Ok thì gọi chạy hàm nhận dạng ký tự
// hàm runTextRecognition
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == 1) {
Bitmap bp = (Bitmap) Objects.requireNonNull(data.getExtras()).get("data");
this.image.setImageBitmap(bp);
runTextRecognition(bp);
}
}
}
//xử dụng công cụ thị giác firebase để xử lý hình ảnh
private void runTextRecognition(Bitmap mSelectedImage) {
FirebaseVisionImage image = FirebaseVisionImage.fromBitmap(mSelectedImage);
FirebaseVisionTextRecognizer detector =
FirebaseVision.getInstance().getOnDeviceTextRecognizer();
detector.processImage(image).addOnSuccessListener(
new OnSuccessListener<FirebaseVisionText>() {
@Override
public void onSuccess(FirebaseVisionText texts) {
processTextRecognitionResult(texts);
}
});
}
//hàm xử lý nhận dạng ký tự và trả về chuỗi ký tự đã nhận dạng
@SuppressLint("SetTextI18n")
private void processTextRecognitionResult(FirebaseVisionText texts) {
FirebaseApp.initializeApp(MainActivity.this);
List<FirebaseVisionText.TextBlock> blocks = texts.getTextBlocks();
if (blocks.size() == 0) {
text.setText("No text found!");
return;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < blocks.size(); i++) {
List<FirebaseVisionText.Line> lines = blocks.get(i).getLines();
for (int j = 0; j < lines.size(); j++) {
List<FirebaseVisionText.Element> elements = lines.get(j).getElements();
for (int k = 0; k < elements.size(); k++)
sb.append(elements.get(k).getText()).append(" ");
sb.append("");
}
sb.append("");
}
text.setText(sb.toString());
//Xử lý lại chuỗi text bằng cách cách bỏ khoảng trắng hai đầu, in thường tất cả
//gán vào biến ans
String ans = ((TextView)text).getText().toString().trim().toLowerCase();
//kiểm tra nội dung biến ans này với các ký tự điều khiển để gởi

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


//giá trị tương ứng lên biến LED trên firebase
switch (ans) {
case "red":
myref.setValue("000");
break;
case "blue":
myref.setValue("001");
break;
case "green":
myref.setValue("010");
break;
case "yellow":
myref.setValue("011");
break;
case "cyan":
myref.setValue("100");
break;
case "purple":
myref.setValue("101");
break;
case "white":
myref.setValue("110");
break;
case "off":
myref.setValue("111");
break;
}
}
}
Tiến hành biên dịch và chay ứng dụng trên máy thật là Pixel 4XL ta được kết quả như sau:
Ta viết các màu sắc bằng tiếng Anh vào giấy hoặc bảng và mở ứng dụng lên. Khi nhấn
camera chụp ảnh thì nó sẽ trả về ký tự nhận dạng được và đồng thời nó sẽ thay đổi giá trị
biến “LED” trên firebase gần như tức thời. Giá trị biến LED trên firebase thay đổi tương
ứng với màu sắc nhận dạng mà ta đã quy định. Ví dụ màu red tương ứng “000”, blue tương
ứng “001”, green tương ứng “010”,...
Bạn hãy xem các kết quả chụp màn hình của một số trường hợp nhé.

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


Hình 8.7 Giao diện ứng dụng khi mới mở lên
Vì nhận dạng từ các chữ viết tay nên yêu cầu các chữ viết cầu đẹp, rõ. Trường hợp đánh
máy, in ra, rồi chụp lại các chữ thì việc nhận dạng rất chính xác. Rõ ràng là vậy rồi!

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


Hình 8.9 Một số kết quả nhận dạng chữ từ hình ảnh chụp
Khi nhận được chữ blue thì giá trị biến LED trên firebase thay đổi thành “001”

Hình 8.10 Giá trị biến LED trên firebase thay đổi khi nhận dạng chữ blue
Khi ứng dụng nhận dạng được ký tự là green thì biến LED trên Firebase thay đổi thành
“010”

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


Hình 8.11 Giá trị biến LED trên firebase thay đổi khi nhận dạng được chữ green

8.3.4 THIẾT KẾ BỘ ĐIỀU KHIỂN


Ở phần bộ điều khiển ta dùng module NodeMCU với nhân là chip ESP8266. Module này
cung cấp sẵn các chân IO và nguồn ra rất thuận tiện cho việc kết nối với ngoại vi. Hơn nữa
chip ESP tích hợp kết nối wifi rất phù hợp cho các ứng dụng IOTs. Trong ứng dụng này ta
kết nối module NodeMCU điều khiển một led đa màu. Led đa màu với ba chân điều khiển
ba màu cơ bản RGB có thể kết hợp để tạo ra nhiều màu khác nhau. Nếu bạn gặp khó khăn
trong việc tìm các led đa màu này thì bạn có thể dùng nhiều led màu đơn khác nhau cho
thí nghiệm của mình. Riêng với led đa màu, trên thị trường khá đa dạng về chủng loại. Nên
bạn mua loại nào phải đo đạc hoặc tham khảo datasheet để có kết nối đúng đắn. Bên dưới
là một loại led đa màu RGB với chân chung là chân Anode (chân dương). Khi kết nối với
NodeMCU chân Anode này sẽ nối đến chân 3.3v hoặc 5v, các chân R, G, B sẽ kết nối đến
các chân D0, D1, D2. Việc điều khiển cho một màu nào sáng bằng cách xuất ra mức logic
0 tương ứng. Và ngược lại, muốn màu nào tắt thì xuất ra mức logic 1. Bên dưới là hình ảnh
của một led đa màu được sử dụng

Hình 8.12 Hình ảnh led đa màu Anode chung

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


Phần cứng kết nối sử dụng ESP8266 khá đơn giản như sau:

Hình 8.13 Kết nối NodeMCU với led đa màu


Bước tiếp theo lập trình cho ESP8266 kết nối wifi, đọc giá trị biến “LED” từ firebase và
điều khiển đèn Led sáng theo màu chỉ định.
Đoạn code viết cho ESP8266 trên phần mềm Arduino IDE được trình bày bên dưới, các
chú thích được gõ kèm cho thuận tiện trong việc tham khảo
#include <ESP8266WiFi.h>
#include <FirebaseArduino.h>
//firebase_host chính là địa chỉ file dự án firebase ta đã tạo
#define FIREBASE_HOST " ocr-dieu-khien-led.firebaseio.com "
//#define FIREBASE_AUTH "vào Firebase lấy hoặc bỏ qua phần này"
#define WIFI_SSID "tên mạng wifi"
#define WIFI_PASSWORD "password mạng wifi"
#define RED_PIN D2 //kết nối chân R của led đa màu
#define GREEN_PIN D1 //kết nối chân G của led đa màu
#define BLUE_PIN D0 //kết nối chân B của led đa màu
String fireStatus_LED = ""; //biến nhận giá trị từ biến LED trên firebase
void setup() {
Serial.begin(9600); //dùng cho debug khi kết nối máy tính
delay(1000);
pinMode(LED_BUILTIN, OUTPUT);
//cấu hình các chân điều khiển led là ngõ ra
pinMode(RED_PIN, OUTPUT);

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


pinMode(GREEN_PIN, OUTPUT);
pinMode(BLUE_PIN, OUTPUT);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); // thử kết nối với wifi
Serial.print("Dang ket noi ");
Serial.print(WIFI_SSID);
while (WiFi.status() != WL_CONNECTED) {
Serial.print(".");
delay(500);
}
//dùng debug
Serial.println();
Serial.print("Da ket noi ");
Serial.println(WIFI_SSID);
Serial.print("Dia chi IP la : ");
Serial.println(WiFi.localIP());
Firebase.begin(FIREBASE_HOST); // kết nối với firebase
//FIREBASE_AUTH);
Firebase.setString("LED", "111"); //gửi chuỗi trạng thái ban đầu tương ứng giá trị tắt led
}
void loop()
{
fireStatus_LED = Firebase.getString("LED"); // lấy giá trị biến LED từ firebase
displayAllBasicColors();
}
void displayAllBasicColors()
{
if (fireStatus_LED== "000" )
{
Serial.println("LED is red");
//thiết lập tạo màu đỏ cho LED
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, HIGH);
}
else if (fireStatus_LED == "001" )
{
Serial.println("LED is blue ");
//Thiết lập Led sáng màu xanh dương
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, LOW);
}
else if (fireStatus_LED == "010" )
{
Serial.println("LED is green");

//thiết lập led sáng màu xanh lá


digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, HIGH);
}
else if (fireStatus_LED == "011" )
{
Serial.println("LED is yellow");
//Thiết lập led sáng màu vàng
digitalWrite(RED_PIN, LOW);

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


digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, HIGH);
}
else if (fireStatus_LED == "100" )
{
Serial.println("LED is cyan");

//Thiết lập led sáng màu xanh lam


digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
}
else if (fireStatus_LED == "101" )
{
Serial.println("LED is purple ");

//thiết lập led sáng màu tím


digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, LOW);
}
else if (fireStatus_LED == "110" )
{
Serial.println("LED is white ");
//Thiết lập led sáng màu trắng
digitalWrite(RED_PIN, LOW);
digitalWrite(GREEN_PIN, LOW);
digitalWrite(BLUE_PIN, LOW);
}
else if (fireStatus_LED == "111" )
{
Serial.println("LED is off ");
//Thiết lập led tắt
digitalWrite(RED_PIN, HIGH);
digitalWrite(GREEN_PIN, HIGH);
digitalWrite(BLUE_PIN, HIGH);
}
else
{
Serial.println("Lenh dieu khien sai! Vui long gui lại");
}}

Tiến hành viết code cho NodeMCU xong và chạy thực nghiệm kiểm tra kết quả cùng với
ứng dụng Android trên điện thoại. Ta thấy việc nhận nhận ký tự từ ứng dụng OCR và điều
khiển led màu khá đơn giản. Hi vọng với nền tảng kiến thức cơ bản này độc giả tiếp tục
phát triển thêm cho các nghiên cứu, hoặc ứng dụng thực tế hơn, phức tạp hơn.

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


CHƯƠNG 9 ỨNG DỤNG CẢM BIẾN GIA TỐC
TRONG ĐIỀU KHIỂN
9.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 dụng, được
sử dụng khá nhiều trong các ứng dụng hay nghiên cứu làm đồ án. Những cảm biến gia tốc
như MPU6050 hay HMC5883 được sử dụng nhiều cho các ứng dụ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, và phát hiện sự té ngã. Ở 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 hình hay những game đua xe điều khiển bằng cách nghiêng điện thoại để
tương tác cũng chính là những ứng dụng từ cảm biến gia tốc.

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

Trong chương này, chúng tôi muốn giới thiệu với các bạn về cảm biến gia tốc trong điện
thoại Android và các sử dụng dụng nó thông qua một ứng dụng cơ bản. Mục tiêu của chúng
ta là mang những tương tác giữa con người và điện thoại qua cảm biến gia tốc chuyển thành
những tương tác điều khiển đến các thiết bị điện bên ngoài. Hãy làm một ứng dụng đơn
giản, giả sử có bốn led đơn, chúng ta sẽ viết một ứng dụng Android trên điện thoại để điều
khiển bốn led đơn này sáng tắt dựa vào cảm biến gia tốc. Hay nói cách khác, chúng ta cầm
điện thoại nghiêng tới, lui, trái, phải để đóng/mở các đèn led này. Trong một ứng dụng xa
hơn, nhưng khá gần gũi là các bạn có thể viết ứng dụng điều khiển một chiếc xe chạy tới
lui, qua trái qua phải bằng cách nghiêng điện thoại. Trở lại ứng dụng của chúng ta, nó hoạt
động theo nguyên tắc sau: sử dụng cảm biến Accelerometer tích hợp trong điện thoại
Android và đọc về các giá trị gán vào ba thông số tương ứng cho ba trục là ax, ay, az. Tạo
ngưỡng để so sánh với các giá trị cảm biến. Khi giá trị ax đọc về bé hơn -2 thì đang nghiêng
điện thoại về phía trước. Lớn hơn 2 thì đang nghiêng về phía sau. Nếu giá trị trong khoảng
-1 đến 1 ta xem như cân bằng. Tương tự cho trục y cũng vậy. Hiển nhiên các ngưỡng này
tùy theo ứng dụng cụ thể bạn có thể thay đổi để phù hợp với độ nhạy hệ thống, sau đó
truyền tín hiệu qua Bluetooth điều khiển bốn bóng đèn tương ứng cho bốn góc nghiêng.

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


9.2 PHẦN MỀM TRÊN ĐIỆN THOẠ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 hình nằm ngang như sau:

Hình 9.2 Giao diện của ứng dụng

Thao tác quét và kết nối Bluetooth tương tự chương trước, sau khi kết nối hoàn tất ứng
dụng mới cho phép gửi dữ liệu điều khiển đi.

Hình 9.3 Giao diện điều khiển

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


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

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


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" />
</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 thị cho bốn hướng của góc nghiêng, một Listview để lưu các
địa chỉ Bluetooth quét được, một Button để dò tìm các thiết bị có thể kết nối Bluetooth và
một Textview để hiển thị giá trị cảm biến đọc được.
File String.xml hiệu chỉnh với các key như sau:
<resources>
<string name="app_name">Accelerometer</string>
<string name="img_top">img_top</string>
<string name="btn_scan">scan</string>
</resources>

Do giao diện màn hình thiết kế nằm ngang nên phải hiệu chỉnh file manifest như sau:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.ngb.accelerometer" >
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.sensor.accelerometer"
android:required="true" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:configChanges="keyboardHidden|orientation"
android:name=".MainActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

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


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

Chú ý: Các dòng bên dưới activity. Chúng ta sử dụng thuộc tính screenOrientation để thiết
lập hướng màn hình. Với giá trị Landcape là màn hình ngang và Potrait là màn hình dọc.
Trong ứng dụng chúng ta sử dụng cảm biến gia tốc nên phải thiết lập Permission cho phép
sử dụng phần cứng cảm biến gia tốc accelerometer. Và vì chúng ta giao tiếp với thiết bị
bên ngoài qua bluetooth nên bluetooth cũng được cho phép sử dụng. Cuối cùng trong ứng
dụng chúng ta có thể viết chương trình cho phép điện thoại rung lên phản hồi trong một vài
trường hợp nên cũng cần phải thiết lập cho phép rung (vibrate).
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-feature android:name="android.hardware.sensor.accelerometer"
android:required="true" />

Sau đó, chúng ta vào file MainActivity.java để viết code cho ứng dụng thực hiện yêu cầu.
Phần này có sử dụng dòng chú thích để giải nghĩa các đoạn code trong chương trình. Tuy
nhiên, giải thích cụ thể hơn sẽ được trình bày ở phần bên dưới.
package com.example.ngb.accelerometer;
import android.annotation.TargetApi;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.UUID;
public class MainActivity extends Activity implements SensorEventListener{
//chú ý đây là class để lắng nghe các cảm biến
TextView txtv_view;
ImageView top,bot,left,right;

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


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;
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();
on();
btn_scan.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
find();
Toast.makeText(MainActivity.this, "Đang quét thiết bị, xin chờ trong giây lát",
Toast.LENGTH_LONG).show();
}
});
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override

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


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

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


{
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()
{
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()) {

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


// 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));
}
}
//hàm kết nối Bluetooth
public void Connect() {
Log.d(TAG, BtAddress);
BluetoothDevice device = myBluetoothAdapter.getRemoteDevice(BtAddress);
Log.d(TAG, "Connecting to ... " + device);
myBluetoothAdapter.cancelDiscovery();
try {
btSocket = device.createRfcommSocketToServiceRecord(MY_UUID);
btSocket.connect();
Log.d(TAG, "Connection made.");

} catch (IOException e) {
try {
btSocket.close();
} catch (IOException e2) {
Log.d(TAG, "Unable to end the connection");
}
Log.d(TAG, "Socket creation failed");
}}
//hàm gửi dữ liệu qua bluetooth
private void writeData(String data) {
try {
outStream = btSocket.getOutputStream();
} catch (IOException e) {
Log.d(TAG, "Bug BEFORE Sending stuff", e);
}
String message = data;
byte[] msgBuffer = message.getBytes();
try {
outStream.write(msgBuffer);
} catch (IOException e) {
Log.d(TAG, "Bug while sending stuff", e);
String msg = "Sự cố! Vui lòng thực hiện kết nối lại";
}}
//hủy đăng ký khi ứng dụng thoát
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
if (chophepgui) {
unregisterReceiver(bReceiver);
}
myBluetoothAdapter.disable();
}}

Chương trình trên hoạt động như sau:


Vấn đề quét, kết nối và gửi Bluetooth đã được trình bày rõ ràng trong chương trước, các
bạn chỉ cần chú ý vào một chi tiết mới này:

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


Ứng dụng này chỉ hoạt động gửi lệnh khi đã kết nối Bluetooth với thiết bị thành công, vì
vậy việc xác định Bluetooth đã kết nối hay chưa sẽ thông qua phương thức sau:
Trong hàm Oncreate khởi tạ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 thêm điều kiện ràng buộc như sau:
else if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
Toast.makeText(MainActivity.this, "kết nối thành công", Toast.LENGTH_SHORT).show();
chophepgui=true;
}

Khi kết nối hoàn tất sẽ thông báo ra Toast và biến chophepgui đổi thành true. Khi
chophepgui bằng true thì dữ liệu sẽ được gửi.
Tiếp đến là vấn đề đọc cảm biến gia tốc.
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType()==Sensor.TYPE_ACCELEROMETER){
ax=Math.round(event.values[0]); //đọc giá trị trục x
ay=Math.round(event.values[1]); //đọc giá trị trục y
az=Math.round(event.values[2]); //đọc giá trị trục z
txtv_view.setText("x:"+ax+"y"+ay+"z"+az);}
if (chophepgui==true) {
if (ax <= -2) {
top.setImageResource(R.drawable.top);
writeData("1"); }
else if (ax >= 2) {
bot.setImageResource(R.drawable.bot);
writeData("2");
} else {
top.setImageResource(R.drawable.none1);
bot.setImageResource(R.drawable.none1);
writeData("3");
}
if (ay <= -2) {
left.setImageResource(R.drawable.left);
writeData("4");
} else if (ay >= 2) {
right.setImageResource(R.drawable.right);
writeData("5");
} else {
left.setImageResource(R.drawable.none1);
right.setImageResource(R.drawable.none1);
writeData("6");
}
}
}

Giá trị của cảm biến được gán vào ba biến ax, ay, az và làm tròn bằng hàm round.
Sau khi biến chophepgui là true thì bắt đầu so sánh ngưỡng để biết hướng nghiêng của điện
thoại. Nếu 𝑎𝑥 ≤ −2 thì đang nghiêng phía trước, 𝑎𝑥 ≥ 2 đang nghiêng phía sau, tương tự

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


cho ay khi 𝑎𝑦 ≤ −2 thì nghiêng bên trái và 𝑎𝑦 ≥ 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 trạng
thái qua Bluetooth. Khi ứng dụ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);

9.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 bốn 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 như sau:

Hình 9.4 Sơ đồ kết nối phần cứng

Code chương trình Arduino:


char buff1;
int k;
void receive_uart();
void setup()
{
Serial.begin(9600);
pinMode(6,OUTPUT);
pinMode(7,OUTPUT);

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


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

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


CHƯƠNG 10 ỨNG DỤNG CÔNG NGHỆ NHẬN
DẠNG GIỌNG NÓI
10.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 chúng
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ỉ 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 bằng 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à từ 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.
Trong phần này tác giả sẽ trình bày về một ứng dụng khá lý thú 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 chúng ta có thể nhận dạng được rất nhiều ngôn
ngữ khác nhau, bao gồm cả việc nhận dạng bằng tiếng Việt. Kết quả nhận dạng có thể được
đưa vào nhiều ứng dụng điều khiển khác nhau. Trong ứng dụng mà tác giả xây dựng bên
dưới đây, 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 một số người khuyết 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ó ba 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 hai bóng đèn còn lại. Để 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. 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ư phần đã trình bày ở các chương trước.
10.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.

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


- 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ề.

Hình 10.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"
android:id="@+id/textView_ledred"

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


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

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


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" />
<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 trọng 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;

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


import java.util.UUID;
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);
//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() {

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


@Override
public void onClick(View v) {
find();
}});
//xử lý khi listview thiết bị được nhấn
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();
}});
}
@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_RESULTS);
//lưu chuỗi chuyển hóa từ giọng nói vào listview //list_data
V2TArrayAdapter = new ArrayAdapter<>(this,
android.R.layout.select_dialog_item, text);

list_data.setAdapter(V2TArrayAdapter);
for (int i = 0; i <text.size(); i++)
{
String buff = text.get(i);
//đọc nội dung từng gợi ý chuyển đổi và lấy từ khóa
if (buff.contains("bật") || buff.contains("Bật"))
{
if (buff.contains("đỏ") || buff.contains("Đỏ"))
{
writeData("1");
}
if (buff.contains("lục") || buff.contains("Lục"))
{
writeData("2");
}
if (buff.contains("dương") || buff.contains("Dương"))
{
writeData("3");
}
}
if (buff.contains("tắt")|| buff.contains("Tắt"))
{
if (buff.contains("đỏ") || buff.contains("Đỏ"))
{
writeData("4");
}
if (buff.contains("lục") || buff.contains("Lục"))
{
writeData("5");
}
if (buff.contains("dương") || buff.contains("Dương"))
{
writeData("6");
}
}}}}}
public void on(){
if (!myBluetoothAdapter.isEnabled()) {
Intent turnOnIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);

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


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

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


}
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()
{
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"))

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


{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
{
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ỉ trình
bày về phần chuyển giọng nói thành Text.
Quá trình chuyển đổi giọng nói sang text có hai phần chính.
• Hàm con chuyển đổi dữ liệu giọng nói thành text. Hàm này chỉ thực thi khi điện thoại
đã kết nối với thiết bị điều khiển. Sau khi có được dữ liệu giọng nói (đã chuyển thành
text) thì kết quả hiển thị lên listview list_data, sau đó dữ liệu sẽ được chuyển lần lượt
thành các chuỗi dạng string và chứa trong buff. Bây giờ, nhiệm vụ của chúng ta là tìm
trong các chuỗi dữ liệu nhận dạng này (buff) các từ khóa điều khiển để ra lệnh điều
khiển phù hợp, gởi đến bộ điều khiển qua Bluetooth.
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (chophepgui) {
//khi kết nối với một thiết bị nào đó rồi mới thực hiện gửi //được
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RESULT_SPEECH && resultCode == RESULT_OK) {
text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
//lưu chuỗi chuyển hóa từ giọng nói vào listview list_data
V2TArrayAdapter = new ArrayAdapter<>(this,
android.R.layout.select_dialog_item, text);
list_data.setAdapter(V2TArrayAdapter);
//đọc và chuyển nội dung lần lượt lưu vào buff
for (int i = 0; i <text.size(); i++)
{
String buff = text.get(i);
//đọc nội dung từng gợi ý chuyển đổi và tìm từ khóa
//Tìm từ khóa Bật/bật để mở đèn led
if (buff.contains("bật") || buff.contains("Bật"))
{
//tìm từ khóa đèn đỏ
if (buff.contains("đỏ") || buff.contains("Đỏ"))
{//nếu đúng từ khóa bật đèn đỏ thì gởi lệnh “1”
writeData("1");

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


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

Khi nhận được phản hồi cho phép chuyển đổi, lưu chuỗi chuyển đổi vào chuỗi text.
text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);

Việc so sánh như xử lý chuỗi thông thường, dùng lớp contains để tìm nội dung từ khóa
trong chuỗi text.
• Bắt đầu việc nhập giọng nói bằng cách nhấn vào imageView micro.
btn_speak.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//tạo intent thực hiện việc nhập giọng nói và ghi lại
Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
//chọn ngôn ngữ đầu ra, ở đây tôi chọn FREE_FORM, ngôn ngữ sẽ phụ //thuộc vào thiết
lập của thiết bị
intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
//khởi động activityForResult để gửi phản hồi
startActivityForResult(intent, RESULT_SPEECH);
}
});

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


10.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 10.2 Kết nối Ardunio với HC05 và ba led đơn

Chương trình điều khiển viết cho Arduino gần như không có khác biệt với các chương
trước, vì chỉ bao gồm quá trình nhận dữ liệu từ Bluetooth, tác động các led tương ứng và
phản hồi. Việc giao tiếp giữa Arduino và HC05 dựa theo chuẩn truyền nối tiếp bất đồng bộ
UART quen thuộc với tốc độ baud được thiết lập là 9600. Chân số 7 kết nối led màu xanh
dương, chân số 8 kết nối led màu đỏ, chân số 9 kết nối led màu xnah lục. Các chân này các
bạn chú ý thêm điện trở hạn dòng thích hợp cho nó. Các mã lệnh trên Android gởi xuống
cần phải chú ý để thực thi cho đúng. Mã lệnh điều khiển sáng đèn đỏ, xanh lục xanh dương
lần lượt là “1”, “2”, “3”. Tương tự mã lệnh điều khiển tắt đèn đỏ, xanh lục và xanh dương
lần lượt là “4”, “5”, “6”. Lưu ý các mã là mã Ascii nên trong chương trình vi điều khiển
cũng phải dùng mã Ascii để kiểm tra, hoặc theo cách khác là chuyển mã Ascii thành số nhị
phân bình thường. Đối với các con số từ mã nhị phần chuyển thành Ascii thì cộng thêm
0x30, còn từ Ascii chuyển thành nhị phân thường thì trừ 0x30.
Chú ý: Các ghi chú kế bên các dòng lệnh để hiểu rõ chương trình.
char buff1;
int k;
void receive_uart();
void setup()
{
Serial.begin(9600); //thiết lập tốc độ baud 9600
//thiết lập chân số 7 kết nối led xanh dương là ngõ ra
pinMode(7,OUTPUT);
//thiết lập chân số 8 kết nối led đỏ là ngõ ra
pinMode(8,OUTPUT);
//thiết lập chân số 9 kết nối led xanh lục là ngõ ra

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


pinMode(9,OUTPUT);
}

voidloop()
{
//gọi hàm nhận dữ liệu từ port nối tiếp: board HC05
receive_uart();
delay(20);
//điều khiển trạng thái các led tương ứng lệnh điều khiển
//sau khi gọi hàm receive_uart()nếu có lệnh điều khiển //gởi từ phần mềm Android thì
nó sẽ được chứa trong buff1
switch (buff1)
{
case 1: {
digitalWrite(8,HIGH); //sáng led đỏ
//gởi dữ liệu phản hồi cho biết đèn đỏ sáng
Serial.println("ds");
break;
}
case 4: {
digitalWrite(8,LOW); //tắt led đỏ
//gởi dữ liệu phản hồi cho biết đèn đỏ tắt
Serial.println("dt");
break;
}
case 2: {
digitalWrite(9,HIGH); //sáng led xanh lục
//Gởi phản hồi về phần mềm Android cho biết đèn
//xanh lục đã sáng
Serial.println("ls");
break;
}
case 5: {
digitalWrite(9,LOW); //tắt led xanh lục
Serial.println("lt");
break;
}
case 3: {
digitalWrite(7,HIGH); //sáng led xanh dương
Serial.println("xs");
break;
}
case 6: {
digitalWrite(7,LOW); //tắt led xanh dương
Serial.println("xt");
break;
}
default: break;
}}
void receive_uart() //nhận dữ liệu từ port nối tiếp
{
while ((Serial.available() == 0)) {}
while (Serial.available() > 0)
{
buff1 =Serial.read() - 0x30;
//nhận dữ liệu và chuyển dữ liệu từ mã Ascii thành số nhị //phân thông thường để
tiện việc so sánh
}}

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


CHƯƠNG 11 SỬ DỤNG BLYNK TẠO ỨNG DỤNG
ĐIỀU KHIỂN VỚI GOOGLE ASSISTANT
11.1 ĐẶT VẤN ĐỀ
Google Assistant là một trợ lý cá nhân ảo được phát triển bởi Google dành cho thiết bị di
động và nhà thông minh, được giới thiệu lần đầu tại hội nghị nhà phát triển của hãng vào
tháng 5 năm 2016. Không giống như Google Now (có thể được xem là tiền thân của Google
Assistant, hỗ trợ các điều khiển trên điện thoại bằng giọng nói), Trợ lý Google có thể tham
gia các cuộc trò chuyện hai chiều. Google Assistant hiện tại hỗ trợ rất nhiều ngôn ngữ khác
nhau, có tiếng Việt. Các cuộc trò chuyện của Google Assistant được xem là tự nhiên, thông
minh nhất hiện nay. Hiện tại Google Assistant có thể trả lời và thực thi rất nhiều công việc
như trả lời thời tiết, ngày giờ, chỉ đường, bật nhạc,... Các cuộc trò chuyện ngày càng tự
nhiên hơn và đôi khi khá hài hước. Google Assistant cơ bản có thể giải phóng hoàn toàn
người dùng việc chạm vào điện thoại hoặc thiết bị thông minh chạy Android như Loa
Thông minh chẳng hạn. Hiện tại Google Assistant cực kỳ được ưa chuộng trong các hệ
thống điều khiển nhà thông minh, xe hơi,..nhưng nơi mà việc ra lệnh thuận tiện hơn, nhanh
hơn việc cầm thiết bị lên và chạm vào để điều khiển.
Trong chương này tác giả sẽ trình bày cách để xây dựng một ứng dụng điều khiển hai led
đơn thông qua Google assistant. Và việc xây dựng này theo một hướng mới hoàn toàn khác
với việc sử dụng Android Studio đã trình bày trong các chương trước. Đó là dùng Blynk
để xây dựng. Mục đích ứng dụng khá vui vẻ, bạn đọc có thể dễ dàng làm nhanh và khả
năng thành công cao. Điều đó kích thích sự đam mê và sáng tạo đúng không? Nếu một bạn
học sinh cấp 2 có đam mê vẫn có thể làm theo hướng dẫn và thành công việc mọi việc khá
đơn giản. Các kiến thức lập trình cơ bản được hỗ trợ gần trọn vẹn với các mã nguồn mở
phong phú có thể giúp người đọc có thêm một hướng nghiên cứu “nhẹ nhàng” hơn. Sơ đồ
hệ thống như hình bên dưới:

Hình 11.1 Sơ đồ hệ thống điều khiển với Google Assistant

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


Như sơ đồ hệ thống hình bên trên, ta dùng phần cứng khá quen thuộc là NodeMCU kết nối
và điều khiển hai led đơn, một màu xanh và một màu đỏ. Việc điều khiển sẽ được thực
hiện từ Google Assistant. Chúng ta biết rằng, trợ lý ảo của Google chúng ta có thể dễ dàng
gọi ra bằng các cú pháp “Hey Google” hoặc “OK Google” và sau đó ra lệnh điều khiển.
Trong bài này ta dùng các lệnh điều khiển tiếng Việt cho thuận lợi và có cảm giác tự nhiên
hơn. Ví dụ ta có thể dùng câu lệnh “Hãy sáng led đỏ” để bật led đỏ chẳng hạn. Chúng ta
đã có một chương nói về điều khiển thiết bị bằng giọng nói, việc dùng Google Assistant
điều khiển cũng có nhiều điểm tương đồng. Tuy nhiên Google Assistant có sự phản hồi hai
chiều, thông minh và tiện dụng hơn.
11.2 XÂY DỰNG ỨNG DỤNG VỚI BLYNK
11.2.1 GIỚI THIỆU VỀ APP BLYNK
Blynk là một ứng dụng được thiết kế cho các hệ thống IOTS (Internet of Things). Nó có thể
cho phép người dùng có thể tạo ra giao diện một cách nhanh chóng và dễ dàng mà không cần
lập trình. Blynk có thể điều khiển phần cứng từ xa, nó có thể hiển thị trạng thái on/off của
thiết bị, hiển thị dữ liệu cảm biến…và có thể lưu trữ dữ liệu trên sever của Blynk. Ngoài ra,
Blynk là một ứng dụng chạy trên điện thoại nên ta có thể kéo thả, vọc vạch trực tiếp trên điện
thoại. Blynk được phát hành trên cả hai nền tảng Android và Ios. Việc phát sinh code tự động
Android đã có Blynk lo! Đặc biệt Blynk cung cấp rất nhiều giao diện đẹp mắt để theo dõi các
thông số, điều khiển thiết bị trong nhà thông minh. Việc này nếu bạn dùng Android Studio
nguyên bản thì sẽ khá là khó khăn.
11.2.2 TẠO MỚI DỰ ÁN TRÊN BLYNK
- Đầu tiên chúng ta phải tải ứng dụng Blynk trên Play Store.
- Sử dụng tài khoản Gmail hoặc Facebook để đăng ký tài khoản Blynk.

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


Hình 11.2 Giao diện đăng nhập Blynk

- Sau khi đăng nhập xong, ta tiến hành tạo mới một project trên Blynk. Nếu bạn có một
ứng dụng sẵn rồi thì có thể mở nó ra và hiệu chỉnh phù hợp.

Hình 11.3 Tạo một ứng dụng mới với Blynk

- Sau đó lựa chọn phần cứng phần cứng mà chúng ta mà chúng ta sử dụng và đặt tên
cho project. Ở đây chúng ta chọn phần cứng là “ESP8266” tên project là “điều khiển
led” và sau đó nhấn Create.

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


Hình 11.4 Chọn phần cứng cho dự án Blynk

- Hoàn thành tạo mới project ứng dụng sẽ gửi mã Auth Token vào gmail mà chúng ta
đã dùng để đăng ký tài khoản và dùng mã này để làm việc với project vừa khởi tạo.

Hình 11.5 Thông báo gởi mã Auth Token đến tài khoản liên kết

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


- Lựa chọn các Widget phù hợp với thiết kế của mình. Chúng ta thấy các widget được
thiết kế sẵn và rất đa dạng. Chúng ta chỉ cần chọn cái phù hợp.

Hình 11.6 Hộp widget của Blynk

- Bây giờ chúng ta sẽ tiến hành tạo hai nút nhấn để điều khiển bật tắt hai led đơn bằng
cách chạm vào “Button” và kéo đến màn hình thiết kế.

Hình 11.7 Giao diện xây dựng các Button điều khiển

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


- Thiết lập các đặc tính cho nút nhấn thứ nhất bằng cách chạm vào nút nhấn đó (nút
nhấn thứ 2 chúng cũng làm tương tự). Ở đây ta có thể thiết lập nút nhấn liên kết đến
chân nào của ESP, các nhãn ON/OFF tương ứng các mức logic 1/0, thông tin của nút
nhấn cần thể hiện. Bạn hãy thử thay đổi thoải mái và kiểm tra sự thay đổi đó ảnh
hưởng như thế nào. Không có hư hỏng gì ở đây cả. Bạn không phải lo lắng!

Hình 11.8 Thiết lập các thuộc tính cho nút Button trong Blynk

- Đặt tên cho nút nhấn thứ nhất là “điều khiển led 1”
- Mode có 2 chế độ là PUSH (nút nhấn thả nút nhấn trở về trạng thái ban đầu) và
SWITCH (nút nhấn thả sẽ chuyển trạng thái, trạng thái đó được giữ nguyên cho đến
khi bạn nhấn lần tiếp theo), trong phần này chúng ta chọn là SWITCH.
- Chạm vào mã PIN để chọn chân, ở đây chúng kết nối led đơn ở chân GPIO16
NodeMCU nên chọn chân là “gp16”. Nếu phần cứng bạn có sự khác biệt, hãy điều
chỉnh cho phù hợp.

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


Hình 11.9 Thiết chân kết nối cho nút nhấn

Như vậy việc thiết lập đặc tính cho nút nhấn thứ nhất thì đã xong, nút nhấn thứ 2 chúng ta
cũng thiết lập tương tự nhưng tại mã PIN chúng ta chọn chân theo sự kết nối của led đơn
với chân GPIO của NodeMCU.
Sau khi hoàn thành thiết lập cho 2 nút nhấn chúng ta nhấn vào nút Run (Hình tam giác gốc
phải) để khởi chạy Blynk.

Hình 11.10 Khởi chạy Blynk

11.3 ĐĂNG NHẬP VÀ CÀI ĐẶT TRÊN IFTTT


Trợ lý Google không thể giao tiếp trực tiếp với BLYNK. Để liên kết giữa trợ lý Google
và Blynk chúng ta sử dụng IFTTT. If This Then That ( IFTTT) là một dịch vụ web miễn phí.
Trong đó, If This (nếu sự việc này xảy ra) sẽ dẫn đến Then That (làm việc kia), được xem

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


là nguyên lý hoạt động của câu lệnh. Tức là khi câu lệnh này được kích hoạt thì thông qua
IFTTT ứng dụng kia cũng sẽ kích hoạt và thay đổi tương tự.

Trong dự án này chúng ta sử dụng WEBHOOKS trên IFTTT để xử lý và update dữ liệu


trên sever Blynk. Web hook là một cách cực kỳ hữu ích và tương đối dễ dàng trong việc
triển khai các phản ứng sự kiện. Các web hook cung cấp một cơ chế trong đó một ứng dụng
server-side có thể thông báo cho một ứng dụng phía client-side khi một sự kiện mới đã xảy
ra trên máy chủ.

Đầu tiên chúng ta phải đăng nhập vào trang Web IFTTT (ifttt.com). Tài khoản đăng nhập
IFTTT phải là tài khoản của Google Assistant. Sau đó, chúng ta sẽ thiết lập câu lệnh để
điều khiển bật tắt led 1 còn led 2 chúng ta cũng làm tương tự. Sau khi đăng nhập xong
IFTTT thì ta nhấn vào nút Create để tạo một Applet mới như hình bên dưới

Hình 11.11 Tạo một applet trên IFTTT

Sau đó vào mục This.

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


Hình 11.12 Tạo điều kiện This

Như chúng ta đã nói phía trên, mục This này sẽ xác định điều kiện xảy ra để thực thi mục
Then That. Ở đây ta sử dụng Google Assistant cho việc điều khiển nên ta gắn nó với Google
Assistant bằng cách gõ chữ “Google Assistant” vào mục chọn một dịch vụ.

Hình 11.13 Liên kết Google Assistant với IFTTT

Sau đó ta chọn loại Trigger (kích hoạt) là loại nói một câu đơn “Say a simple Phrase”. Tất
nhiên trong một số điều khiển khác, mục đích khác bạn có thể chọn loại trigger khác.

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


Hình 11.14 Chọn loại trigger cho IFTTT

Ở mục “What do you want to say” (Bạn muốn nói gì?) chúng ta đặt câu lệnh cần nói ở
đây. Các câu lệnh điều khiển bạn có thể đặt theo ý các bạn với sự hỗ trợ ngôn ngữ của
Google Assistant trên thiết bị. Ở đây tôi chọn là “turn on led 1”. Nếu thiết bị các bạn đã
cập nhật Google Assistant phiên bản hỗ trợ tiếng Việt thì bạn có thể đặt câu lệnh tiếng Việt
cho thuận tiện trong việc điều khiển. Trong phần “What do you want to say” có đến ba
tùy chọn cho chúng ta. Điều đó có nghĩa là chỉ cần bạn nói đúng một trong ba câu lệnh thì
nó đều có thể đáp ứng. Bạn cũng có thể tổ hợp các câu lệnh này vừa bằng tiếng Anh và
tiếng Việt kết hợp như hình 11.15.

“ What do you want the Assistant to say in response?” Đây là câu phản hồi của Google
Asistant sau khi mình thực hiện câu lệnh vừa nói. Bạn thích nghe “Chị Gồ” trả lời những
lời ngọt tai thì cứ đưa mẫu câu vào đây!

Sau khi cài đặt câu lệnh xong thì nhấn vào “Create trigger”. Như vậy ta đã thực hiện xong
mục “This”

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


Hình 11.15 Thiết lập các câu lệnh trigger cho sự kiện This

Sau khi thực hiện xong mục “This” thì ta tiến hành thực hiện mục “That”

Hình 11.16 Thiết lập sự kiện That

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


Ở mục “That” ta chọn Webhooks. Với webhooks, bạn sẽ được nhận “push notification”
(đẩy thông báo đến) khi có sự kiện xảy ra trên máy chủ. Bạn sẽ không cần phải thăm dò
các API để xác định những sự kiện này đã xảy ra hay chưa. Bạn chỉ cần đăng ký vào một
sự kiện với webhooks. Giống như bạn "subscribe" một kênh Youtube, khi nào có video
mới bạn sẽ được thông báo ngay lập tức.

Hình 11.17 Chọn dịch vụ Webhooks cho sự kiện That

Sau đó nhấn vào “Make a web request”

Hình 11.18 Chọn hành động cho webhooks

Chúng ta điền đầy đủ các thông tin sau đây, lưu ý phải điền đủ đủ và đúng

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


Hình 11.19 Thiết lập các thuộc tính cho hành động (action)

Sau khi điền đầy đủ thông tin thì ta nhấn “Create action” để hoàn thành.
Như vậy thông qua IFTTT ta liên kết Google Assistant cho sự kiện trigger “This” và
Webhooks cho hành động phản ứng “That”. Hoàn thànhbằng việc nhấn Finish

Hình 11.20 Hoàn thành thiết lập IFTTT

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


Như vậy chúng ta điều khiển được led 1 sáng với Google Assistant, bằng cách nói câu lệnh
mà chúng ta đã thiết lập ở mục “this”. Phần điều khiển led 1 tắt chúng ta cũng thực hiện
tương tự. Ở đây tôi chỉ trình bày các hình ảnh mà không chi tiết các bước nữa.

Hình 11.21 Thiết lập cho điều khiển tắt Led 1

Ta làm điều tương tự để điều khiển sáng/tắt Led 2. Kết quả sau khi chúng ta thiết lập xong như
hình 11.22.

Hình 11.22 Kết quả sau khi hoàn thành thiết lập cho hai led

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


11.4 BỘ ĐIỀU KHIỂN
Nhắc lại phần cứng chúng ta dùng mô đun NodeMCU kết nối hai led đơn qua hai chân
D0, D1. Điều khiển mức 1 sáng, mức 0 tắt.

Hình 11.23 Phần cứng kết nối NodeMCU

Bây giờ chúng ta mở phần mềm Adruino IDE để tiến hành viết chương trình cho Nodemcu.
Chúng ta thấy chương trình rất đơn giản vì chúng ta sử dụng các thư viện và hàm hỗ trợ
gần như hoàn toàn.
#define BLYNK_PRINT Serial
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
char auth[] = " Mã Auth Token ";
char ssid[] = "TÊN WIFI";
char pass[] = "PASS WIFI";
void setup()
{
Serial.begin(9600);
Blynk.begin(auth, ssid, pass);
}
void loop()
{
Blynk.run();
}
Kết thúc chương 11, chúng ta thấy việc điều khiển các led thông qua mạng Internet, sử
dụng luôn cả Google Assistant chạy rất trơn tru. Một ứng dụng IoTs rất “xịn xò” nhưng có
thể thực hiện đơn giản thông qua các thao tác. Mục này rất phù hợp hướng dẫn các bạn
sinh viên ngoài ngành, các bạn sinh viên mới bắt đầu khám phá lãnh vực IoTs. Đặc biệt

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


với học sinh cấp 2, cấp 3 vẫn có thể tham khảo và tự xây dựng các ứng dụng thực tế như
điều khiển, giám sát khu vườn, trang trại, các thiết bị nhà thông minh một cách dễ dàng.
Và hơn hết, khi tiếp tục xúc với lãnh vực công nghệ, những bước chân đầu tiên như thế
này tạo một sự đam mê mãnh liệt đối với các em, kích thích sự tìm tòi, khám phá hơn bao
giờ hết. Hi vọng sẽ chương này đạt được mục đích khơi sáng, bùng cháy ngọn lửa yêu khoa
học công nghệ của bạn đọc như tác giả đã từng.

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


CHƯƠNG 12 WEAR OS VÀ ỨNG DỤNG ĐIỀU
KHIỂN
12.1 ĐẶT VẤN ĐỀ
Hệ điều hành Android là hệ điều hành không chỉ dành cho mỗi điện thoại di động.
Android rồi còn là hệ điều hành dùng cho máy tính bảng (tablet), tivi, mắt kính thông minh,
đồng hồ thông minh và xe hơi,..Hướng đến mỗi loại thiết bị thì Android có những tùy chỉnh
phù hợp. Ví dụ đối với thiết bị đeo (đồng hồ) thì mặt đồng hồ vuông, hoặc tròn kích thước
nhỏ. Do đó giao diện và chức năng phải làm sao cho dễ thao tác, trực quan. Hiện tại nếu
chúng ta xây dựng ngôi nhà thông minh sử dụng Google Home làm trung tâm điều khiển
thì chúng ta thấy rằng App Google Home đã có sẵn, chúng ta chỉ cần mua thiết bị có hỗ trợ
và tiến hành cài đặt (như hình 12.1). Sau khi cài đặt xong thì chúng ta có thể “trò chuyện”
tự nhiên với loa Google Home, có thể điều khiển các thiết bị một cách tự nhiên nhất. Chúng
ta cũng có thể dùng Google Assistant trên điện thoại để ra lệnh hoặc thao tác điều khiển.
Nếu nhà bạn sử dụng Tivi Android, từ remote tivi chúng ta có thể ra lệnh tắt mở đèn, quạt.
Tuy nhiên nếu bạn đang đeo một đồng hồ Android, luôn nằm trên tay và luôn ở trạng thái
sẵn sàng chờ nhận lệnh điều khiển thì việc giao tiếp với ngôi nhà thông minh càng trở nên
dễ dàng hơn. Wear OS tên đầy đủ chính thức là 'Wear OS by Google', trước đây có tên là
Android Wear, là phiên bản hệ điều hành mở Android của Google thiết kế cho đồng hồ
thông minh và thiết bị đeo khác. Bằng cách kết nối với điện thoại thông minh, Wear OS sẽ
tích hợp chức năng Google Assistant và gởi các thông báo từ điện thoại di động đến đồng
hồ thông minh.
Trong tài liệu này, chúng ta tập trung giải quyết cách xây dựng một ứng dụng Android
trên điện thoại thông minh và ứng dụng Android Wear (hiện tại là WearOS) trên đồng hồ
thông minh. Từ đó chúng ta sử dụng hai thiết bị này thay đổi dữ liệu Database Firebase để
điều khiển ESP8266 bật tắt Led. Như ta đã phân tích, nếu bạn làm một ngôi nhà thông
minh theo hệ sinh thái của Google thì có thể việc lập trình này không cần thiết. Nhưng nếu
bạn muốn xây dựng một giao diện ứng dụng riêng, điều khiển một số thiết bị “riêng biệt”
cho bạn thì đây lại là một vấn đề. Vấn đề chúng ta sẽ bàn thảo là làm sao để viết ứng dụng
điều khiển nằm trên chính chiếc đồng hồ chạy Android của bạn. Và hơn thế nữa là các đối
tượng điều khiển không chỉ giới hạn trong hệ sinh thái Google. Các đối tượng trong hệ sinh
thái Google phải nói là rất tốt, đa dạng. Tuy nhiên hiện tại vẫn có giá thành cao, khó tiếp
cận. Hầu hết người dùng nhà thông minh “bình dân” đang lựa chọn các thiết bị hỗ trợ giá

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


cả phải chăng hơn, ví dụ các đèn Yeelight, các quạt Xiaomi. Các thiết bị này vẫn nằm trong
hệ sinh thái hỗ trợ của Google Home. Trở lại vấn đề chúng ta đang nói, giờ chúng ta sẽ xây
dựng một ứng dụng điều khiển từ đồng hồ

Hình 12.1 Giao diện của ứng dụng Google Home

12.2 TẠO MỘT DATABASE FIREBASE


Việc tạo mới dự án trên Firebase chúng ta đã bàn thảo rất nhiều ở các chương trình.
Tuy nhiên các chương trước đó chúng ta đi theo các tiện lợi là sau khi có dự án Android
xong ta mới tiến hành link kết và tạo dự án Firebase từ chính Android Studio. Ở đây chúng
ta làm theo một cách khác đó là tiến hành tạo dự án Firebase trước. Thật ra cách này sẽ dài
dòng hơn! Nhưng thêm một cách làm cũng hay đúng không cách bạn?
Khởi tạo 1 database firebase trên “firebase.google.com”.
Các bạn vào trang web https://firebase.google.com/ → Chọn “GET STARTED” → “Add
project” và đặt tên cho Project trên Firebase, chọn quốc gia “Vietnam”. → Chọn “CREATE
PROJECT”.

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


Hình 12.2 Tạo dự án Firebase mới

Sau khi CREATE PROJECT thì cửa sổ nhảy sang bước 2 như hình dưới, các bạn tải file
google-services.json về và bỏ vào trong thư mục app/src của project mà bạn tạo →
CONTINUE

Hình 12.3 Chép file .json và project Android

Sau bước 2 thì đến bước 3, bạn thêm các dòng code được hướng dẫn từ Firebase như hình
dưới, phần này vào phần Android Studio tôi sẽ nói chi tiết hơn các bạn đừng lo.

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


Hình 12.4 Hướng dẫn tích hợp Firebase vào Android project

Vậy là xong 3 bước trên. Tiếp theo, trong hình dưới chọn “Add Firebase to your Android
app”

Hình 12.5 Chọn dự án Android để tích hợp Firebase

Bây giờ mới đến phần quan trọng, các bạn sẽ vào MainActivity.java trong Android Studio
để lấy “Android package name” để đăng ký cho Firebase → Sau đó chọn “REGISTER
APP” . Nhớ là phải lấy cho đúng đừng sót chữ nào của package name.

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


Vào project Android
copy Android
package name chép
vào ô này. Thật chính
xác nhé.
Android Package
name nằm trong file
MainActivity.java tại

Hình 12.6 Thêm Package Name để Add dự án vào firebase

Tiếp sau các bạn vào thiết lập cho phép đọc ghi dữ liệu. Các bạn vào Database → Rule →
chuyển thành “true” chỗ read write. Sau đó nhấn PUBLISH.

Hình 12.7 Thiết lập cho phép đọc ghi dữ liệu lên DataFirebase

Sau đó nhấn vào DATA và bạn sẽ thấy cơ sở dữ liệu dạng cây JSON như hình dưới. Bạn
có thể tạo các biến dữ liệu tại đây.

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


Hình 12.8 Tạo dữ liệu cho DataFirebase

Và như vậy là Database đã sẵn sàng để đọc ghi dữ liệu lên rồi, giờ chúng ta sẽ xây dựng
một ứng dụng trên Smartphone để gửi dữ liệu lên Database Firebase thôi.
12.3 TẠO MỘT ỨNG DỤNG TRÊN SMARTPHONE
Việc tạo ứng dụng Android đơn giản như các ứng dụng trước. Không có gì khác biệt. Sau
khi khởi tạo xong project ta sẽ chép File Google-services.json vào thư mục.

Ở phần Firebase, yêu cầu ta chép các thư viện cho file Gradle. Ta tiến hành chép các dòng
code như hướng dẫn bên Firebase lúc nãy vào file build.gradle (project). Lưu ý là các phiên
bản Android Studio mới thì Gradle có sự sắp xếp khác biệt một vài điểm. Nhắc lại, nếu bạn
liên kết Firebase vào project một cách tự động như đã từng làm ở các chương trước thì các
thư viện này được thêm vào tự động và ta không phải làm thủ công.

Hình 12.9 Thêm các thư viện Firebase vào Gradle

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


Tiếp theo ta vào file Build.gradle(Module: app) thêm các dòng code như hướng dẫn bên
Firebase, sau khi nhập xong nhấn SYNC ở góc trên để đồng bộ.

Hình 12.10 Thêm thư viện vào Build.Gradle(app)

Tiếp theo vào phần layout cho phần mềm, thiết kế một switch (nút gạt điều khiển)

Hình 12.11 Thiết kế giao diện cho ứng dụng trên điện thoại

Đặt ID của Widget này là “switch1”. Sau đó ta qua MainActivity.java để lập trình hoạt
động của ứng dụng. Việc việc code cho ứng dụng điều khiển này các bạn xem lại chương
Xây dựng các ứng dụng IOTs sử dụng Firebase.

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


Hình 12.12 Xây dựng code cho file MainActivity.java

Sau khi viết đoạn code và chạy mô phỏng hoặc chạy trên thiết bị thật ta sẽ thấy các biến
trên Firebase được tạo và thay đổi giá trị

Hình 12.13 Kết quả quan sát Firebase khi chạy ứng dụng

12.4 TẠO MỘT ỨNG DỤNG TRÊN SMARTWATCH


Nếu bạn có một đồng hồ thông minh chạy Android, bạn hãy tạo project và cài chương trình
thực tế lên trên đồng hồ thông minh của mình. Ở đây tao sử dụng một đồng hồ khá cũ đó
là Asus Zenwatch 2. Các đồng hồ khác các bạn có thể thay đổi lúc kết nối và đổ chương
trình xuống. Hoặc bạn có thể chạy mô phỏng ở phần này trong Android Studio.

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


Việc tạo project tương tự như project Android bình thường nhưng lưu ý các bạn phải chọn
là Wear OS và mặt đồng hồ tròn/vuông cho phù hợp

Hình 12.14 Tạo ứng dụng cho đồng hồ thông minh

Tùy thuộc vào người lập trình có muốn chọn các giao diện có chứa sẵn các công cụ hỗ trợ
hay không, nếu người lập trình muốn giao diện trống thì người lập trình có thể lựa chọn
Blank Activity.

Tiếp theo sau đó là đặt tên cho ứng dụng, chọn phiên bản hệ điều hành, ngôn ngữ lập trình.

Hình 12.15 Thiết lập ứng dụng Wear OS mới

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


Khi hoàn thành hết thao tác thì môi trường để thiết kế, giao diện lập trình sẽ xuất hiện như
sau

Hình 12.16 Giao diện xây dựng ứng dụng Wear OS

Về cơ bản, ứng dụng viết trên đồng hồ thông minh cũng được viết tương tự như trên điện
thoại thông minh nên tác giả sẽ không thực hiện lại. Vì thế xem như ta đã viết một chương
trình cho đồng hồ thông minh. Và bây giờ là cách để nạp chương trình đó cho đồng hồ
“ASUS ZENWATCH 2”

Mở đồng hồ Android, vào cài đặt → tùy chọn nhà phát triển → chọn “gỡ lỗi qua Wi-Fi”
và ta có thể thấy địa chỉ IP của đồng hồ trong hình dưới là 192.168.1.104 cũng như PORT
để kết nối với laptop là 5555:

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


Hình 12.17 Xem địa chỉ IP của đồng hồ thông minh

Vào thư mục ADB (Android Debugging Bridge - ta có thể tải ADB trên rất nhiều nguồn
trên google) → Nhấn Shift và click chuột trái chọn “Open command window here”:

Hình 12.18 Vào cửa sổ command ‘Android Debugging bridge”

Tại Cửa sổ command mở lên ta gõ “adb connect 192.168.1.104:5555”

Hình 12.19 Dòng lệnh để kết nối đồng hồ thông minh với máy tính

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


Sau đó ta nạp chương trình qua wifi theo thứ tự như hình 4.42:

Hình 12.20 Debugging thông qua wifi giữa máy tính và đồng hồ

12.5 MẠCH ĐIỀU KHIỂN ESP8266 ĐỌC DỮ LIỆU TỪ FIREBASE


Ở đây mình sử dụng ESP8266, chân dương (Anode) của led được gắn vào chân D4 của ESP8266
Chú ý: Để ESP8266 kết nối đọc dữ liệu từ FIREBASE thì phải thêm thư viện Firebase “#include
<FirebaseArduino.h>”.
Khối xử lý trung tâm (Modul wifi ESP8266 NodeMCU Mini D1).

Hình 12.21 Modul thu phát wifi ESP8266 NodeMCU Mini D1

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


Modul wifi ESP8266 NodeMCU Mini D1 là kit phát triển dựa trên nền chip Wifi SoC
ESP8266EX với thiết kế dễ dàng sử dụng vì tích hợp sẵn mạch nạp sử dụng chip CH340
trên bo mạch. Người dùng muốn nạp chương trình chỉ cần kết nối với máy tính qua cáp
USB (Loại dùng cho Laptop và điện thoại thông minh). Bạn có thể sử dụng trực tiếp phần
mềm IDE của Arduino để lập trình với bộ thư viện riêng để lập trình cho ESP.
Chương trình cho ESP8266 đọc dữ liệu từ Firebase điều khiển LED, như đã nói ở trên,
chân sử dụng kết nối led là D4.
#include <ESP8266WiFi.h>
#include <FirebaseArduino.h>
#define wifiName "wifiName" //Đây là SSID của mạng wifi bạn muốn ESP kết nối tới
#define wifiPass "wifiPass" //Đây là password
#define LED D4
String chipId = "Den 1";
//Bạn điền đường link của dự án firebase vào bên dưới
#define firebaseURl "link_dự_án.firebaseio.com"
// #define authCode " nếu dùng thì lấy auth từ dự án firebase, không thì bỏ qua"
int onlyt=1,onlys=1;
void setupFirebase() {
Firebase.begin(firebaseURl, authCode);
}
void setupWifi() {
//kết nối wifi với tên và mật khẩu từ hai mảng đã lưu
WiFi.begin(wifiName, wifiPass);
Serial.println("Hey i 'm connecting...");
while (WiFi.status() != WL_CONNECTED) {
Serial.println(".");
delay(500);
}
Serial.println();
Serial.println("I 'm connected and my IP address: ");
Serial.println(WiFi.localIP());
}
void setup() {
pinMode(LED, OUTPUT);
WiFi.mode(WIFI_OFF);
delay(50);
Serial.begin(115200);
delay(10);
setupWifi();
setupFirebase();
}
void getData() {
String path = chipId;
FirebaseObject object = Firebase.get(path);
String trangthai = object.getString("Trang Thai");
Serial.println("TRANG THAI DEN: ");
Serial.println(trangthai);
if(trangthai!="\0")
{
if(trangthai=="SANG")
{ if(onlys==1)
{
Firebase.setString(chipId+"/TT","SANG");
onlys=0;
onlyt=1;

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


};
analogWrite(LED,255);
}else
{
if(onlyt==1)
{
Firebase.setString(chipId+"/TT","TAT");
onlyt=0;
onlys=1;
};
analogWrite(LED,0);
}
}
}
void loop() {
getData();
}
Nếu bạn sử dụng authCode của dự án Firebase thì có thể lấy như hình hướng dẫn bên dưới

Hình 12.22 Hướng dẫn lấy Authcode của dự án Firebase

Như vậy đến đây ta cơ bản đã xây dựng được một ứng dụng IoTs với một chiếc đồng hồ
thông minh, việc điều khiển trở nên tiện lợi và đa dạng hơn. Chúc các bạn tiếp tục phát
triển những dự án hay phù hợp khác.

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


TỔNG KẾT

Đến đây các bạn cơ bản đã nắm được các kiến thức nền tảng với lập trình di động
trong ứng dụng điều khiển. Các bạn có thể dễ dàng xây dựng một ứng dụng IoTs phù hợp
cho riêng mình. Hệ điều hành Android là một hệ điều hành mở, ngày càng có nhiều hỗ trợ
người dùng hơn bao giờ hết. Ngày trước việc điều khiển bằng giọng nói, khuôn mặt, vân
tay,...có vẻ rất khó khăn, nhưng hiện tại Google cung cấp rất nhiều hàm API để ta dễ dàng
thực hiện điều đó.
Tác giả cố gắng cập nhật, thêm mới nhiều nội dung. Tuy nhiên kiến thức về lập trình
Android trong điều khiển và ứng dụng là rất rộng. Vì thế tác giả cũng chọn lọc một số phần
cốt lõi, phổ biến để đưa đến độc giả. Hi vọng ở lần tái bản tiếp theo sẽ có nhiều cải tiến
phù hợp hơn.
Rất cám ơn quý độc giả, các bạn sinh viên với lòng đam mê khoa học đã đọc hết
quyển sách này. Chúc các bạn luôn giữ nhiệt huyết sáng tạo, vận dụng các kiến thức vào
thực tế để giúp đất nước ngày càng tươi đẹp và phát triển.
Trân trọng.

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


TÀI LIỆU THAM KHẢO

[1] Nguyễn Văn Hiệp, Lập trình Android trong ứng dụng điều khiển, NXB Đại học Quốc gia
TPHCM – 2016
[2] Trang hỗ trợ các nhà lập trình Android http://developer.android.com , 01-09/2020
[3] Diễn đàn lập trình Android http://stackoverflow.com/questions , 25/03/2020
[4] Cộng đồng Arduino Việt Nam http://arduino.vn/, 28/05/2020

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

You might also like