ESP8266 kết nối Internet - Phần 1.1: ESP8266 đi thuê phòng ở khách sạn Socket Server

Ở bài viết 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. Trong bài này, chúng ta đã làm mô hình một thiết bị ESP8266 kết nối vào Socket Server. Nhưng trong thực tế, Socket là một mô hình mạng có thể kết nối nhiều thiết bị với nhau. Và qua bài viết này, mình làm một ví dụ cho ESP8266 kết nối với một ESP8266 khác. Cùng khám phá nhé.

I. ESP8266 trong mô hình có thể mở rộng hơn hay không?

Socket Server là một ông trùm (server), nơi mà mọi client (ESP8266) hay Webapp phải kết nối đến. Như vậy, theo mô hình này, nếu ta có nhiều ESP8266 thì sẽ như thế nào nhỉ? Em hèm, thật sự là phức tạp phải không nào? Vì nó mâu thuẫn với mô hình này vì chỉ có một con ESP8266. Như vậy, để làm thêm một con ESP8266 nữa thì phải sửa lại mô hình hay sao?

 Nếu các bạn đã đọc qua phần 2, phần 3 thì lúc này bạn sẽ biết đến khái niệm namespace thì việc thêm một con ESP8266 bằng cách phá vỡ mô hình thì không có chuyện gì khó. Tuy nhiên, nếu chỉ mới đọc phần 1, thì để giải quyết vấn đề có 2, 3 hay nhiều con ESP8266 ta phải làm quen với khái niệm room.

Room: tức là phòng. Như vậy để dễ hiểu, các bạn cứ xem Socket Server là một khách điếm (hotel - khách sạn). Ở khách điếm này nếu một bé ESP8266 đến thuê phòng thì khách điếm này cấp cho ESP8266 một phòng riêng theo chính id duy nhất (gọi là socket id)cho kết nối từ ESP8266 (lưu ý id này khác với id của ESP8266). Theo như hình dưới, mỗi khi thực hiện (2) Tạo ra một socket mới rồi đưa vào sự quản lý của IO thì ESP8266 sẽ được vào một Room Room này có mã là socket id. Về bản chất khái niệm Room đã tồn tại sẵn, tức là mỗi con ESP8266 sẽ ở một phòng riêng mà không ai ở chung với nhau hết. Nhưng những phòng này sẽ cùng thực hiện những lệnh khi nó nghe được vì nó cùng một loại, mà loại này là ESP8266 (tức là khái niệm namespace trong phần 2 và phần 3).

Vậy để phân biệt giữa các ESP8266 với nhau, chúng ta cần biết được socket id của con ESP8266 còn lại và bạn sẽ định tuyến dữ liệu đến dữ liệu ESP8266 đó. Thành ra, nếu bạn muốn có sự truyền tin giữa các bé ESP8266 lẫn nhau, chúng ta phải giải quyết được vấn đề định tuyến giữa những bé ESP8266 này.

Ta biết rằng, các em ESP8266 là cùng một loại, tức là cùng namespace. Trong bài viết này, ta giữ nguyên mô hình và mặc định gán các bé ESP8266 cùng một namespace rỗng (gọi là root namespace).

Để đơn giản hóa hơn nữa, trong bài viết này, mình sẽ thu hẹp lại phạm vi bài toán thành như sau:

  • Có 02 con ESP8266:
    • 01 con là transmitter (bên gửi).
    • 01 con là receiver (bên nhận).
  • Transmitter sẽ gửi dữ liệu trên Serial cho Receiver.
  • Socket Server sẽ nhận dữ liệu từ Transmitter và gửi đến ReceiverCái này gọi là định tuyến.

Vậy quay lại câu hỏi đề bài. Để trả lời nó, chúng ta sẽ trả lời cho bài toán trên.

II. Phân biệt giữa các ESP8266

Để phân biệt giữa các ESP8266 với nhau, cách duy nhất là ta phân biệt dựa theo Room (số phòng). Vậy để transmitter gửi được dữ liệu tới receiver, ta cần cho transmitter biết địa chỉ phòng của receiver. Tức là receiver phải gửi được địa chỉ socket id của nó (tức là phòng mặc định) đến cho transmitter. Ối trời ơi, vậy thì rơi vào vòng loop mất tiêu rồi, muốn tới B, nhưng phải gửi thông tin cho A. Có thần cũng không gửi được dữ liệu. Ok, đặt vào trường hợp thực tế, bạn shipper muốn gửi hàng đến địa chỉ cho bạn nhận đồ thì bạn shipper sẽ phải biết địa chỉ của bạn nhận đồ. Thật vậy, nếu receiver cố định một địa chỉ chứ không phải thay đổi địa chỉ liên tục mỗi lần kết nối tới socket server thì có phải ngon lành không? Như thế thì phá vỡ được vòng loop, từ đấy mới thấy mọi thứ thực tế luôn có thể ứng dụng trong lập trình đó mà. Nếu receiver cố định được địa chỉ thì thôi thì làm transmitter cố định luôn cho rồi *ahihi*. Okie, vấn đề sẽ được giải quyết nếu chúng ta có thể sét được một Room cố định cho mỗi em ESP8266. 

Okie, theo như thư viện Socket.io thì mỗi socket client có thể tham gia vào nhiều phòng và mỗi phòng có thể nhiều socket client.

Vậy, câu hỏi đầu bài đã được giải quyết. Chỉ cần sét Room cố định (thực ra là socket client ở thêm một phòng nữa mà cái id phòng đó cố định thôi à) là xong thôi. Nhưng nãy giờ mình vòng vo giải thích là để giúp cho các bạn hiểu từ một vấn đề thực tế và liên kết đến tài liệu hướng dẫn thôi á.

III. Giải quyết bài toán

1. Socket Server

Đầu tiên, các bạn clone project của mình về

git clone https://github.com/ngohuynhngockhanh/socketServer1.1.git

Sau đó để cài đặt project socket server

cd socketServer1.1

npm install

Chạy lệnh sau để khởi động Socket Server.

npm start

Đây là phần code trong Socket Server

const PORT = 3484;									//Đặt địa chỉ Port được mở ra để tạo ra chương trình mạng Socket Server
 
var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

server.listen(PORT);

app.get('/', function (req, res) {
  res.end("Hello World")
});

var ip = require('ip');
									// 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)
 
//giải nén chuỗi JSON thành các OBJECT
function ParseJson(jsondata) {
    try {
        return JSON.parse(jsondata);
    } catch (error) {
        return null;
    }
}
 
 
//Khi có mệt kết nối được tạo giữa Socket Client và Socket Server
io.on('connection', function(socket) {	//'connection' (1) này khác gì với 'connection' (2)
	//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.
	
	//Gửi đi lệnh 'welcome' với một tham số là một biến JSON. Trong biến JSON này có một tham số và tham số đó tên là message. Kiểu dữ liệu của tham số là một chuối.
    socket.emit('welcome', {
        message: 'Connected !!!!'
    });
	
	//khi lắng nghe được lệnh "atime" với một tham số, và chúng ta đặt tên tham số đó là data. Mình thích thì mình đặt thôi
    socket.on('transmit', function(data) {
        console.log("Transmit data", data)	//ghi log ra			
		socket.broadcast.to("receiver").emit('FROM TRANSMITTER', data); //Gửi dữ liệu xuống node receiver
    });
	
	socket.on('join', function(data) { //khi nhận lệnh join thì tham gia vào một phòng nào đó
		console.log("Join to ROOM", data)	// ghi log ra
		socket.join(data.id)				// join vào một phòng
	})
	
});

2. Code Transmitter

Mình có bình luận trong code, bạn xem và sáng tạo nhé!

#include <ESP8266WiFi.h>
#include <SocketIOClient.h>
 
SocketIOClient client;
#define BUFFER_LENGTH 200

const char* ssid = "linksys";          //Tên mạng Wifi mà Socket server của bạn đang kết nối
const char* password = "khanhsuphu";  //Pass mạng wifi ahihi, anh em rãnh thì share pass cho mình với.
 
char host[] = "192.168.2.107";  //Đị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!

char buffer[BUFFER_LENGTH];
//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;
 
 
//Một số biến dùng cho việc tạo một task
unsigned long previousMillis = 0;
long interval = 2000;
 
void setup()
{
    //Bật baudrate ở mức 115200 để giao tiếp với máy tính qua Serial
    Serial.begin(115200);
    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!"));
        while(1);
    }
    //Gửi lệnh join với json {id: "transmitter"}
    client.send("join", "id", "transmitter");
 
    //Khi đã kết nối thành công
    if (client.connected()) {
        Serial.println("Da ket noi den Server");
        //Thì gửi sự kiện ("connection") đến Socket server ahihi.
        client.send("connection", "message", "Connected !!!!");
    }
}


void loop()
{
    //tạo một task cứ sau "interval" giây thì chạy lệnh:
    if (Serial.available()) {
        byte idx = 0;
        while (Serial.available()) {
          char ch = Serial.read(); // đọc ký hiệu
          if (ch != '\n' && ch != '\r')
            buffer[idx++] = ch;
          delay(5);
        }
        buffer[idx] = '\0'; //Ký tự '\0' là ký tự đóng gói chuỗi, xem thêm ở trong tài liệu tham khảo
        Serial.print("Buffer: ");
        Serial.println(buffer);
        //gửi sự kiện "atime" là một JSON chứa tham số message có nội dung là Time please?
        client.send("transmit", "message", buffer);
    }
 
    //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()) {
        Serial.println(RID);
        Serial.println(Rfull);
    }
 
    //Kết nối lại!
    if (!client.connected()) {
      client.reconnect(host, port);
      //Gửi lệnh join với json {id: "transmitter"}
      client.send("join", "id", "transmitter");
    }
}

2. Code Receiver

Mình có bình luận trong code, bạn xem và sáng tạo nhé!

#include <ESP8266WiFi.h>
#include <SocketIOClient.h>
 
SocketIOClient client;
#define BUFFER_LENGTH 200

const char* ssid = "linksys";          //Tên mạng Wifi mà Socket server của bạn đang kết nối
const char* password = "khanhsuphu";  //Pass mạng wifi ahihi, anh em rãnh thì share pass cho mình với.
 
char host[] = "192.168.2.107";  //Đị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!

char buffer[BUFFER_LENGTH];
//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;
 
 
//Một số biến dùng cho việc tạo một task
unsigned long previousMillis = 0;
long interval = 2000;
 
void setup()
{
    //Bật baudrate ở mức 115200 để giao tiếp với máy tính qua Serial
    Serial.begin(115200);
    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!"));
        while(1);
    }
    //Gửi lệnh join với json {id: "receiver"}
    client.send("join", "id", "receiver");
 
    //Khi đã kết nối thành công
    if (client.connected()) {
        Serial.println("Da ket noi den Server");
        //Thì gửi sự kiện ("connection") đến Socket server ahihi.
        client.send("connection", "message", "Connected !!!!");
    }
}


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()) {
        Serial.println(RID);
        Serial.println(Rfull);
    }
 
    //Kết nối lại!
    if (!client.connected()) {
      client.reconnect(host, port);
      //Gửi lệnh join với json {id: "receiver"}
      client.send("join", "id", "receiver");
    }
}

IV. Kết

Chúc các bạn thành công và ngày càng sáng tạo với thư viện của mình và module ESP8266.

Youtube: 
Ví dụ
Những hình ảnh về dự án: 
Bài viết truyền cảm hứng: 
lên
6 thành viên đã đánh giá bài viết này hữu ích.
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ả

Hướng dẫn sử dụng if else trong Node-RED / iNut Node-RED IDE

Bài viết sẽ hướng dẫn bạn sử dụng hàm rẻ nhánh if else trong nền tảng node-red / iNut Node-RED IDE.

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

Giả lập Arduino trên mây miễn phí - Tự học Arduino mà không cần có đầy đủ thiết bị

Từ lâu, chúng ta đã được biết đến phần mềm Proteus với khả năng giả lập các thiết bị phần cứng chuyên nghiệp được dùng trong mọi lĩnh vực của ngành điện tử. Tuy nhiên, ai cũng biết rằng, chúng ta đang vi phạm quyền sở hữu trí tuệ một cách có hệ thống khi dùng bản proteus đã được uống thuốc. Trước tình hình đó, cùng với xu hướng ảo hóa và lên mây. Autodesk đã cho ra đời dịch vụ miễn phí 123d.circuits.io để chúng ta có thể giả lập được mạch Arduino cùng với rất nhiều module cảm biến và linh kiện điện tử. Bạn có sẵn sàng đổi mới và thử chưa?

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