Gộp 2 code Duck DDNS và check port trên ESP32

Gộp 2 code Duck DDNS và check port trên ESP32.

Việc dùng ESP32 làm  việc check port mà mỗi lần cần lấy ippublic thì khá mất công chưa kể nó còn thay đổi. Nên ta sẽ kết hợp với DuckDDns để có tên miền để sử dụng khi check port. Mặc dù biết là hơi tham trên phần cứng ít ỏi của ESP32 nhưng dẫu sao nó vẫn hoạt động tốt nếu chỉ sử dụng với nhu cầu cá nhân.
1Screenshot 2024 06 16 142606

Đoạn code thực thi chức năng sau:

Kết nối Wi-Fi: Thiết lập và duy trì kết nối Wi-Fi với mạng cục bộ, thử lại kết nối nếu cần thiết.

Điều khiển đèn LED: Quản lý việc bật/tắt đèn LED.

Cập nhật DDNS: Tự động cập nhật địa chỉ IP động lên DuckDNS để duy trì kết nối từ xa.

Kiểm tra cổng: Cho phép người dùng kiểm tra trạng thái của các cổng trên một địa chỉ IP hoặc tên miền.

Khởi động lại thiết bị: Thực hiện khởi động lại ESP32 định kỳ để giữ cho thiết bị hoạt động ổn định.

 

Mỗi chức năng trong đoạn code đảm bảo thiết bị ESP32 cập nhật DDNS để duy trì kết nối từ xa và cho phép người dùng kiểm tra trạng thái của các cổng trên một địa chỉ IP hoặc tên miền từ xa nếu mở port cho ESP32.

#include <WiFi.h>
#include <WebServer.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <RTClib.h>

const char* ssid = "mixxxxx";
const char* password = "1234567999";
const char* duckDNS_domain = "subdomain4";
const char* duckDNS_token = "xxx0bd9-ef52-4846-bd17-xxxxxxxx";
const int ledPin = 15; 
const int MAX_CONNECTION_ATTEMPTS = 3;
const int WIFI_RETRY_INTERVAL = 7000; 
const int DDNS_UPDATE_INTERVAL = 60000;
const unsigned long LIGHT_CYCLE_DURATION = 500;
const unsigned long LIGHT_ON_DURATION = 500; 
const unsigned long RESTART_INTERVAL = 43200000; 
bool isWifiConnected = false;
bool isAttemptingConnection = false;
int wifiConnectionAttempts = 0;
unsigned long lastCycleTime = 0;
unsigned long lastDDNSUpdateTime = 0;
unsigned long lastRestartTime = 0;

WebServer server(81);

const char* htmlPage = R"rawliteral(
<!DOCTYPE HTML>
<html>
<head>
  <title>ESP32 Port Checker</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    body {
      font-family: Arial, sans-serif;
      text-align: center;
      background-color: #f0f0f0;
      margin: 0;
      padding: 0;
    }
    h2 {
      color: #333;
    }
    form {
      background: #fff;
      padding: 20px;
      border-radius: 5px;
      box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      display: inline-block;
      margin-top: 50px;
    }
    label {
      display: block;
      margin-bottom: 8px;
      color: #555;
    }
    input[type="text"] {
      width: calc(100% - 22px);
      padding: 10px;
      margin-bottom: 20px;
      border: 1px solid #ddd;
      border-radius: 3px;
    }
    input[type="submit"] {
      padding: 10px 20px;
      border: none;
      background-color: #007bff;
      color: white;
      border-radius: 3px;
      cursor: pointer;
    }
    input[type="submit"]:hover {
      background-color: #0056b3;
    }
  </style>
  <script>
    async function fetchPublicIP() {
      const response = await fetch('https://api.ipify.org?format=json');
      const data = await response.json();
      document.getElementById('ip').value = data.ip;
    }
    window.onload = fetchPublicIP;
  </script>
</head>
<body>
  <h2>ESP32 Port Checker</h2>
  <form action="/check" method="get">
    <label for="ip">IP/Domain:</label>
    <input type="text" id="ip" name="ip"><br>
    <label for="port">Ports (separated by space):</label>
    <input type="text" id="port" name="port"><br>
    <input type="submit" value="Check">
  </form>
  %s
</body>
</html>
)rawliteral";

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT); 
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  digitalWrite(ledPin, HIGH);

  server.on("/", HTTP_GET, []() {
    server.send(200, "text/html", htmlPage);
  });

  server.on("/check", HTTP_GET, []() {
    if (server.hasArg("ip") && server.hasArg("port")) {
      String ip = server.arg("ip");
      String ports = server.arg("port");
      String result = "<p>Checking " + ip + " - ";

      int start = 0;
      int end = ports.indexOf(' ');
      while (end != -1) {
        String portStr = ports.substring(start, end);
        int port = portStr.toInt();
        bool isOpen = checkPort(ip, port);
        result += "Port " + portStr + " is " + (isOpen ? "Open" : "Closed") + ". ";
        start = end + 1;
        end = ports.indexOf(' ', start);
      }
      String portStr = ports.substring(start);
      int port = portStr.toInt();
      bool isOpen = checkPort(ip, port);
      result += "Port " + portStr + " is " + (isOpen ? "Open" : "Closed") + ". ";

      result += "</p>";
      server.send(200, "text/html", String(htmlPage) + result);
    } else {
      server.send(200, "text/html", String(htmlPage) + "<p>Invalid input</p>");
    }
  });

  server.begin();

  lastRestartTime = millis(); 
  updateDDNS();
}

void loop() {
  server.handleClient();
  checkWifiConnection();
  controlLED();
    if (millis() - lastRestartTime >= RESTART_INTERVAL) {
    restartESP32();
  }
    if (isWifiConnected && millis() - lastDDNSUpdateTime >= DDNS_UPDATE_INTERVAL) {
    updateDDNS();
    lastDDNSUpdateTime = millis();
  }
}


bool checkPort(String host, uint16_t port) {
  WiFiClient client;
  bool connected = client.connect(host.c_str(), port);
  client.stop(); 
  return connected;
}

void controlLED() {
  unsigned long currentMillis = millis();


  if (isWifiConnected && (currentMillis - lastCycleTime >= LIGHT_CYCLE_DURATION)) {
    lastCycleTime = currentMillis;
    // Nếu có kết nối WiFi, sáng LED và bỏ qua điều kiện chớp tắt ban đầu
    digitalWrite(ledPin, HIGH);
    return;
  }

  if (currentMillis - lastCycleTime >= LIGHT_ON_DURATION) {
    digitalWrite(ledPin, LOW);
  } else {
    digitalWrite(ledPin, HIGH); 
  }
}

void checkWifiConnection() {
  if (WiFi.status() != WL_CONNECTED) {

    isWifiConnected = false;
    if (!isAttemptingConnection) {
      isAttemptingConnection = true;
      wifiConnectionAttempts = 0;
    }

    if (isAttemptingConnection && wifiConnectionAttempts < MAX_CONNECTION_ATTEMPTS) {
      if (millis() - lastCycleTime >= WIFI_RETRY_INTERVAL) {
        wifiConnectionAttempts++;
        Serial.print("Attempting to reconnect to WiFi (Attempt ");
        Serial.print(wifiConnectionAttempts);
        Serial.println(")");
        WiFi.begin(ssid, password);
        lastCycleTime = millis();
      }
    } else {
      isAttemptingConnection = false;
      Serial.println("WiFi connection failed after multiple attempts.");
      digitalWrite(ledPin, LOW); 
    }
  } else {
    isWifiConnected = true;
    isAttemptingConnection = false;
    wifiConnectionAttempts = 0; 
    updateDDNS();
  }
}

void updateDDNS() {
  String duckDNS_update_url = "http://www.duckdns.org/update?domains=" + String(duckDNS_domain) + "&token=" + String(duckDNS_token) + "&ip=";
  HTTPClient http;
  http.begin("https://ipv4.icanhazip.com");
  int httpCode = http.GET();
  String currentIP = "";
  if (httpCode == HTTP_CODE_OK) {
    currentIP = http.getString();
    Serial.println("Current IP: " + currentIP);
    HTTPClient httpUpdate;
    httpUpdate.begin(duckDNS_update_url + currentIP);
    int httpUpdateCode = httpUpdate.GET();
    if (httpUpdateCode == HTTP_CODE_OK) {
      Serial.println("DDNS update success");
    }
    httpUpdate.end();
  } else {
    Serial.println("Failed to connect to IP service");
  }
  http.end();
}

void restartESP32() {
  Serial.println("Restarting ESP32...");
  ESP.restart();
}

 

Trong code  tôi có hơi tham khi cho kiểm tra nhiều cổng 1 lúc sẽ ảnh hưởng tới tốc độ xử lý và trả về kết quả của ESP32 là chậm một chút. Nhưng nếu bạn chỉ kiểm tra 1 cổng thì vẫn khá nhanh và vì giới hạn phần cứng nên khuyên bạn chỉ nên sài với nhu cầu cá nhân.

Hãy thay đổi 1 số thông tin sau trong code cho phù hợp với bạn.

  const char* ssid = “xxxx”; //tên wifi của bạn
  const char* password = “passwd”; //mật khẩu wifi
  WebServer server(81); // đang port 81 đổi thành port khác nếu cần
  const char* duckDNS_domain = “subdomain4”;
  const char* duckDNS_token = “xxx0bd9-ef52-4846-bd17-xxxxxxxx”;

 

Dưới đây là phần giải thích sơ về đoạn mã Arduino kết hợp các chức năng từ hai đoạn mã trước:

Thư viện

– `#include <WiFi.h>`: Thư viện cho kết nối WiFi.
– `#include <WebServer.h>`: Thư viện cho việc thiết lập máy chủ web.
– `#include <HTTPClient.h>`: Thư viện cho việc thực hiện các yêu cầu HTTP.
– `#include <Wire.h>`: Thư viện cho giao tiếp I2C.
– `#include <RTClib.h>`: Thư viện cho việc điều khiển RTC (Real-Time Clock).

Biến toàn cục

– `const char* ssid = “mixxxxx”;`: Tên SSID cho mạng WiFi.
– `const char* password = “1234567999”;`: Mật khẩu cho mạng WiFi.
– `const char* duckDNS_domain = “subdomain4”;`: Tên miền DuckDNS.
– `const char* duckDNS_token = “xxx0bd9-ef52-4846-bd17-xxxxxxxx”;`: Token DuckDNS.
– `const int ledPin = 15;`: Pin GPIO15 được sử dụng để điều khiển đèn LED.
– `const int MAX_CONNECTION_ATTEMPTS = 3;`: Số lần thử kết nối lại WiFi tối đa.
– `const int WIFI_RETRY_INTERVAL = 7000;`: Thời gian chờ giữa các lần thử kết nối lại WiFi (7 giây).
– `const int DDNS_UPDATE_INTERVAL = 60000;`: Thời gian giữa các lần cập nhật DDNS (1 phút).
– `const unsigned long LIGHT_CYCLE_DURATION = 500;`: Thời gian một chu kỳ sáng tắt là 1 giây.
– `const unsigned long LIGHT_ON_DURATION = 500;`: Thời gian sáng là 0.5 giây.
– `const unsigned long RESTART_INTERVAL = 43200000;`: Thời gian giữa các lần khởi động lại (12 giờ).

Các biến để kiểm soát trạng thái kết nối WiFi và đèn LED:

– `bool isWifiConnected = false;`: Trạng thái kết nối WiFi.
– `bool isAttemptingConnection = false;`: Đang thử kết nối lại WiFi hay không.
– `int wifiConnectionAttempts = 0;`: Số lần thử kết nối lại WiFi.
– `unsigned long lastCycleTime = 0;`: Thời gian của chu kỳ đèn LED.
– `unsigned long lastDDNSUpdateTime = 0;`: Thời gian lần cập nhật DDNS gần nhất.
– `unsigned long lastRestartTime = 0;`: Thời gian lần khởi động lại gần nhất.

WebServer

– `WebServer server(81);`: Tạo một máy chủ web chạy trên cổng 81.
– `const char* htmlPage = R”rawliteral(…)rawliteral”;`: Nội dung HTML của trang web, được định nghĩa dưới dạng raw string literal.

Nội dung HTML

Trang web bao gồm:

– Tiêu đề, form để nhập địa chỉ IP hoặc tên miền và cổng cần kiểm tra, cùng với một số định dạng CSS để trang trí giao diện.
– Một đoạn JavaScript để tự động lấy địa chỉ IP công cộng khi trang web tải xong.

Hàm `setup()`

– `Serial.begin(115200);`: Khởi động giao tiếp serial với tốc độ 115200 baud.
– `pinMode(ledPin, OUTPUT);`: Thiết lập pin GPIO15 làm đầu ra để điều khiển đèn LED.
– `WiFi.begin(ssid, password);`: Bắt đầu kết nối đến mạng WiFi.
– `while (WiFi.status() != WL_CONNECTED)`: Chờ đến khi kết nối thành công tới WiFi.
– `Serial.println(“Connected to WiFi”);`: In ra thông báo kết nối thành công.
– `digitalWrite(ledPin, HIGH);`: Bật đèn LED để báo hiệu trạng thái kết nối WiFi thành công.

Thiết lập máy chủ web:

– `server.on(“/”, HTTP_GET, []() { … });`: Xử lý yêu cầu GET tới trang chủ `/`, gửi nội dung HTML.
– `server.on(“/check”, HTTP_GET, []() { … });`: Xử lý yêu cầu GET tới đường dẫn `/check`, kiểm tra xem các tham số `ip` và `port` có được gửi hay không.

Hàm `loop()`

– `server.handleClient();`: Xử lý các yêu cầu từ client.
– `checkWifiConnection();`: Kiểm tra kết nối WiFi.
– `controlLED();`: Điều khiển đèn LED.
– `if (millis() – lastRestartTime >= RESTART_INTERVAL)`: Kiểm tra thời gian từ lần khởi động lại cuối cùng, nếu vượt quá RESTART_INTERVAL thì khởi động lại ESP32.
– `if (isWifiConnected && millis() – lastDDNSUpdateTime >= DDNS_UPDATE_INTERVAL)`: Nếu đã kết nối WiFi và đến thời gian cập nhật DDNS, thì cập nhật DDNS.

Hàm `checkPort()`

– `bool checkPort(String host, uint16_t port)`: Hàm kiểm tra xem cổng có mở hay không.
– `WiFiClient client;`: Tạo đối tượng WiFiClient để kết nối TCP.
– `bool connected = client.connect(host.c_str(), port);`: Trả về true nếu kết nối tới địa chỉ IP/host và cổng thành công, ngược lại trả về false.
– `client.stop();`: Dừng kết nối.
– `return connected;`: Trả về kết quả kiểm tra cổng.

Hàm `controlLED()`

– Điều khiển đèn LED dựa trên trạng thái kết nối WiFi và thời gian chu kỳ.

Hàm `checkWifiConnection()`

– Kiểm tra trạng thái kết nối WiFi.
– Nếu mất kết nối, thử kết nối lại tối đa MAX_CONNECTION_ATTEMPTS lần.
– Nếu kết nối thành công, cập nhật trạng thái và thực hiện cập nhật DDNS.

Hàm `updateDDNS()`

– Lấy địa chỉ IP công cộng và cập nhật tên miền DuckDNS với địa chỉ IP này.

Hàm `restartESP32()`

– `Serial.println(“Restarting ESP32…”);`: In ra thông báo khởi động lại.
– `ESP.restart();`: Khởi động lại ESP32.

Tổng kết

Đoạn mã này kết hợp các chức năng của hai đoạn mã trước, cung cấp một máy chủ web trên ESP32 để kiểm tra các cổng mở của một địa chỉ IP hoặc tên miền, và đồng thời quản lý kết nối WiFi và cập nhật DDNS. Nó cũng có chức năng điều khiển đèn LED để báo hiệu trạng thái kết nối và khởi động lại ESP32 định kỳ.

 

Leave a Comment

👈 Vuốt để chuyển bài 👉

KIỂM TRA PORT

IPv6 của bạn: Đang lấy...