ESP8266 kết nối Internet - Phần 2: Arduino gặp ESP8266, hai đứa nói chuyện bằng JSON

VI. Về bài trước

Bạn buộc phải xem bài viết ESP8266 kết nối Internet - Phần 1: Cài đặt ESP8266 làm một socket client kết nối tới socket server trong mạng LAN trước khi xem bài này. Bắt buộc phải xem, nếu không bạn sẽ không hiểu những phần sau này là gì.

Ở bài trước, chúng ta đã xây dựng phương thước giao tiếp giữa tầng 1 (socket server) và tầng 2 (ESP8266). Chúng ta đã xây dựng một chương trình thử nghiệm trên socket server để test ra lệnh cho ESP8266 và cũng thử nghiệm cho ESP8266 gửi sự kiện ngược lại Socket Server.

Hôm nay, chúng ta sẽ nghiên cứu về việc giao tiếp giữa tầng 2 (ESP8266) và tầng 3 (Arduino). Các bạn hãy cùng nhau theo dõi nhé.

VII. Phương thức giao tiếp giữa tầng 2 và tầng 3

Như đã thống nhất ở bài trước, chúng ta sẽ sử dụng Serial làm phương thức truyền đạt giữa tầng 2 và tầng 3. Phương thức Serial được dùng là vì nó đơn giản nhất, chỉ cần 2 dây mà thôi. Dữ liệu được truyền trong Serial là các dãi bit theo từng byte. Và thứ chúng ta làm việc chính là chuỗi dữ liệu. 

Vấn đề giao tiếp giữa tầng 2 và tầng 3 không phải là một vấn đề phức tạp. Vấn đề đó được tối giản hóa ở các thư viện như SerialCommandSoftwareSerial. Vậy quy trình vận chuyển này cụ thể như thế nào? Bạn hãy xem hình sau sẽ rõ:

Còn SoftwareSerial giúp chúng ta tạo một cổng Serial thứ hai. Vì sao lại cần cổng Serial thứ hai này? Vì trên mỗi tầng (Arduino và ESP8266) thông thường chỉ có một cổng Serial phần cứng (Arduino có một cổng, ESP8266 có 1 cổng đầy đủ và một cổng gửi (chỉ TX)). Những cổng Serial này được dùng để nạp code. Vì vậy, nếu trong quá trình nạp code mà các cổng này nối với thiết bị ngoại vi, ví dụ như: nối Arduino với ESP8266 hay nối với con LED, thì sẽ không nạp code được mà phải ngắt kết nối với các thiết bị ngoại vi, sau đó mới kết nối được. Vì vậy, một cổng Serial là không đủ cho dân chơi chúng ta! Và thật vui, khi Cộng đồng nguồn mở đã tạo ra một thư viện mang tên SoftwareSerial để chúng ta có thể tạo ra nhiều ứng dụng thú vị như dự án này chẳng hạn. Và ở mỗi bên, chúng ta sẽ tạo ra một cổng Serial ảo, từ đó dùng cổng này giao tiếp với Module còn lại.

Nhiệm vụ của thư viện SerialCommand đó là cho phép chúng ta định nghĩa các chuỗi gửi đi. Tuy nhiên, ở thư viện SerialCommand gốc, nó có nhiều hạn chế, như:

  • Chiều dài mỗi lệnh chỉ có 16 mà thôi. Vượt quá cái con số 16 này thì bỏ qua lệnh đó luôn devil. Trong khi đó ta lại gửi JSON mà, mà JSON thì độ dài phải thông thoán tầm vài chục đến vài trăm ký tự mới vui! Chứ 16 ký tự thì làm JSON ngán lắm ahihi. Nhiều bạn sẽ nghĩ đến việc vô file header và tăng buffer lên! Nhưng hãy bình tĩnh nào, nếu bạn tăng lên buffer lên 100 thì chúng ta sẽ vứt đi (100-16) * 10 = 840 byte vào vùng nhớ stack đó. Thử tăng lên 255 thì bạn sẽ thấy độ bựa của việc tăng này. Bản chất của việc này, đó là tác giả xem lệnh bằng: lệnh + danh sách các biến (nhiều biến cách nhau bằng giấu cách). Còn mình thì xem lệnh là tên lệnh mà thôi, còn danh sách biến nằm riêng, ngoài ra tất cả tham số được nén vô chuỗi JSON, và chuỗi này lưu trữ danh sách biến rồi. Dẫn đến thư viện cũ không còn phù hợp nữa.
  • Tối đa 10 lệnh. Cái này có thể dễ dàng chỉnh sửa trong file header.

Vì vậy, mình đã chỉnh sửa lại thư viện SerialCommand cho phù hợp với nhu cầu của mình trong dự án này. Và mình chia sẻ nó với các bạn, các bạn nào đang dùng thư viện SerialCommand cũ thì xóa đi, rồi cài thư viện mới của mình vô. Sau đây là cải tiến của thư viện:

  • Lệnh và danh sách các biến được chia ra làm hai phần khác nhau. Lệnh là một chuỗi có độ dài tối đa 10 ký tự (tính luôn ký tự \0 thì còn 9 ký tự).
  • Danh sách các biến chỉ là một chuỗi JSON đã được mã hóa. 
  • Cú pháp được thay đổi từ: <Lệnh>(dấu cách)<danh sách các biến, cách nhau bởi dấu cách>\r
    • thành: <Lệnh>\r<Chuỗi JSON mã hóa các biến>\r
    • Xem thêm ở ví dụ đầu tiên, bạn sẽ hiểu ngay vấn đề đó mà.
  • Mình chỉnh thư viện mặc định có thể chơi đến 16 lệnh. Các bạn có thẻ tăng hoặc giảm con số này khi bị báo lỗi không thể thêm lệnh được nữa. Mà mình thấy vầy là ok rồi ahihi.

Một số vấn đề mà mình thấy có thể xảy ra nhưng nếu bạn nối dây ngon lành thì nó sẽ không xảy ra. Nối dây ngon lành ở đây có nghĩa là bạn đã nối RX của mạch nhận vào TX của mạch gửi. Vậy là ok :D.

  • Nếu bị lỏng dây mà chuỗi json chưa đến mà lệnh sau đã đến => Lỗi. Nói chung ít khi xảy ra, mắc dây nối ổn là ok :).
  • Nếu bị tràn bộ đệm do chuỗi JSON quá dài thì coi như lệnh đó bị bỏ qua và không xử lý. Để khăc phục, bạn hãy tăng ARGUMENTBUFFER trong file header là ổn.

Để tiếp tục, bạn hãy tải về 2 thư viện nhé.

  • SerialCommand (mirror) (bản mới mình đã chỉnh sửa, nếu bạn đã có rồi thì hãy xóa và cập nhập lại)
  • SoftwareSerial (thông thường các bản Arduino 1.6.x trở lên đều có thư viện này)
  • ArduinoJson (mirror)

1. Ví dụ về một lệnh

Như đã nởi trên, một lệnh gồm 2 phần:

  • Tên lệnh
  • Chuỗi JSON chứa các tham số

Trong đó, tên lệnh là một chuỗi có tối đa 9 ký tự và phân biết hoa thường. Ví dụ: LEDKhAnhSP, ksp, KHaNHSP,... là những lệnh khác nhau.

Chuỗi JSON là một chuỗi được định nghĩa sẵn theo chuẩn JSON. Cả 2 đầu Arduino và ESP8266 đều dùng thư viện ArduinoJson để hiểu chuỗi này.

Cuối mỗi phần phải có ký tự '\r' để phân biệt.

Ví dụ về một lệnh

LED\r{"led":[true,false]}\r

KhAnhSP\r{"deptrai":true}\r

Bản thân lệnh không có nghĩa, nếu muốn lệnh có nghĩa thì tại Arduino hoặc ESP8266 (tùy vào mong muốn của bạn), bạn phải bắt tên lệnh mà mình muốn bắt, sau đó gán cho nó một cái hàm để xử lý cái lệnh đó.

2. Ví dụ về gán hàm cho một lệnh

Phần khai báo sẽ được nói ở phần dưới, ở đây mình demo ngắn thôi.

Ở hàm setup(),  sẽ cài đặt hàm khai báo lệnh

 sCmd.addCommand("LED",   led); //Khi có lệnh LED thì sẽ thực thi hàm led  

Bây giờ, ta cài đặt một hàm void led() {} đơn giản là có thể bắt được lệnh rồi

void led() {
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  //Chuỗi JSON là tham số!
  .
  .
  .
  .
  //hàm xử lý
}

3. Ví dụ 1: SocketServer điều khiển 2 con LED của Arduino

Lắp mạch

Linh kiện cần chuẩn bị:

Nôm na ta sẽ nối TX Software (2) của Arduino với RX Software (D1) của ESP8266, RX Software (3) của Arduino với TX Software (D2) của ESP8266. Vì Arduino Uno dùng điện áp IO 5V nên ta phải dùng mạch cần phân áp bằng 2 điện trở như hình để khi nối với mạch ESP8266 (sử dụng điện áp IO 3.3V) không bị hỏng. Còn 2 con đèn LED đỏ và xanh lần lượt nối với chân digital 4, 5. Đơn giản vậy thôi à!

Code Socket Server

Ở phần Socket Server, bạn có thể clone project socketServer2 của mình bằng cách

git clone https://github.com/ngohuynhngockhanh/socketServer2

hoặc bạn có thể sử dụng code ở bài trước và thay nội dung file index.js bằng nội dung sau:

const PORT = 3484;									//Đặt địa chỉ Port được mở ra để tạo ra chương trình mạng Socket Server

var http = require('http') 							//#include thư viện http - Tìm thêm về từ khóa http nodejs trên google nếu bạn muốn tìm hiểu thêm. Nhưng theo kinh nghiệm của mình, Javascript trong môi trường NodeJS cực kỳ rộng lớn, khi bạn bí thì nên tìm hiểu không nên ngồi đọc và cố gắng học thuộc hết cái reference (Tài liêu tham khảo) của nodejs làm gì. Vỡ não đó!
var socketio = require('socket.io')				//#include thư viện socketio

var ip = require('ip');
var app = http.createServer();					//#Khởi tạo một chương trình mạng (app)
var io = socketio(app);								//#Phải khởi tạo io sau khi tạo app!
app.listen(PORT);										// Cho socket server (chương trình mạng) lắng nghe ở port 3484
console.log("Server nodejs chay tai dia chi: " + ip.address() + ":" + PORT)

//Khi có mệt kết nối được tạo giữa Socket Client và Socket Server
io.on('connection', function(socket) {	
	//hàm console.log giống như hàm Serial.println trên Arduino
    console.log("Connected"); //In ra màn hình console là đã có một Socket Client kết nối thành công.
	
	var led = [true, false] //định nghĩa một mảng 1 chiều có 2 phần tử: true, false. Mảng này sẽ được gửi đi nhằm thay đổi sự sáng tắt của 2 con đèn LED đỏ và xanh. Dựa vào cài đặt ở Arduino mà đèn LEd sẽ bị bật hoặc tắt. Hãy thử tăng hoạt giảm số lượng biến của mảng led này xem. Và bạn sẽ hiểu điều kỳ diệu của JSON!
	
	//Tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 200ms
	var interval1 = setInterval(function() {
		//đảo trạng thái của mảng led, đảo cho vui để ở Arduino nó nhấp nháy cho vui.
		for (var i = 0; i < led.length; i++) {
			led[i] = !led[i]
		}
		
		//Cài đặt chuỗi JSON, tên biến JSON này là json 
		var json = {
			"led": led //có một phần tử là "led", phần tử này chứa giá trị của mảng led.
		}
		socket.emit('LED', json) //Gửi lệnh LED với các tham số của của chuỗi JSON
		console.log("send LED")//Ghi ra console.log là đã gửi lệnh LED
	}, 200)//200ms
	
	//Khi socket client bị mất kết nối thì chạy hàm sau.
	socket.on('disconnect', function() {
		console.log("disconnect") 	//in ra màn hình console cho vui
		clearInterval(interval1)		//xóa chu kỳ nhiệm vụ đi, chứ không xóa là cái task kia cứ chạy mãi thôi đó!
	})
});

Theo như nội dung code trên, socket server sẽ tiếp nhận socket client. Sau đó tạo một chu kỳ nhiệm vụ gửi lệnh LED với chuỗi json được cài đặt như trên để thử nghiệm khả năng nhận lệnh.

Bạn đừng quên chạy lệnh này để khởi động chương trình nhé.

node index.js

Quy trình của code server

Code ESP8266

ESP8266 sẽ tiếp nhận lệnh từ Socket Server, sau đó in ra màn hình Serial monitor bằng cổng Serial hardware, cùng lúc đó sẽ in ra màn hình Software Serial nhằm giao tiếp với Arduino.

#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <SocketIOClient.h>

//include thư viện để kiểm tra free RAM trên con esp8266
extern "C" {
  #include "user_interface.h"
}


const byte RX = D1;
const byte TX = D2;

SoftwareSerial mySerial = SoftwareSerial(RX, TX, false, 256); 

SocketIOClient client;
const char* ssid = "MACHTUDONG";          //Tên mạng Wifi mà Socket server của bạn đang kết nối
const char* password = "smarthome12345";  //Pass mạng wifi ahihi, anh em rãnh thì share pass cho mình với.

char host[] = "192.168.200.145";  //Địa chỉ IP dịch vụ, hãy thay đổi nó theo địa chỉ IP Socket server của bạn.
int port = 3484;                  //Cổng dịch vụ socket server do chúng ta tạo!

//từ khóa extern: dùng để #include các biến toàn cục ở một số thư viện khác. Trong thư viện SocketIOClient có hai biến toàn cục
// mà chúng ta cần quan tâm đó là
// RID: Tên hàm (tên sự kiện
// Rfull: Danh sách biến (được đóng gói lại là chuối JSON)
extern String RID;
extern String Rfull;


void setup()
{
    //Bật baudrate ở mức 57600 để giao tiếp với máy tính qua Serial
    Serial.begin(57600);
    mySerial.begin(57600); //Bật software serial để giao tiếp với Arduino, nhớ để baudrate trùng với software serial trên mạch arduino
    delay(10);

    //Việc đầu tiên cần làm là kết nối vào mạng Wifi
    Serial.print("Ket noi vao mang ");
    Serial.println(ssid);

    //Kết nối vào mạng Wifi
    WiFi.begin(ssid, password);

    //Chờ đến khi đã được kết nối
    while (WiFi.status() != WL_CONNECTED) { //Thoát ra khỏi vòng 
        delay(500);
        Serial.print('.');
    }

    Serial.println();
    Serial.println(F("Da ket noi WiFi"));
    Serial.println(F("Di chi IP cua ESP8266 (Socket Client ESP8266): "));
    Serial.println(WiFi.localIP());

    if (!client.connect(host, port)) {
        Serial.println(F("Ket noi den socket server that bai!"));
        return;
    }
}

void loop()
{

    //Khi bắt được bất kỳ sự kiện nào thì chúng ta có hai tham số:
    //  +RID: Tên sự kiện
    //  +RFull: Danh sách tham số được nén thành chuỗi JSON!
    if (client.monitor()) {

        //in ra serial cho Arduino
        mySerial.print(RID);
        mySerial.print('\r');
        mySerial.print(Rfull);
        mySerial.print('\r');

        //in ra serial monitor
        Serial.print(RID);
        Serial.print(' ');
        Serial.println(Rfull);
        
        //Kiểm tra xem còn dư bao nhiêu RAM, để debug
        uint32_t free = system_get_free_heap_size();
        Serial.println(free);
    }
    //Serial.println(client.connected());

    //Kết nối lại!
    if (!client.connected()) {
      client.reconnect(host, port);
    }
}

Bạn hãy xem lưu đồ code sau là hiểu ngay. Đoạn code trên nôm na sẽ giúp ta xây dựng một cầu nối từ Socket Server đến thẳng Arduino, chúng ta không cần quan tâm đến lệnh truyền đi làm gì, có lệnh gì thì chuyển xuống hết Arduino.

Code Arduino

Arduino sẽ nhận lệnh từ ESP8266. Arduino được cài đặt lệnh LED, nó sẽ thay đổi trạng thái của LED màu đỏ và LED màu xanh.

#include <ArduinoJson.h>
#include <SoftwareSerial.h>
#include <SerialCommand.h>  // Thêm vào sketch thư viện Serial Command
const byte RX = 3;          // Chân 3 được dùng làm chân RX
const byte TX = 2;          // Chân 2 được dùng làm chân TX

SoftwareSerial mySerial = SoftwareSerial(RX, TX); 


SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command
 
int red = 4, blue = 5; // led đỏ đối vô digital 4, led xanh đối vô digital 5
 
void setup() {
  //Khởi tạo Serial ở baudrate 57600 để debug ở serial monitor
  Serial.begin(57600);

  //Khởi tạo Serial ở baudrate 57600 cho cổng Serial thứ hai, dùng cho việc kết nối với ESP8266
  mySerial.begin(57600);
  
  //pinMode 2 đèn LED là OUTPUT
  pinMode(red,OUTPUT);
  pinMode(blue,OUTPUT);
  
  
  // Một số hàm trong thư viện Serial Command
  sCmd.addCommand("LED",   led); //Khi có lệnh LED thì sẽ thực thi hàm led  
  Serial.println("Da san sang nhan lenh");
}
 
void loop() {
  sCmd.readSerial();
  //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả
}
 
// hàm led_red sẽ được thực thi khi gửi hàm LED_RED
void led() {
  Serial.println("LED");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<200> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 200 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  int redStatus = root["led"][0];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!
  int blueStatus = root["led"][1];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!

  //kiểm thử giá trị
  Serial.print(F("redStatus "));
  Serial.println(redStatus);
  Serial.print(F("blueStatus "));
  Serial.println(blueStatus);


  //xuất ra màn hình
  digitalWrite(red, redStatus);
  digitalWrite(blue, blueStatus);
}

4. Ví dụ 2: SocketServer điều khiển 2 con LED của Arduino, rồi Arduino gửi dữ liệu trả về cho Socket Server

Việc lắp mạch như ví dụ 1 đã nói trên, chúng ta không cần phải làm gì thêm cả ahihi.

Code Socket Server

Bạn có thể clone code về từ git của mình

git clone https://github.com/ngohuynhngockhanh/socketServer3

hoặc sửa file index.js từ các dự án trước thành

const PORT = 3484;									//Đặt địa chỉ Port được mở ra để tạo ra chương trình mạng Socket Server

var http = require('http') 							//#include thư viện http - Tìm thêm về từ khóa http nodejs trên google nếu bạn muốn tìm hiểu thêm. Nhưng theo kinh nghiệm của mình, Javascript trong môi trường NodeJS cực kỳ rộng lớn, khi bạn bí thì nên tìm hiểu không nên ngồi đọc và cố gắng học thuộc hết cái reference (Tài liêu tham khảo) của nodejs làm gì. Vỡ não đó!
var socketio = require('socket.io')				//#include thư viện socketio

var ip = require('ip');
var app = http.createServer();					//#Khởi tạo một chương trình mạng (app)
var io = socketio(app);								//#Phải khởi tạo io sau khi tạo app!
app.listen(PORT);										// Cho socket server (chương trình mạng) lắng nghe ở port 3484
console.log("Server nodejs chay tai dia chi: " + ip.address() + ":" + PORT)

//Khi có mệt kết nối được tạo giữa Socket Client và Socket Server
io.on('connection', function(socket) {	
	//hàm console.log giống như hàm Serial.println trên Arduino
    console.log("Connected"); //In ra màn hình console là đã có một Socket Client kết nối thành công.
	
	var led = [true, false] //định nghĩa một mảng 1 chiều có 2 phần tử: true, false. Mảng này sẽ được gửi đi nhằm thay đổi sự sáng tắt của 2 con đèn LED đỏ và xanh. Dựa vào cài đặt ở Arduino mà đèn LEd sẽ bị bật hoặc tắt. Hãy thử tăng hoạt giảm số lượng biến của mảng led này xem. Và bạn sẽ hiểu điều kỳ diệu của JSON!
	
	//Tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 200ms
	var interval1 = setInterval(function() {
		//đảo trạng thái của mảng led, đảo cho vui để ở Arduino nó nhấp nháy cho vui.
		for (var i = 0; i < led.length; i++) {
			led[i] = !led[i]
		}
		
		//Cài đặt chuỗi JSON, tên biến JSON này là json 
		var json = {
			"led": led //có một phần tử là "led", phần tử này chứa giá trị của mảng led.
		}
		socket.emit('LED', json) //Gửi lệnh LED với các tham số của của chuỗi JSON
		console.log("send LED")//Ghi ra console.log là đã gửi lệnh LED
	}, 1000)//1000ms
	
	
	//Khi nhận được lệnh LED_STATUS
	socket.on('LED_STATUS', function(status) {
		//Nhận được thì in ra thôi hihi.
		console.log("recv LED", status)
	})
	
	//Khi socket client bị mất kết nối thì chạy hàm sau.
	socket.on('disconnect', function() {
		console.log("disconnect") 	//in ra màn hình console cho vui
		clearInterval(interval1)		//xóa chu kỳ nhiệm vụ đi, chứ không xóa là cái task kia cứ chạy mãi thôi đó!
	})
});

Mình giữ nguyên code của ví dụ trên, chỉ thêm một sự kiện đó là khi ở dưới ESP8266 gửi lên lệnh LED_STATUS thì in ra chuỗi JSON thu được.

Code ESP8266

#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <SocketIOClient.h>
#include <SerialCommand.h>  

//include thư viện để kiểm tra free RAM trên con esp8266
extern "C" {
  #include "user_interface.h"
}


const byte RX = D1;
const byte TX = D2;

SoftwareSerial mySerial(RX, TX, false, 256); 
SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command

SocketIOClient client;
const char* ssid = "MACHTUDONG";          //Tên mạng Wifi mà Socket server của bạn đang kết nối
const char* password = "smarthome12345";  //Pass mạng wifi ahihi, anh em rãnh thì share pass cho mình với.

char host[] = "192.168.200.145";  //Địa chỉ IP dịch vụ, hãy thay đổi nó theo địa chỉ IP Socket server của bạn.
int port = 3484;                  //Cổng dịch vụ socket server do chúng ta tạo!

//từ khóa extern: dùng để #include các biến toàn cục ở một số thư viện khác. Trong thư viện SocketIOClient có hai biến toàn cục
// mà chúng ta cần quan tâm đó là
// RID: Tên hàm (tên sự kiện
// Rfull: Danh sách biến (được đóng gói lại là chuối JSON)
extern String RID;
extern String Rfull;


void setup()
{
    //Bật baudrate ở mức 57600 để giao tiếp với máy tính qua Serial
    Serial.begin(57600);
    mySerial.begin(57600); //Bật software serial để giao tiếp với Arduino, nhớ để baudrate trùng với software serial trên mạch arduino
    delay(10);

    //Việc đầu tiên cần làm là kết nối vào mạng Wifi
    Serial.print("Ket noi vao mang ");
    Serial.println(ssid);

    //Kết nối vào mạng Wifi
    WiFi.begin(ssid, password);

    //Chờ đến khi đã được kết nối
    while (WiFi.status() != WL_CONNECTED) { //Thoát ra khỏi vòng 
        delay(500);
        Serial.print('.');
    }

    Serial.println();
    Serial.println(F("Da ket noi WiFi"));
    Serial.println(F("Di chi IP cua ESP8266 (Socket Client ESP8266): "));
    Serial.println(WiFi.localIP());

    if (!client.connect(host, port)) {
        Serial.println(F("Ket noi den socket server that bai!"));
        return;
    }

    sCmd.addDefaultHandler(defaultCommand); //Lệnh nào đi qua nó cũng bắt hết, rồi chuyển xuống hàm defaultCommand!
    Serial.println("Da san sang nhan lenh");
    
}

void loop()
{

    //Khi bắt được bất kỳ sự kiện nào thì chúng ta có hai tham số:
    //  +RID: Tên sự kiện
    //  +RFull: Danh sách tham số được nén thành chuỗi JSON!
    if (client.monitor()) {

        //in ra serial cho Arduino
        mySerial.print(RID);
        mySerial.print('\r');
        mySerial.print(Rfull);
        mySerial.print('\r');

        //in ra serial monitor
        Serial.print(RID);
        Serial.print(' ');
        Serial.println(Rfull);
        
        //Kiểm tra xem còn dư bao nhiêu RAM, để debug
        uint32_t free = system_get_free_heap_size();
        Serial.println(free);
    }

    //Kết nối lại!
    if (!client.connected()) {
      client.reconnect(host, port);
    }

    sCmd.readSerial();
}

void defaultCommand(String command) {
  char *json = sCmd.next();
  client.send(command, (String) json);//gửi dữ liệu về cho Socket Server

  //In ra serial monitor để debug
  Serial.print(command);
  Serial.print(' ');
  Serial.println(json);
  Serial.println("Tot lam, da gui du lieu roi, xem trong console cua Socket server di");
}

Code này căn bản là giống y hệt ví dụ trên. Mình chỉ thêm thư viện SerialCommand để bắt các lệnh từ Arduino gửi lên, sau đó gửi tại cho Socket Server. Nghĩa là, ESP8266 là trung gian vận chuyển dữ liệu giữa Arduino và Socket Server. ESP8266 bắt được lệnh gì từ Socket Server thì nó chuyển xuống hết Arduino, và khi ESP8266 bắt được lệnh gì từ Arduino thì nó lại chuyển hết lên Socket Server. Đoạn code này kể từ giờ về sau mình sẽ dùng cho ESP8266 trong mọi ví dụ còn lại. Vì nó đã hoàn chỉnh, ESP8266 làm đúng nhiệm vụ của nó, đó là làm cầu nối cho Arduino và Socket Server. Kể từ bây giờ, bạn gửi lệnh gì từ Server thì nó sẽ đến thẳng Arduino và ngược lại.

Code Arduino

#include <ArduinoJson.h>
#include <SoftwareSerial.h>
#include <SerialCommand.h>  // Thêm vào sketch thư viện Serial Command
const byte RX = 3;          // Chân 3 được dùng làm chân RX
const byte TX = 2;          // Chân 2 được dùng làm chân TX

SoftwareSerial mySerial = SoftwareSerial(RX, TX); 


SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command
 
int red = 4, blue = 5; // led đỏ đối vô digital 4, led xanh đối vô digital 5
 
void setup() {
  //Khởi tạo Serial ở baudrate 57600 để debug ở serial monitor
  Serial.begin(57600);

  //Khởi tạo Serial ở baudrate 57600 cho cổng Serial thứ hai, dùng cho việc kết nối với ESP8266
  mySerial.begin(57600);
  
  //pinMode 2 đèn LED là OUTPUT
  pinMode(red,OUTPUT);
  pinMode(blue,OUTPUT);
  
  
  // Một số hàm trong thư viện Serial Command
  sCmd.addCommand("LED",   led); //Khi có lệnh LED thì sẽ thực thi hàm led  
  Serial.println("Da san sang nhan lenh");
}
 
void loop() {
  sCmd.readSerial();
  //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả
}
 
// hàm led_red sẽ được thực thi khi gửi hàm LED_RED
void led() {
  Serial.println("LED");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<200> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 200 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  int redStatus = root["led"][0];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!
  int blueStatus = root["led"][1];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!

  //kiểm thử giá trị
  Serial.print(F("redStatus "));
  Serial.println(redStatus);
  Serial.print(F("blueStatus "));
  Serial.println(blueStatus);

  StaticJsonBuffer<200> jsonBuffer2;
  JsonObject& root2 = jsonBuffer2.createObject();
  root2["KhanhDepTrai"] = "Dung roi ahihi";
  root2["redStatus"] = redStatus;
  root2["blueStatus"] = blueStatus;

  //Tạo một mảng trong JSON
  JsonArray& data = root2.createNestedArray("data");
  data.add(redStatus); 
  data.add(blueStatus);
  
  
  //in ra cổng software serial để ESP8266 nhận
  mySerial.print("LED_STATUS");   //gửi tên lệnh
  mySerial.print('\r');           // gửi \r
  root2.printTo(mySerial); //gửi chuỗi JSON
  mySerial.print('\r');           // gửi \r

  //in ra Serial để debug
  root2.printTo(Serial); //Xuống dòng
  
  //xuất ra màn hình
  digitalWrite(red, redStatus);
  digitalWrite(blue, blueStatus);
}

Tương tự như ví dụ trên, tuy nhiên, mình đã tạo ra một lệnh trả lời là LED_STATUS, nó sẽ gửi JSON chữa các trường dữ liệu:

  • "Khanh dep trai" là một chuỗi tĩnh có nội dung "Dung roi ahihi"
  • "redStatus" là một số nguyên mang giá trị của biến redStatus
  • "blueStatus" là một số nguyên mang giá trị của biến blueStatus
  • "data" là một mảng dữ liệu
    • phần tử thứ 0 mang giá trị của biến redStatus
    • phần tử thứ 1 mang giá trị của biến redStatus

Sau đó gửi đúng lệnh theo cú pháp lệnh Serial!

<tên lệnh>\r<chuỗi JSON>\r

LED_STATUS\r{"KhanhDepTrai":"Dung roi ahihi","redStatus":0,"blueStatus":1,"data":[0,1]\r

5. Ví dụ 3: Ví dụ 2 + Đọc cảm biến mưa

Bạn hãy đọc bài viết về cảm biến mưa nếu chưa biết về cảm biến mưa nhé. Thực ra nó cũng giống như button, chúng ta đọc giá trị digital thôi mà haha.

Bạn lắp mạch này thêm vào phần mạch ở bài 2 nhé. Nếu bạn không có cảm biến mưa thì cũng không sao cả, kệ nó, giá trị sẽ được tự tạo random. Sẽ thú vị hơn nhiều khi không có module cảm biến mưa!

Code ESP8266

Như ví dụ 2

Code Socket Server

const PORT = 3484;									//Đặt địa chỉ Port được mở ra để tạo ra chương trình mạng Socket Server

var http = require('http') 							//#include thư viện http - Tìm thêm về từ khóa http nodejs trên google nếu bạn muốn tìm hiểu thêm. Nhưng theo kinh nghiệm của mình, Javascript trong môi trường NodeJS cực kỳ rộng lớn, khi bạn bí thì nên tìm hiểu không nên ngồi đọc và cố gắng học thuộc hết cái reference (Tài liêu tham khảo) của nodejs làm gì. Vỡ não đó!
var socketio = require('socket.io')				//#include thư viện socketio

var ip = require('ip');
var app = http.createServer();					//#Khởi tạo một chương trình mạng (app)
var io = socketio(app);								//#Phải khởi tạo io sau khi tạo app!
app.listen(PORT);										// Cho socket server (chương trình mạng) lắng nghe ở port 3484
console.log("Server nodejs chay tai dia chi: " + ip.address() + ":" + PORT)

//Khi có mệt kết nối được tạo giữa Socket Client và Socket Server
io.on('connection', function(socket) {	
	//hàm console.log giống như hàm Serial.println trên Arduino
    console.log("Connected"); //In ra màn hình console là đã có một Socket Client kết nối thành công.
	
	var led = [true, false] //định nghĩa một mảng 1 chiều có 2 phần tử: true, false. Mảng này sẽ được gửi đi nhằm thay đổi sự sáng tắt của 2 con đèn LED đỏ và xanh. Dựa vào cài đặt ở Arduino mà đèn LEd sẽ bị bật hoặc tắt. Hãy thử tăng hoạt giảm số lượng biến của mảng led này xem. Và bạn sẽ hiểu điều kỳ diệu của JSON!
	
	//Tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 200ms
	var interval1 = setInterval(function() {
		//đảo trạng thái của mảng led, đảo cho vui để ở Arduino nó nhấp nháy cho vui.
		for (var i = 0; i < led.length; i++) {
			led[i] = !led[i]
		}
		
		//Cài đặt chuỗi JSON, tên biến JSON này là json 
		var json = {
			"led": led //có một phần tử là "led", phần tử này chứa giá trị của mảng led.
		}
		socket.emit('LED', json) //Gửi lệnh LED với các tham số của của chuỗi JSON
		console.log("send LED")//Ghi ra console.log là đã gửi lệnh LED
	}, 1000)//1000ms
	
	
	//Khi nhận được lệnh LED_STATUS
	socket.on('LED_STATUS', function(status) {
		//Nhận được thì in ra thôi hihi.
		console.log("recv LED", status)
	})
	
	socket.on('RAIN', function(status) {
		console.log("recv RAIN", status)
	})
	
	//Khi socket client bị mất kết nối thì chạy hàm sau.
	socket.on('disconnect', function() {
		console.log("disconnect") 	//in ra màn hình console cho vui
		clearInterval(interval1)		//xóa chu kỳ nhiệm vụ đi, chứ không xóa là cái task kia cứ chạy mãi thôi đó!
	})
});

Hoặc bạn có thể clone về

git clone https://github.com/ngohuynhngockhanh/socketServer4

Nhớ chạy

node index,js

để khởi động socket server

Code Arduino

#include <ArduinoJson.h>
#include <SoftwareSerial.h>
#include <SerialCommand.h>  // Thêm vào sketch thư viện Serial Command
const byte RX = 3;          // Chân 3 được dùng làm chân RX
const byte TX = 2;          // Chân 2 được dùng làm chân TX

SoftwareSerial mySerial = SoftwareSerial(RX, TX); 


SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command
 
int red = 4, blue = 5; // led đỏ đối vô digital 4, led xanh đối vô digital 5
int rainSensor = 6; // Chân tín hiệu cảm biến mưa ở chân digital 6 (arduino)

const unsigned long CHU_KY_1_LA_BAO_NHIEU = 2000UL; //Cứ sau 2000ms = 2s thì chu kỳ lặp lại
 
void setup() {
  //Khởi tạo Serial ở baudrate 57600 để debug ở serial monitor
  Serial.begin(57600);

  //Khởi tạo Serial ở baudrate 57600 cho cổng Serial thứ hai, dùng cho việc kết nối với ESP8266
  mySerial.begin(57600);
  
  //pinMode 2 đèn LED là OUTPUT
  pinMode(red,OUTPUT);
  pinMode(blue,OUTPUT);

  pinMode(rainSensor,INPUT);// Đặt chân cảm biến mưa là INPUT, vì tín hiệu sẽ được truyền đến cho Arduino
  
  
  // Một số hàm trong thư viện Serial Command
  sCmd.addCommand("LED",   led); //Khi có lệnh LED thì sẽ thực thi hàm led  
  Serial.println("Da san sang nhan lenh");
}


unsigned long chuky1 = 0;
void loop() {
  //Khởi tạo một chu kỳ lệnh, chu kỳ là 2000ms
  if (millis() - chuky1 > CHU_KY_1_LA_BAO_NHIEU) {
    chuky1 = millis();
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& root = jsonBuffer.createObject();
    //đọc giá trị cảm biến rồi in ra root
    root["digital"] = digitalRead(rainSensor);
    root["message"] = digitalRead(rainSensor) ? "Khong mua" : "Co mua ne ahihi";


    //Gửi đi hoy!
    //in ra cổng software serial để ESP8266 nhận
    mySerial.print("RAIN");   //gửi tên lệnh
    mySerial.print('\r');           // gửi \r
    root.printTo(mySerial);        //gửi chuỗi JSON
    mySerial.print('\r');    
  }
  sCmd.readSerial();
  //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả
}
 
// hàm led_red sẽ được thực thi khi gửi hàm LED_RED
void led() {
  Serial.println("LED");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<200> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 200 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  int redStatus = root["led"][0];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!
  int blueStatus = root["led"][1];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!

  //kiểm thử giá trị
  Serial.print(F("redStatus "));
  Serial.println(redStatus);
  Serial.print(F("blueStatus "));
  Serial.println(blueStatus);

  StaticJsonBuffer<200> jsonBuffer2;
  JsonObject& root2 = jsonBuffer2.createObject();
  root2["redStatus"] = redStatus;
  root2["blueStatus"] = blueStatus;

  //Tạo một mảng trong JSON
  JsonArray& data = root2.createNestedArray("data");
  data.add(redStatus); 
  data.add(blueStatus);
  
  
  //in ra cổng software serial để ESP8266 nhận
  mySerial.print("LED_STATUS");   //gửi tên lệnh
  mySerial.print('\r');           // gửi \r
  root2.printTo(mySerial);        //gửi chuỗi JSON
  mySerial.print('\r');           // gửi \r

  //in ra Serial để debug
  root2.printTo(Serial); //Xuống dòng
  
  //xuất ra màn hình
  digitalWrite(red, redStatus);
  digitalWrite(blue, blueStatus);
}

Thành quả, mời bạn xem video.

Code Arduino thêm đọc giá trị Analog

#include <ArduinoJson.h>
#include <SoftwareSerial.h>
#include <SerialCommand.h>  // Thêm vào sketch thư viện Serial Command
const byte RX = 3;          // Chân 3 được dùng làm chân RX
const byte TX = 2;          // Chân 2 được dùng làm chân TX

SoftwareSerial mySerial = SoftwareSerial(RX, TX); 


SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command
 
int red = 4, blue = 5; // led đỏ đối vô digital 4, led xanh đối vô digital 5
int rainSensor = 6; // Chân tín hiệu cảm biến mưa ở chân digital 6 (arduino)
int rainSensorAnalog = 7;

const unsigned long CHU_KY_1_LA_BAO_NHIEU = 2000UL; //Cứ sau 2000ms = 2s thì chu kỳ lặp lại
 
void setup() {
  //Khởi tạo Serial ở baudrate 57600 để debug ở serial monitor
  Serial.begin(57600);

  //Khởi tạo Serial ở baudrate 57600 cho cổng Serial thứ hai, dùng cho việc kết nối với ESP8266
  mySerial.begin(57600);
  
  //pinMode 2 đèn LED là OUTPUT
  pinMode(red,OUTPUT);
  pinMode(blue,OUTPUT);

  pinMode(rainSensor,INPUT);// Đặt chân cảm biến mưa là INPUT, vì tín hiệu sẽ được truyền đến cho Arduino
  
  
  // Một số hàm trong thư viện Serial Command
  sCmd.addCommand("LED",   led); //Khi có lệnh LED thì sẽ thực thi hàm led  
  Serial.println("Da san sang nhan lenh");
}


unsigned long chuky1 = 0;
void loop() {
  //Khởi tạo một chu kỳ lệnh, chu kỳ là 2000ms
  if (millis() - chuky1 > CHU_KY_1_LA_BAO_NHIEU) {
    chuky1 = millis();
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& root = jsonBuffer.createObject();
    //đọc giá trị cảm biến rồi in ra root
    root["digital"] = digitalRead(rainSensor);
    root["message"] = digitalRead(rainSensor) ? "Khong mua" : "Co mua ne ahihi";
    root["analog"] = analogRead(rainSensorAnalog);

    //Gửi đi hoy!
    //in ra cổng software serial để ESP8266 nhận
    mySerial.print("RAIN");   //gửi tên lệnh
    mySerial.print('\r');           // gửi \r
    root.printTo(mySerial);        //gửi chuỗi JSON
    mySerial.print('\r');    
  }
  sCmd.readSerial();
  //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả
}
 
// hàm led_red sẽ được thực thi khi gửi hàm LED_RED
void led() {
  Serial.println("LED");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<200> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 200 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  int redStatus = root["led"][0];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!
  int blueStatus = root["led"][1];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!

  //kiểm thử giá trị
  Serial.print(F("redStatus "));
  Serial.println(redStatus);
  Serial.print(F("blueStatus "));
  Serial.println(blueStatus);

  StaticJsonBuffer<200> jsonBuffer2;
  JsonObject& root2 = jsonBuffer2.createObject();
  root2["redStatus"] = redStatus;
  root2["blueStatus"] = blueStatus;

  //Tạo một mảng trong JSON
  JsonArray& data = root2.createNestedArray("data");
  data.add(redStatus); 
  data.add(blueStatus);
  
  
  //in ra cổng software serial để ESP8266 nhận
  mySerial.print("LED_STATUS");   //gửi tên lệnh
  mySerial.print('\r');           // gửi \r
  root2.printTo(mySerial);        //gửi chuỗi JSON
  mySerial.print('\r');           // gửi \r

  //in ra Serial để debug
  root2.printTo(Serial); //Xuống dòng
  
  //xuất ra màn hình
  digitalWrite(red, redStatus);
  digitalWrite(blue, blueStatus);
}

Thêm một dòng đọc giá trị cảm biến mưa Analog!

Bạn có thể xóa các dòng Serial debug trong code ESP8266 đi để tránh các lỗi nhỏ.

#include <SoftwareSerial.h>
#include <ESP8266WiFi.h>
#include <SocketIOClient.h>
#include <SerialCommand.h>  

//include thư viện để kiểm tra free RAM trên con esp8266
extern "C" {
  #include "user_interface.h"
}


const byte RX = D1;
const byte TX = D2;

SoftwareSerial mySerial(RX, TX, false, 256); 
SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command

SocketIOClient client;
const char* ssid = "MACHTUDONG";          //Tên mạng Wifi mà Socket server của bạn đang kết nối
const char* password = "smarthome12345";  //Pass mạng wifi ahihi, anh em rãnh thì share pass cho mình với.

char host[] = "192.168.200.145";  //Địa chỉ IP dịch vụ, hãy thay đổi nó theo địa chỉ IP Socket server của bạn.
int port = 3484;                  //Cổng dịch vụ socket server do chúng ta tạo!

//từ khóa extern: dùng để #include các biến toàn cục ở một số thư viện khác. Trong thư viện SocketIOClient có hai biến toàn cục
// mà chúng ta cần quan tâm đó là
// RID: Tên hàm (tên sự kiện
// Rfull: Danh sách biến (được đóng gói lại là chuối JSON)
extern String RID;
extern String Rfull;


void setup()
{
    //Bật baudrate ở mức 57600 để giao tiếp với máy tính qua Serial
    Serial.begin(57600);
    mySerial.begin(57600); //Bật software serial để giao tiếp với Arduino, nhớ để baudrate trùng với software serial trên mạch arduino
    delay(10);

    //Việc đầu tiên cần làm là kết nối vào mạng Wifi
    Serial.print("Ket noi vao mang ");
    Serial.println(ssid);

    //Kết nối vào mạng Wifi
    WiFi.begin(ssid, password);

    //Chờ đến khi đã được kết nối
    while (WiFi.status() != WL_CONNECTED) { //Thoát ra khỏi vòng 
        delay(500);
        Serial.print('.');
    }

    Serial.println();
    Serial.println(F("Da ket noi WiFi"));
    Serial.println(F("Di chi IP cua ESP8266 (Socket Client ESP8266): "));
    Serial.println(WiFi.localIP());

    if (!client.connect(host, port)) {
        Serial.println(F("Ket noi den socket server that bai!"));
        return;
    }

    sCmd.addDefaultHandler(defaultCommand); //Lệnh nào đi qua nó cũng bắt hết, rồi chuyển xuống hàm defaultCommand!
    Serial.println("Da san sang nhan lenh");
    
}

void loop()
{

    //Khi bắt được bất kỳ sự kiện nào thì chúng ta có hai tham số:
    //  +RID: Tên sự kiện
    //  +RFull: Danh sách tham số được nén thành chuỗi JSON!
    if (client.monitor()) {
        //in ra serial cho Arduino
        mySerial.print(RID);
        mySerial.print('\r');
        mySerial.print(Rfull);
        mySerial.print('\r');
    }

    //Kết nối lại!
    if (!client.connected()) {
      client.reconnect(host, port);
    }

    sCmd.readSerial();
}

void defaultCommand(String command) {
  char *json = sCmd.next();
  client.send(command, (String) json);//gửi dữ liệu về cho Socket Server
}

Trong demo, bạn có thể để 57600 làm baudrate mặc định cho 2 cổng Software Serial. Nhưng thực tế cho thấy, nếu bạn để 38400 thì dữ liệu truyền sẽ ổn định và không bị lỗi gửi thiếu dữ liệu. Nếu bạn bị lỗi hãy thử để baudrate của software thấp xuống! Nhớ đổi ở cả Arduino và ESP8266 nhé.

5. Ví dụ 4: Ví dụ 2 + Ghi giá trị truyền từ Socket Server xuống màn hình LCD (thông qua module I2C LCD)

Nãy giờ chúng ta đã thử với một loại cảm biến, bây giờ chúng ta sẽ thử với việc dùng thư viện này bằng cách thử sử dụng với module I2C LCD. Module LCD 1602 sẽ nhận lệnh từ Arduino thông qua module I2C LCD. Module Arduino nhận lệnh trực tiếp từ Socket server mà ở đó ESP8266 làm trung gian trung chuyển lệnh giữa tầng 1 và tầng 3.

Code ESP8266

Như ví dụ 2

Code Socket Server

const PORT = 3484;									//Đặt địa chỉ Port được mở ra để tạo ra chương trình mạng Socket Server

var http = require('http') 							//#include thư viện http - Tìm thêm về từ khóa http nodejs trên google nếu bạn muốn tìm hiểu thêm. Nhưng theo kinh nghiệm của mình, Javascript trong môi trường NodeJS cực kỳ rộng lớn, khi bạn bí thì nên tìm hiểu không nên ngồi đọc và cố gắng học thuộc hết cái reference (Tài liêu tham khảo) của nodejs làm gì. Vỡ não đó!
var socketio = require('socket.io')				//#include thư viện socketio

var ip = require('ip');
var app = http.createServer();					//#Khởi tạo một chương trình mạng (app)
var io = socketio(app);								//#Phải khởi tạo io sau khi tạo app!
app.listen(PORT);										// Cho socket server (chương trình mạng) lắng nghe ở port 3484
console.log("Server nodejs chay tai dia chi: " + ip.address() + ":" + PORT)

//Khi có mệt kết nối được tạo giữa Socket Client và Socket Server
io.on('connection', function(socket) {	
	//hàm console.log giống như hàm Serial.println trên Arduino
    console.log("Connected"); //In ra màn hình console là đã có một Socket Client kết nối thành công.
	
	var led = [true, false] //định nghĩa một mảng 1 chiều có 2 phần tử: true, false. Mảng này sẽ được gửi đi nhằm thay đổi sự sáng tắt của 2 con đèn LED đỏ và xanh. Dựa vào cài đặt ở Arduino mà đèn LEd sẽ bị bật hoặc tắt. Hãy thử tăng hoạt giảm số lượng biến của mảng led này xem. Và bạn sẽ hiểu điều kỳ diệu của JSON!
	
	//Tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 1000ms
	var interval1 = setInterval(function() {
		//đảo trạng thái của mảng led, đảo cho vui để ở Arduino nó nhấp nháy cho vui.
		for (var i = 0; i < led.length; i++) {
			led[i] = !led[i]
		}
		
		//Cài đặt chuỗi JSON, tên biến JSON này là json 
		var json = {
			"led": led //có một phần tử là "led", phần tử này chứa giá trị của mảng led.
		}
		socket.emit('LED', json) //Gửi lệnh LED với các tham số của của chuỗi JSON
		console.log("send LED")//Ghi ra console.log là đã gửi lệnh LED
	}, 1000)//1000m
	
	//tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 1569ms
	var interval2 = setInterval(function() {
		socket.emit("LCD_PRINT", {
			"line": [
				new Date(),
				"KSP dep trai ahihi"
			]
		})
	}, 1569)
	
	//tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 2569ms
	var i = 0;
	var interval3 = setInterval(function() {
		socket.emit("LCD_PRINT", {
			"line": [
				"arduino.vn " + (++i).toString(),
				new Date(),
			]
		})
	}, 2569)
	
	
	//Khi nhận được lệnh LED_STATUS
	socket.on('LED_STATUS', function(status) {
		//Nhận được thì in ra thôi hihi.
		console.log("recv LED", status)
	})
	
	socket.on('RAIN', function(status) {
		console.log("recv RAIN", status)
	})
	
	//Khi socket client bị mất kết nối thì chạy hàm sau.
	socket.on('disconnect', function() {
		console.log("disconnect") 	//in ra màn hình console cho vui
		clearInterval(interval1)		//xóa chu kỳ nhiệm vụ đi, chứ không xóa là cái task kia cứ chạy mãi thôi đó!
		clearInterval(interval2)
		clearInterval(interval3)			
	})
});

Hoặc bạn có thể clone về

git clone https://github.com/ngohuynhngockhanh/socketServer5

Nhớ chạy

node index,js

để khởi động socket server.

Trong đoạn code trên, các bạn hãy chú ý đến interval2, và interval3. Đây là hai chu kỳ lệnh, mà mình dùng trong việc thử nghiệm module I2C LCD. Cứ sau mỗi chu kỳ 1569ms và 2569ms thì LCD sẽ được thay đổi nội dung. Sự thay đổi dựa vào lệnh nào tới và được xử lý bới Arduino. Các bạn có thể xem video sau để rõ.

Code Arduino

#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
#include <SoftwareSerial.h>
#include <SerialCommand.h>  // Thêm vào sketch thư viện Serial Command
const byte RX = 3;          // Chân 3 được dùng làm chân RX
const byte TX = 2;          // Chân 2 được dùng làm chân TX



SoftwareSerial mySerial = SoftwareSerial(RX, TX); 


SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command
 
int red = 4, blue = 5; // led đỏ đối vô digital 4, led xanh đối vô digital 5

//Bạn đừng lo, nếu ko có cảm biến mưa thì cũng chả sao, cứ để đó haha.
int rainSensor = 6; // Chân tín hiệu cảm biến mưa ở chân digital 6 (arduino)
int rainSensorAnalog = A0;

LiquidCrystal_I2C lcd(0x27,16,2);


const unsigned long CHU_KY_1_LA_BAO_NHIEU = 2000UL; //Cứ sau 2000ms = 2s thì chu kỳ lặp lại
 
void setup() {
  //Khởi tạo Serial ở baudrate 57600 để debug ở serial monitor
  Serial.begin(57600);

  //Khởi tạo Serial ở baudrate 57600 cho cổng Serial thứ hai, dùng cho việc kết nối với ESP8266
  mySerial.begin(57600);
  
  //pinMode 2 đèn LED là OUTPUT
  pinMode(red,OUTPUT);
  pinMode(blue,OUTPUT);

  pinMode(rainSensor,INPUT);// Đặt chân cảm biến mưa là INPUT, vì tín hiệu sẽ được truyền đến cho Arduino
  
 
  //Cài đặt LCD
  lcd.init();  
  //Bật đèn màn hình LCD
  lcd.backlight();
  //in ra màn hình chào mừng
  lcd.print("Xin chao");

  
  // Một số hàm trong thư viện Serial Command
  sCmd.addCommand("LED",   led); //Khi có lệnh LED thì sẽ thực thi hàm led  
  sCmd.addCommand("LCD_PRINT",   lcd_print);
  Serial.println("Da san sang nhan lenh");
}


unsigned long chuky1 = 0;
void loop() {
  //Khởi tạo một chu kỳ lệnh, chu kỳ là 2000ms
  if (millis() - chuky1 > CHU_KY_1_LA_BAO_NHIEU) {
    chuky1 = millis();
    StaticJsonBuffer<200> jsonBuffer;
    JsonObject& root = jsonBuffer.createObject();
    //đọc giá trị cảm biến rồi in ra root
    root["digital"] = digitalRead(rainSensor);
    root["message"] = digitalRead(rainSensor) ? "Khong mua" : "Co mua ne ahihi";
    root["analog"] = analogRead(rainSensorAnalog);

    //Gửi đi hoy!
    //in ra cổng software serial để ESP8266 nhận
    mySerial.print("RAIN");   //gửi tên lệnh
    mySerial.print('\r');           // gửi \r
    root.printTo(mySerial);        //gửi chuỗi JSON
    mySerial.print('\r');    
  }
  sCmd.readSerial();
  //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả
}
 
// hàm led_red sẽ được thực thi khi gửi hàm LED_RED
void led() {
  Serial.println("LED");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<200> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 200 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  int redStatus = root["led"][0];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!
  int blueStatus = root["led"][1];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!

  //kiểm thử giá trị
  Serial.print(F("redStatus "));
  Serial.println(redStatus);
  Serial.print(F("blueStatus "));
  Serial.println(blueStatus);

  StaticJsonBuffer<200> jsonBuffer2;
  JsonObject& root2 = jsonBuffer2.createObject();
  root2["redStatus"] = redStatus;
  root2["blueStatus"] = blueStatus;

  //Tạo một mảng trong JSON
  JsonArray& data = root2.createNestedArray("data");
  data.add(redStatus); 
  data.add(blueStatus);
  
  
  //in ra cổng software serial để ESP8266 nhận
  mySerial.print("LED_STATUS");   //gửi tên lệnh
  mySerial.print('\r');           // gửi \r
  root2.printTo(mySerial);        //gửi chuỗi JSON
  mySerial.print('\r');           // gửi \r

  //in ra Serial để debug
  root2.printTo(Serial); //Xuống dòng
  
  //xuất ra màn hình
  digitalWrite(red, redStatus);
  digitalWrite(blue, blueStatus);
}

void lcd_print() {
  Serial.println("LCD_PRINT");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<200> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 200 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  char *line1 = root["line"][0];
  char *line2 = root["line"][1];

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(line1);
  lcd.setCursor(0, 1);
  lcd.print(line2);
  Serial.println("ok aihi");
}

Ở code này, khi nhận được lệnh LCD_PRINT, thì hàm lcd_print được gọi. Sau khi đọc được chuỗi JSON thì mọi thứ đã trở nên đơn giản hơn rất nhiều.

6. Ví dụ 5: Ví dụ 1 + Ghi giá trị truyền từ Socket Server xuống màn hình LCD và điều khiển Servo

Khi điều khiển động cơ, thì việc quan trọng nhất để giữ sự ổn định đó là lọc nhiễu. Qua ví dụ này, mình muốn cho các bạn thấy, vì sao thư viện của mình chỉ dùng cho việc demo làm các bản thử (prototype), mà không thể dùng trong các dự án đưa ra thị trường rộng rãi.

Sự nhiễu của động cơ là rất lớn, vì dòng điện cảm ứng sinh ra từ motor làm cho việc điều khiển nó không phải là điều dễ dàng. Nhưng nếu bạn lọc nhiễu tốt cho động cơ, lọc được dòng điện cảm ứng phản hồi thì sự nhiễu sẽ mất đi. Trong ví dụ trên, cữ mối 200ms thì mình lại thay đổi góc của servo đi một lần, mỗi lần một đơn vị! Và ở cuối video, vì thay đổi quá nhiều nên làm toàn bộ bị nhiễu và ngừng hoạt động. Các bạn hãy thử lại ví dụ và làm nó hoàn hảo hơn nhé.

Code ESP8266 

không thay đổi

Code Arduino

#include <LiquidCrystal_I2C.h>
#include <ArduinoJson.h>
#include <SoftwareSerial.h>
#include <Servo.h>
#include <SerialCommand.h>  // Thêm vào sketch thư viện Serial Command
const byte RX = 3;          // Chân 3 được dùng làm chân RX
const byte TX = 2;          // Chân 2 được dùng làm chân TX



SoftwareSerial mySerial = SoftwareSerial(RX, TX); 


SerialCommand sCmd(mySerial); // Khai báo biến sử dụng thư viện Serial Command
 
int red = 4, blue = 5; // led đỏ đối vô digital 4, led xanh đối vô digital 5

int servoPin = 7;
Servo servo;


LiquidCrystal_I2C lcd(0x27,16,2);


void setup() {
  //Khởi tạo Serial ở baudrate 57600 để debug ở serial monitor
  Serial.begin(57600);

  //Khởi tạo Serial ở baudrate 57600 cho cổng Serial thứ hai, dùng cho việc kết nối với ESP8266
  mySerial.begin(57600);
  
  //pinMode 2 đèn LED là OUTPUT
  pinMode(red,OUTPUT);
  pinMode(blue,OUTPUT);

 
  //Cài đặt LCD
  lcd.init();  
  //Bật đèn màn hình LCD
  lcd.backlight();
  //in ra màn hình chào mừng
  lcd.print("Xin chao");

  //cài đặt servo
  servo.attach(servoPin);
  
  // Một số hàm trong thư viện Serial Command
  sCmd.addCommand("LED",   led); //Khi có lệnh LED thì sẽ thực thi hàm led  
  sCmd.addCommand("SERVO", servo_enjoy);
  sCmd.addCommand("LCD_PRINT",   lcd_print);
  Serial.println("Da san sang nhan lenh");
}


unsigned long chuky1 = 0;
void loop() {
  sCmd.readSerial();
  //Bạn không cần phải thêm bất kỳ dòng code nào trong hàm loop này cả
}
 
// hàm led_red sẽ được thực thi khi gửi hàm LED_RED
void led() {
  Serial.println("LED");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<100> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 100 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  int redStatus = root["led"][0];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!
  int blueStatus = root["led"][1];//json -> tham số root --> phần tử thứ 0. Đừng lo lắng nếu bạn không có phần tử này, không có bị lỗi đâu!

  //kiểm thử giá trị
  Serial.print(F("redStatus "));
  Serial.println(redStatus);
  Serial.print(F("blueStatus "));
  Serial.println(blueStatus);

  StaticJsonBuffer<100> jsonBuffer2;
  JsonObject& root2 = jsonBuffer2.createObject();
  root2["redStatus"] = redStatus;
  root2["blueStatus"] = blueStatus;

  //Tạo một mảng trong JSON
  JsonArray& data = root2.createNestedArray("data");
  data.add(redStatus); 
  data.add(blueStatus);
  
  
  //in ra cổng software serial để ESP8266 nhận
  mySerial.print("LED_STATUS");   //gửi tên lệnh
  mySerial.print('\r');           // gửi \r
  root2.printTo(mySerial);        //gửi chuỗi JSON
  mySerial.print('\r');           // gửi \r

  //in ra Serial để debug
  root2.printTo(Serial); //Xuống dòng
  
  //xuất ra màn hình
  digitalWrite(red, redStatus);
  digitalWrite(blue, blueStatus);
}

void lcd_print() {
  Serial.println("LCD_PRINT");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<100> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 100 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  char *line1 = root["line"][0];
  char *line2 = root["line"][1];

  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(line1);
  lcd.setCursor(0, 1);
  lcd.print(line2);
  Serial.println("ok aihi");
}


void servo_enjoy() {
  Serial.println("SERVO");
  char *json = sCmd.next(); //Chỉ cần một dòng này để đọc tham số nhận đươc
  Serial.println(json);
  StaticJsonBuffer<50> jsonBuffer; //tạo Buffer json có khả năng chứa tối đa 100 ký tự
  JsonObject& root = jsonBuffer.parseObject(json);//đặt một biến root mang kiểu json

  int degree = root["degree"];
  const char *message = root["message"];
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print(message);
  servo.write(degree);
  Serial.println(degree);
}

Code Socket Server

const PORT = 3484;									//Đặt địa chỉ Port được mở ra để tạo ra chương trình mạng Socket Server

var http = require('http') 							//#include thư viện http - Tìm thêm về từ khóa http nodejs trên google nếu bạn muốn tìm hiểu thêm. Nhưng theo kinh nghiệm của mình, Javascript trong môi trường NodeJS cực kỳ rộng lớn, khi bạn bí thì nên tìm hiểu không nên ngồi đọc và cố gắng học thuộc hết cái reference (Tài liêu tham khảo) của nodejs làm gì. Vỡ não đó!
var socketio = require('socket.io')				//#include thư viện socketio

var ip = require('ip');
var app = http.createServer();					//#Khởi tạo một chương trình mạng (app)
var io = socketio(app);								//#Phải khởi tạo io sau khi tạo app!
app.listen(PORT);										// Cho socket server (chương trình mạng) lắng nghe ở port 3484
console.log("Server nodejs chay tai dia chi: " + ip.address() + ":" + PORT)

//Khi có mệt kết nối được tạo giữa Socket Client và Socket Server
io.on('connection', function(socket) {	
	//hàm console.log giống như hàm Serial.println trên Arduino
    console.log("Connected"); //In ra màn hình console là đã có một Socket Client kết nối thành công.
	
	var led = [true, false] //định nghĩa một mảng 1 chiều có 2 phần tử: true, false. Mảng này sẽ được gửi đi nhằm thay đổi sự sáng tắt của 2 con đèn LED đỏ và xanh. Dựa vào cài đặt ở Arduino mà đèn LEd sẽ bị bật hoặc tắt. Hãy thử tăng hoạt giảm số lượng biến của mảng led này xem. Và bạn sẽ hiểu điều kỳ diệu của JSON!
	
	//Tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 1000ms
	var interval1 = setInterval(function() {
		//đảo trạng thái của mảng led, đảo cho vui để ở Arduino nó nhấp nháy cho vui.
		for (var i = 0; i < led.length; i++) {
			led[i] = !led[i]
		}
		
		//Cài đặt chuỗi JSON, tên biến JSON này là json 
		var json = {
			"led": led //có một phần tử là "led", phần tử này chứa giá trị của mảng led.
		}
		socket.emit('LED', json) //Gửi lệnh LED với các tham số của của chuỗi JSON
		console.log("send LED")//Ghi ra console.log là đã gửi lệnh LED
	}, 1000)//1000m
	
	
	//tạo một chu kỳ nhiệm vụ sẽ chạy lại sau mỗi 2569ms
	var degree = 0;
	var k = 1;
	var interval2 = setInterval(function() {
		if (degree >= 180)
			k = -1 
		if (degree <= 0)
			k = 1
		
		degree += k;
		//gửi lệnh SERVO với hai tham số đến Arduino
		socket.emit('SERVO', {
			'degree': degree,
			"message":"Degree: " + degree
		})
	}, 200)
	
	
	//Khi nhận được lệnh LED_STATUS
	socket.on('LED_STATUS', function(status) {
		//Nhận được thì in ra thôi hihi.
		console.log("recv LED", status)
	})
	
	//Khi socket client bị mất kết nối thì chạy hàm sau.
	socket.on('disconnect', function() {
		console.log("disconnect") 	//in ra màn hình console cho vui
		clearInterval(interval1)		//xóa chu kỳ nhiệm vụ đi, chứ không xóa là cái task kia cứ chạy mãi thôi đó!
		clearInterval(interval2)			
	})
});

Chúc các bạn thành công.

Phần tiếp theo của mình sẽ nói về việc xây dựng một website mini mà bạn có thể truy cập vào và điều khiển thiêt bị từ xa từ điện thoại của mình.

lên
8 thành viên đã đánh giá bài viết này hữu ích.
Chuyên mục: 
Các dự án được truyền cảm hứng

Bộ điều khiển PID - ứng dụng phần 2 - xe dò line dùng thuật toán PID

Tiép nối bài viết về xe dò line cảm ơn Đỗ Hữu Toàn đã viết hộ mình phần 4. hôm nay mình sẽ làm cho chiếc xe dò line đi mượt và có hồn hơn 

lên
34 thành viên đã đánh giá bài viết này hữu ích.
Các bài viết cùng tác giả

Module Relay - Cách sử dụng rơ le và những ứng dụng hay của nó

Rơ-le là một loại linh kiện điện tử thụ động rất hay gặp trong các ứng dụng thực tế. Khi bạn gặp các vấn đề liên quan đến công suất và cần sự ổn định cao, ngoài ra có thể dễ dàng bảo trì, thì rơ-le chính là cái bạn cần tìm. Vì vậy, hôm nay, chúng ta sẽ cùng nhau tìm hiểu về relay và các ứng dụng của nó trong cuộc sống!

lên
74 thành viên đã đánh giá bài viết này hữu ích.

kLaserCutter - Tự làm máy cắt laser bằng mã nguồn người Việt - Phần 1: "In" máy cắt của chính bạn

Tớ là một người rất thích bộ môn nghệ thuật Kirigami – cắt giấy. Tuy nhiên, tớ không phải là một người khéo tay và thường xuyên cắt phạm giấy hoặc bị thương. Nhưng không vì thế mà khiến tớ bỏ qua bộ môn nghệ thuật đòi hỏi sự sáng tạo này. Các bạn thấy đấy, tớ đã đặt mục tiêu xây dựng chiếc máy cắt laser dưới 1 triệu đồng và đã hoàn thiện được nó. Tuy nhiên, trong phiên bản đó, vẫn có những điều tớ chưa hài lòng và cuối cùng những điều đó đã được khắc phục trong phiên bản máy cắt laser mã nguồn và phần cứng mở kLaserCutter - dự án phần cứng cùng với phần mềm mở đầu tiên ở Việt Nam.

lên
26 thành viên đã đánh giá bài viết này hữu ích.