การใช้งาน IOXESP32 Ethernet shield กับ ArduinoIDE

เขียนโปรแกรมสั่งงานโมดูล IOXESP32 Ethernet shield ด้วย ArduinoIDE

การเขียนโปรแกรมสั่งงาน IOXESP32 Ethernet shield ให้ใช้ไลบรารี่ Ethernet ในการเริ่มเชื่อมต่ออินเตอร์เน็ตผ่านสายแลน จากนั้นหากต้องการเชื่อมต่อ HTTP ให้ใช้ไลบรารี่ ArduinoHttpClient, หากต้องการเชื่อมต่อ MQTT ให้ใช้ไลบรารี่ PubSubClient

เริ่มต้นทดสอบเชื่อมต่ออินเตอร์เน็ตผ่ายสายแลน

ให้ติดตั้งไลบรารี่ Ethernet ตามขั้นตอนดังนี้

ที่โปรแกรม Arduino IDE ให้เปิด Libraby Manager ขึ้นมา (1) ค้นหา Ethernet (2) แล้วกดปุ่ม INSTALL (3)

รอจนกว่าจะติดตั้งเสร็จ เมื่อติดตั้งเสร็จจะมีข้อความ installed ขึ้น (ดังรูป)

คัดลอกโค้ดโปรแกรมต่อไปนี้ไปใส่ในโปรแกรม Arduino IDE

กรณีเลือกขา CS เป็นขาอื่นที่ไม่ใช้ขา 15 ต้องแก้เลข 15 ในคำสั่ง Ethernet.init(); ให้เป็นเลขขา CS ที่เลือกไว้ด้วย

#include <SPI.h>
#include <Ethernet.h>

void setup() {
  Ethernet.init(15);  // IOXESP32 Ethernet shield

  Serial.begin(115200);
}

void loop() {
  auto link = Ethernet.linkStatus();
  Serial.print("Link status: ");
  switch (link) {
    case Unknown:
      Serial.println("Unknown");
      break;
    case LinkON:
      Serial.println("ON");
      break;
    case LinkOFF:
      Serial.println("OFF");
      break;
  }
  delay(1000);
}

เลือกบอร์ด เลือกพอร์ต แล้วอัพโหลดโปรแกรมลงบอร์ด

เมื่ออัพโหลดโปรแกรมลงบอร์ดแล้ว ให้เปิด Serial Monitor ขึ้นมา ปรับเป็น 115200 แล้วกดปุ่ม Reset บนบอร์ด IOXESP32 1 ครั้ง หากสายแลนมีการเชื่อมต่ออยู่ และขอ IP จาก DHCP ได้ จะมีข้อความ ON ขึ้น (ดังรูป)

ทดสอบถอดสายแลนออก ผลที่ได้จะขึ้น OFF (ดังรูป)

การดึงค่าเวลาจากอินเตอร์เน็ต (NTP)

ใช้โค้ดโปรแกรมต่อไปนี้ในการทดสอบ

#include <SPI.h>
#include <Ethernet.h>
#include <EthernetUdp.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

unsigned int localPort = 8888;       // local port to listen for UDP packets

const char timeServer[] = "time.nist.gov"; // time.nist.gov NTP server

const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message

byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming and outgoing packets

// A UDP instance to let us send and receive packets over UDP
EthernetUDP Udp;

void setup() {
  Ethernet.init(15);  // IOXESP32 Ethernet shield

  // Open serial communications and wait for port to open:
  Serial.begin(115200);

  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    } else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
    // no point in carrying on, so do nothing forevermore:
    while (true) {
      delay(1);
    }
  }
  Udp.begin(localPort);
}

void loop() {
  sendNTPpacket(timeServer); // send an NTP packet to a time server

  // wait to see if a reply is available
  delay(1000);
  if (Udp.parsePacket()) {
    // We've received a packet, read the data from it
    Udp.read(packetBuffer, NTP_PACKET_SIZE); // read the packet into the buffer

    // the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. First, extract the two words:

    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
    // combine the four bytes (two words) into a long integer
    // this is NTP time (seconds since Jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    Serial.print("Seconds since Jan 1 1900 = ");
    Serial.println(secsSince1900);

    // now convert NTP time into everyday time:
    Serial.print("Unix time = ");
    // Unix time starts on Jan 1 1970. In seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;
    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;
    // print Unix time:
    Serial.println(epoch);


    // print the hour, minute and second:
    Serial.print("The UTC time is ");       // UTC is the time at Greenwich Meridian (GMT)
    Serial.print((epoch  % 86400L) / 3600); // print the hour (86400 equals secs per day)
    Serial.print(':');
    if (((epoch % 3600) / 60) < 10) {
      // In the first 10 minutes of each hour, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.print((epoch  % 3600) / 60); // print the minute (3600 equals secs per minute)
    Serial.print(':');
    if ((epoch % 60) < 10) {
      // In the first 10 seconds of each minute, we'll want a leading '0'
      Serial.print('0');
    }
    Serial.println(epoch % 60); // print the second
  }
  // wait ten seconds before asking for the time again
  delay(10000);
  Ethernet.maintain();
}

// send an NTP request to the time server at the given address
void sendNTPpacket(const char * address) {
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;     // Stratum, or type of clock
  packetBuffer[2] = 6;     // Polling Interval
  packetBuffer[3] = 0xEC;  // Peer Clock Precision
  // 8 bytes of zero for Root Delay & Root Dispersion
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;

  // all NTP fields have been given values, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); // NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

อัพโหลดโปรแกรมลงบอร์ด แล้วเปิด Serial Monitor ขึ้นมา ผลที่ได้ค่าเวลา UTC จะขึ้นมาแล้ว

ทดสอบเปิดเว็บไซต์เทียบเวลา https://www.epochconverter.com/ จะพบว่าค่าเวลา Unix time ตรงกัน

หากต้องการให้แสดงเวลาประเทศไทย ต้องนำตัวแปร epoch มา +7 ชั่วโมง (7 * 60 * 60) จึงจะแสดงเวลาประเทศไทย

การดึงข้อมูลผ่าน HTTP

HTTP คืออะไร ? (ตอบโดย ChatGPT)

HTTP (Hypertext Transfer Protocol) คือโปรโตคอลที่ใช้สำหรับการสื่อสารระหว่างเว็บเบราว์เซอร์และเซิร์ฟเวอร์ในระบบเวิลด์ไวด์เว็บ (WWW) โดยทำหน้าที่กำหนดรูปแบบและวิธีการส่งข้อมูลระหว่างลูกค้า (Client) และเซิร์ฟเวอร์ (Server)

ตัวอย่างบทความนี้จะเขียนโค้ดดึงข้อมูลหน้าเว็บ Google มาแสดงใน Serial Monitor

ติดตั้งไลบรารี่ ArduinoHttpClient ตามขั้นตอน จากนั้นใช้โค้ดโปรแกรมต่อไปนี้ในการทดสอบ

#include <ArduinoHttpClient.h>
#include <SPI.h>
#include <Ethernet.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

char serverAddress[] = "www.google.com";  // server address
int port = 80;

EthernetClient ethClient;
HttpClient client = HttpClient(ethClient, serverAddress, port);

void setup() {
  Ethernet.init(15);  // IOXESP32 Ethernet shield

  Serial.begin(115200);

  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    } else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
    // no point in carrying on, so do nothing forevermore:
    while (true) {
      delay(1);
    }
  }
}

void loop() {
  Serial.println("making GET request");
  client.get("/");

  // read the status code and body of the response
  int statusCode = client.responseStatusCode();
  String response = client.responseBody();

  Serial.print("Status code: ");
  Serial.println(statusCode);
  Serial.print("Response: ");
  Serial.println(response);
  Serial.println("Wait five seconds");
  delay(5000);
}

ผลที่ได้ ข้อมูลจาก google.com แสดงขึ้นมาบน Serial Monitor แล้ว

การดึงข้อมูลผ่าน HTTPS

ปัจจุบันเว็บไซต์ส่วนใหญ่ใช้ HTTPS และยกเลิกใช้ HTTP แล้ว เนื่องจาก HTTPS มีความปลอดภัยในการรับ-ส่งข้อมูลมากกว่า มีการเข้ารหัสข้อมูลตั้งแต่ต้นทางไปปลายทาง อุปกรณ์ที่คั่นระหว่างกลาง เช่น เร้าเตอร์ หรือ ISP ไม่สามารถดักจับข้อมูลแล้วรู้ได้ว่าข้อมูลที่รับ-ส่งกันอยู่คืออะไร

API คืออะไร? (ตอบโดย ChatGPT)

API (Application Programming Interface) คือชุดของกฎ ระเบียบ และเครื่องมือที่ใช้ในการสร้างและบูรณาการซอฟต์แวร์แอปพลิเคชัน โดย API ทำหน้าที่เป็นตัวกลางในการสื่อสารระหว่างซอฟต์แวร์ต่าง ๆ ทำให้โปรแกรมหนึ่งสามารถขอข้อมูลหรือบริการจากโปรแกรมอื่นได้ โดยไม่ต้องรู้รายละเอียดของการทำงานภายในของโปรแกรมเหล่านั้น

API ที่เว็บไซต์ต่าง ๆ เปิดให้ดึงข้อมูล ล้วนใช้ HTTPS กันเป็นส่วนใหญ่แล้ว หากต้องการใช้ IOXESP32 + Ethernet shield ดึงข้อมูลผ่าน HTTPS จำเป็นต้องติดตั้งไลบรารี่จัดการการเข้ารหัส SSL/TLS เพิ่มเติม โดยบทความนี้เลือกใช้ไลบรารี่ ESP_SSLClient เป็นตัวกลางระหว่าง Ethernet <--> ArduinoHttpClient ได้เส้นทางใหม่เป็น Ethernet <--> ESP_SSLClient <--> ArduinoHttpClient

ติดตั้งไลบรารี่ ESP_SSLClient จากนั้นใช้โค้ดโปรแกรมต่อไปนี้ในการทดสอบ (ดึงราคาทองคำผ่าน API : https://api.chnwt.dev/thai-gold-api/latest)

#include <ArduinoHttpClient.h>
#include <SPI.h>
#include <Ethernet.h>
#include <ESP_SSLClient.h>

// Enter a MAC address for your controller below.
// Newer Ethernet shields have a MAC address printed on a sticker on the shield
byte mac[] = {
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};

char serverAddress[] = "api.chnwt.dev";  // server address
int port = 443;

EthernetClient basic_client;
ESP_SSLClient ssl_client;
HttpClient client = HttpClient(ssl_client, serverAddress, port);

void setup() {
  Ethernet.init(15);  // IOXESP32 Ethernet shield

  Serial.begin(115200);

  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    } else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
    // no point in carrying on, so do nothing forevermore:
    while (true) {
      delay(1);
    }
  }

  // ignore server ssl certificate verification
  ssl_client.setInsecure();

  // Set the receive and transmit buffers size in bytes for memory allocation (512 to 16384).
  ssl_client.setBufferSizes(1024 /* rx */, 512 /* tx */);

  // Assign the basic client
  // Due to the basic_client pointer is assigned, to avoid dangling pointer, basic_client should be existed
  // as long as it was used by ssl_client for transportation.
  ssl_client.setClient(&basic_client);
}

void loop() {
  Serial.println("making GET request");
  client.get("/thai-gold-api/latest");

  // read the status code and body of the response
  int statusCode = client.responseStatusCode();
  String response = client.responseBody();

  Serial.print("Status code: ");
  Serial.println(statusCode);
  Serial.print("Response: ");
  Serial.println(response);
  Serial.println("Wait five seconds");
  delay(5000);
}

ผลที่ได้ ราคาทองคำวันนี้ที่ดึงมาจาก API จะแสดงขึ้นมาบน Serial Monitor แล้ว

การรับ-ส่งข้อมูลผ่าน MQTT

MQTT คืออะไร? (ตอบโดย ChatGPT)

MQTT (Message Queuing Telemetry Transport) คือโปรโตคอลการสื่อสารที่มีน้ำหนักเบาและมีประสิทธิภาพสูง ออกแบบมาเพื่อการส่งข้อมูลระหว่างอุปกรณ์ที่มีการเชื่อมต่อผ่านเครือข่ายที่มีความเสถียรต่ำหรือแบนด์วิดท์จำกัด MQTT ถูกพัฒนาโดย IBM และปัจจุบันเป็นมาตรฐานเปิดที่ได้รับการดูแลโดย OASIS (Organization for the Advancement of Structured Information Standards)

MQTT แบ่งเป็น 2 ฝั่ง คือ Device และ Broker (Server) การใช้งาน MQTT จำเป็นจะต้องมีทั้ง 2 ฝั่งนี้ โดยฝั่ง Device ในบทความนี้จะใช้ IOXESP32 และคอมพิวเตอร์ในการแลกเปลี่ยนข้อมูล ส่วน Broker (Server) เลือกใช้ Public MQTT Broker จาก EMQX

การเขียนโปรแกรมเชื่อมต่อ MQTT ต้องทำผ่านไลบรารี่ PubSubClient

การเชื่อมต่อ MQTT ที่พอร์ต 1883 (TCP Port) จะเป็นการเชื่อมต่อด้วยโปรโตคอล TCP อย่างเดียว ไม่มีการเข้ารหัสข้อมูลระหว่างการรับ-ส่ง สามารถให้ไลบรารี่ PubSubClient เชื่อมต่อกับ Ethernet ได้โดยตรง

แต่หากเลือกใช้พอร์ต 8883 (SSL/TLS Port) จำเป็นต้องมีไลบรารี่ ESP_SSLClient มาคั่น (เช่นเดียวกับ HTTPS) เพื่อให้ไลบรารี่ ESP_SSLClient ช่วยเข้ารหัสก่อนส่งข้อมูล และถอดรหัสเมื่อได้รับข้อมูลมา

ใช้โค้ดต่อไปนี้ในการทดสอบ

#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>

// Update these with values suitable for your network.
byte mac[]    = {  0xDE, 0xED, 0xBA, 0xFE, 0xFE, 0xED };
const char * server = "broker.emqx.io";

void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");
  for (int i=0;i<length;i++) {
    Serial.print((char)payload[i]);
  }
  Serial.println();
}

EthernetClient ethClient;
PubSubClient client(ethClient);

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect("MyIOXESP32")) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("/IOXESP32-Ethernet-Test","hello world");
      // ... and resubscribe
      client.subscribe("/IOXESP32-Ethernet-Test");
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void setup()
{
  Ethernet.init(15);  // IOXESP32 Ethernet shield

  Serial.begin(115200);

  client.setServer(server, 1883);
  client.setCallback(callback);

  // start Ethernet and UDP
  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    // Check for Ethernet hardware present
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println("Ethernet shield was not found.  Sorry, can't run without hardware. :(");
    } else if (Ethernet.linkStatus() == LinkOFF) {
      Serial.println("Ethernet cable is not connected.");
    }
    // no point in carrying on, so do nothing forevermore:
    while (true) {
      delay(1);
    }
  }
}

void loop()
{
  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

เปิด Serial Monitor ขึ้นมา หากเชื่อมต่อกับ Broker ได้ จะมีข้อความ connected ขึ้น (ดังรูป)

เปิดโปรแกรม MQTTX ขึ้นมา (ดาวน์โหลดที่ https://mqttx.app/) เริ่มสร้างการเชื่อมต่อ Broker โดยกดที่เครื่องหมายบวก (1) แล้วกด New Connection (2)

กรอกชื่อ (1) แล้วกรอก Host เป็น broker.emqx.io (2) แล้วกด Connect (3)

เพิ่ม Subscription เพื่อให้รับข้อมูลจากบอร์ด IOXESP32 ได้ โดยกด New Subscription

กรอกช่อง Topic เป็น /IOXESP32-Ethernet-Test แล้วกด Confirm

กดปุ่ม Reset บนบอร์ด IOXESP32 1 ครั้ง หากเชื่อมต่อ MQTT ได้แล้ว จะมีข้อความ hello world แสดงขึ้นมาในโปรแกรม MQTTX

ทดสอบส่งข้อมูลไปที่บอร์ด IOXESP32 โดยใส่ Topic เป็น /IOXESP32-Ethernet-Test กรอกข้อความ แล้วกดปุ่มส่ง

ผลที่ได้ ข้อความที่ส่งไปจะแสดงทั้งในหน้าต่างของ MQTTX และที่ Serial Monitor

Last updated