[TH] ESP8266/ESP32 WiFi

บอร์ด ESP8266 และ ESP32 เป็นอุปกรณ์ที่มีระบบการเชื่อมต่อสัญญาณ WiFi ในตัว โดยสามารถทำงานได้ทั้งโหมดการให้ตนเองเป็น AP (Access Point) และโหมดลูกข่ายที่เชื่อมต่อเข้ากับเครือข่าย WiFi ที่มีอยู่แล้วหรือ STA โดยผู้พัฒนาสามารถตั้งชื่อของอุปกรณ์ (ESSID) หรือใช้ชื่อตามค่าที่ตั้งจากระบบเป็น MicroPython-xxxx ซึ่ง x แทนค่า MAC Address ของอุปกรณ์ โดยรหัสผ่านเป็น micropythoN (ผู้พัฒนาสามารถกำหนดใหม่ได้) พร้อมรหัสหมายเลขไอพี (IP Address) เป็น 192.168.4.1

ภาพที่ 1 บอร์ดทดลอง ESP8266+Uno ของทีม JarutEx

ในบทความนี้ต่อยอดจากบทความที่แล้วที่ได้สื่อสารควบคุมการทำงานของบอร์ด Arduino Uno Maker ผ่านทางการสื่อสารบนบัส I2C โดยบทความนี้จะเป็นการสั่งงาน และดูสถานะของพอร์ต D2,D3,D4,D5,D6,D7,D8 และ D9 ของบอร์ด Arduino Uno Maker ผ่านทางหน้าเว็บของ ESP8266 หรือ ESP32

Network basics

โมดูลเครือข่ายของ ESP8266/ESP32 ต้องนำเข้าไลบรารี network โดยคำสั่งสำหรับตรวจสอบสถานะของ interface ในโหมด AP และ STA เขียนด้วยภาษาไพธอนได้ดัง code3-1 และ code3-2 ซึ่งผลลัพธ์ที่ได้เป็น False เมื่อไม่พร้อมเชื่อมต่อหรือยังไม่ได้เชื่อมต่อกับระบบเครือข่าย WiFi และเป็น True เมื่อพร้อมทำงานกับเครือข่าย WiFi

#code3-1
import network as nw
ifSTA = nw.WLAN(nw.STA_IF)
print(ifSTA.active())
#code3-2
import network as nw
ifAP = nw.WLAN(nw.AP_IF)
print(ifAP.active())

การอ่านค่าการตั้งค่าของ interface สามารถใช้คำสั่งตามรูปแบบต่อไปนี้ เพื่ออ่านค่าหมายเลขไอพี (IP Address) ซับเน็ตมาสค์ (subnetmask) เกตเวย์ (Gateway) และดีเอ็นเอส (DNS)

#code3-3
import network as nw
ifAP = nw.WLAN(nw.AP_IF)
if (ifAP.active() == True):
    print(ifAP.ifconfig())

ตัวอย่างภาพผลลัพธ์จาก code3-3 เป็นดังนี้

ภาพที่ 2 ตัวอย่างผลลัพธฺ์จาก code3-3

AP Mode

โหมด AP เป็นการกำหนดให้ ESP8266 เป็นอุปกรณ์ให้บริการเว็บ และผู้ใช้ทำการเชื่อมต่อ WiFi มายังอุปกรณ์นี้เพื่ออ่านข้อมูลจากเว็บไซต์ซึ่งมีค่า IP Address เป็น 192.168.4.1 โดยชื่อของ ESSID และรหัสผ่านผู้พัฒนาสามารถกำหนดขึ้นเองได้

ขั้นตอนการเขียนโปรแกรมในโหมด AP เป็นดังนี้
1. สร้าง Interface แบบ AP
2. ตรวจสอบสถานะของ Interface ถ้าสถานะเป็น False ให้ทำการ active() ใหม่ และกำหนดชื่อ ESSID และรหัสผ่านของ ESP8266/ESP32
3. สร้างฟังก์ชันสำหรับส่งข้อมูล HTML เมื่อเครื่องลูกข่ายทำการเชื่อมต่อ
4. อ่านค่าการตั้งค่าและแสดงผลออกมา (กรณีที่ไม่ต้องการแสดงหมายเลข IP Address ของอุปกรณ์ AP สามารถข้ามไปได้)
5. เปิด socket และเลือกหมายเลขพอร์ตเป็น 80 พร้อมทั้งเริ่มรอการเชื่อมต่อ
6. วนรอบเพื่อรอการเชื่อมต่อ เมื่อเกิดการเชื่อมต่อ (บรรทัดหลัง accept() จะเริ่มทำงาน) ผู้พัฒนาโปรแกรมสามารถอ่านค่าหมายเลขอุปกรณ์เชื่อมต่อ และค่าที่ได้รับจากการเชื่อมต่อ หลังจากนั้นส่งข้อมูลกลับไปให้บราวเซอร์ของอุปกรณ์ที่เชื่อมต่อเข้ามา หลังจากนั้นต้องสั่งปิดการเชื่อมต่อ

ตัวอย่าง code3-4 เป็นตัวอย่างการแสดงข้อความ Hi JarutEx พร้อมค่าการนับการเปิดหน้าเว็บ

#code3-4
import socket
import network
import esp
#---(1)
ap = network.WLAN(network.AP_IF)
#---(2)
ssid = 'JarutEx-AP'
password = '123456789'
ap.active(True)
ap.config(essid=ssid, password=password)
counter = 0
while ap.active() == False:
  pass
print('Connection successful')
#---(3)
def web_page():
    global counter
    counter = counter + 1
    html = """<html><head><meta name="viewport"
      content="width=device-width, initial-scale=1"></head>
      <body><h1>Hi JarutEx. No."""
    html += str(counter)
    html += "</h1></body></html>"
    return html
#---(4)
print(ap.ifconfig())
#---(5)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
#---(6)
while True:
    conn, addr = s.accept()
    print('conn {} from {}'.format(conn, addr))
    request = conn.recv(1024)
    print('request = {}'.format(request))
    response = web_page()
    conn.send('Content-Type: text/html\n')
    conn.send('Connection: close\n\n')
    conn.send(response)
    conn.close()

STA Mode

กรณีการเชื่อมต่อ ESP8266/ESP32 เข้ากับเครือข่าย WiFi ที่มีอยู่แล้วจะมีขั้นตอนการดำเนินงานดังนี้
1. สร้างตัวแปรของ Interface ที่ทำงานในโหมด STA_IF
2. ตรวจสอบสถานะการพร้อมทำงานของ interface
3. ถ้าพร้อมทำงานให้ข้ามไป แต่ถ้าไม่พร้อมทำงานให้สั่งเริ่มทำงาน
4. ทำการเชื่อมต่อกับเครือข่าย WiFi ถ้าเชื่อมต่อไม่สำเร็จในวนรอบทำการเชื่อมต่อจนกว่าจะสำเร็จ
5. อ่านค่าตั้งค่าที่ระบบเครือข่าย WiFi กำหนดให้กับบอร์ด ESP8266/ESP32
6. ทำงานที่ต้องการ
7. ปิดการทำงานเมื่อเลิกใช้

ตัวอย่าง code3-6 เป็นตัวอย่างการเชื่อมต่อกับเครือข่าย

#code3-6
import network as nw
myESSID = "ชื่อAP"
myPassword = "รหัสผ่าน"
ifSTA = nw.WLAN(nw.STA_IF)
if (ifSTA.active() == False):
    ifSTA.active(True)
    ifSTA.connect(myESSID, myPassword)
    while not ifSTA.isconnected():
        pass
    print("network configuration:\n{}".format(ifSTA.ifconfig()))
ifSTA.active(False)
ภาพที่ 3 ตัวอย่างผลลัพธ์จาก code3-5

หมายเหตุ
1. ขั้นตอนที่ 7 มีความสำคัญมาก ถ้าโปรแกรมที่พัฒนาขึ้นหยุดทำงานโดยไม่ได้ปิด interface ตัวบอร์ด ESP8266 จะยังคงพยายามเชื่อมต่อกับเครือข่าย WiFi ต่อไป ต้องทำการ reset เพื่อเริ่มต้นระบบใหม่
2. การเชื่อมต่อกับเครือข่ายที่ต้องทำการ Authen. จากหน้าเว็บก่อนใช้งานจะไม่สามารถใช้งานกับตัวอย่างนี้ได้ ผู้อ่านต้องใช้การแชร์เครือข่ายจากโทรศัพท์มือถือเพื่อทดลองการทำงานของโปรแกรม

ตัวอย่างโปรแกรมแสดงรายชื่อของ Access Point ทั้งหมดที่บอร์ด ESP8266 มองเห็นเป็นดัง code3-7

#code3-7
import network as nw
ifSTA = nw.WLAN(nw.STA_IF)
if (ifSTA.active() == False):
    ifSTA.active(True)
    listAPs = ifSTA.scan()
    for AP in listAPs:
        print("{}".format(AP[0]))
ifSTA.active(False)

ตัวอย่างการสั่งงาน Arduino Uno ผ่านเว็บ

ตัวอย่างโปรแกรมการสั่งงาน Arduino Uno Maker เพื่อสั่งเปิด/ปิดหลอด LED และแสดงสถานะของหลอด LED ที่เชื่อมต่อกับพอร์ต D2,D3,D4,D5,D6,D7,D8 และ D9 ผ่านเว็บในโหมด AP เขียนโค้ดได้ดัง code3-8 และโค้ดสำหรับ Arduino Uno Maker เป็นตาม code3-9

#code3-8
import esp
import time
import socket
import network
from machine import Pin, I2C

sclPin = Pin(5)
sdaPin = Pin(4)
unoAddr = 7
unoBuffer = bytearray(2)
unoStatus = 0
i2c = I2C(sda = sdaPin, scl = sclPin, freq=100000)

ap = network.WLAN(network.AP_IF)
ssid = 'JarutEx-AP'
password = '123456789'
ap.active(True)
ap.config(essid=ssid, password=password)
while ap.active() == False:
  pass
print('Connection successful')

def doOn():
    global unoBuffer
    global unoAddr
    unoBuffer[0] = 1
    unoBuffer[1] = 0
    i2c.writeto(unoAddr, unoBuffer)
    
def doOff():
    global unoBuffer
    global unoAddr
    unoBuffer[0] = 0
    unoBuffer[1] = 0
    i2c.writeto(unoAddr, unoBuffer)
    
def doReadStatus():
    global unoStatus
    global unoAddr
    unoStatus = i2c.readfrom(unoAddr,1)
    
def web_page():
    doReadStatus()
    html = """<html><head><meta name="viewport"
      content="width=device-width, initial-scale=1"></head>
      <body><h1>Status = """
    html += str(unoStatus)
    html += "</h1>"
    html += """<p><a href="/?led=on"><button>ON</button></a></p>
  <p><a href="/?led=off"><button>OFF</button></a></p>"""
    html += "</body></html>"
    return html

print(ap.ifconfig())
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('', 80))
s.listen(5)
while True:
    conn, addr = s.accept()
    print('conn {} from {}'.format(conn, addr))
    request = str(conn.recv(1024))
    print('request = {}'.format(request))
    ledOn = request.find("/?led=on")
    ledOff = request.find("/?led=off")
    if (ledOn==6):
        doOn()
    if (ledOff==6):
        doOff()
    response = web_page()
    conn.send('HTTP/1.1 200 OK\n')
    conn.send('Content-Type: text/html\n')
    conn.send('Connection: close\n\n')    
    conn.send(response)
    conn.close()
// code3-9 โค้ดของ Arduino Uno
#include <Wire.h>
int unoAddr = 7;
uint8_t ledStatus[8] = {1, 1, 1, 1, 1, 1, 1, 1};
void i2cReceive( int bytes ) {
  uint8_t dInput = Wire.read();
  uint8_t pattern = Wire.read();
  Serial.println(dInput);
  if (dInput == 0) {
    for (int ledPin = 2; ledPin <= 9; ledPin++) {
      digitalWrite(ledPin, 0);
      ledStatus[ledPin-2] = 0;
    }
  }
  else if (dInput == 1) {
    for (int ledPin = 2; ledPin <= 9; ledPin++) {
      digitalWrite(ledPin, 1);
      ledStatus[ledPin-1] = 1;
    }
  } else {
    return;
  }
}
void i2cRequest() {
  for (int ledPin = 2; ledPin <= 9; ledPin++) {
    pinMode(ledPin, INPUT_PULLUP);
  }
  uint8_t bitNo = 0;
  uint8_t unoBuffer = 0;
  for (int ledIdx = 0; ledIdx < 8; ledIdx++) {
    unoBuffer |= ledStatus[ledIdx] << bitNo;
    bitNo++;
  }
  Serial.println(unoBuffer, HEX);
  Wire.write(unoBuffer);
}
void setup() {
  Serial.begin(115200);
  Serial.println("UNO Started");
  for (int ledPin = 2; ledPin <= 9; ledPin++) {
    pinMode(ledPin, OUTPUT);
    digitalWrite(ledPin, ledStatus[ledPin - 2]);
  }
  Wire.begin(unoAddr);
  Wire.onReceive(i2cReceive);
  Wire.onRequest(i2cRequest);
}
void loop() {
}

สรุป

จากบทความนี้ผู้อ่านสามารถศึกษาวิธีการเขียนโปรแกรมเพื่อให้โมดูล WiFi ของ ESP8266/ESP32 ทำงานในโหมด AP และ STA โดยสามารถแสดงรายชื่อ Access Point และเขียนโปรแกรมเพื่อสั่งงาน Arduino Uno Maker ผ่านทางเว็บได้ สุดท้ายนี้ ทางทีมงาน JarutEx หวังว่าบทความนี้คงเป็นแนวทางการศึกษาการเขียนโปรแกรมควบคุมอุปกรณ์ด้วยภาษาไพธอน และมีประโยชน์ในการนำไปประยุกต์ใช้งานต่อไป

ท่านใดต้องการพูดคุยสามารถคอมเมนท์ไว้ได้เลยครับ

อ้างอิง

https://docs.micropython.org/en/latest/esp8266/tutorial/network_basics.html

(C) 2020, โดย อ.ดนัย เจษฎาฐิติกุล/อ.จารุต บุศราทิจ

ปรับปรุง 2020-09-26
ปรังปรุง 2021-09-14
ปรับปรุง 2021-11-30