# ESP32 Networking

## 1. **ESP32 Networking** <a href="#id-943e" id="id-943e"></a>

**ESP32** สามารถเชื่อมต่อกับอุปกรณ์อื่นแบบไร้สายได้ โดยใช้ **Wi-Fi** ตามมาตรฐาน **IEEE 802.11b/g/n (2.4GHz)** และ **Bluetooth** และมีโหมดการทำงานสำหรับ **Wi-Fi** ดังนี้

* **STA (Station)**
* **AP (Access Point)**&#x20;
* **Soft-AP** (**both STA and AP**)&#x20;

แต่ถ้าจะเชื่อมต่อกับ **Ethernet** จะต้องมีวงจร **Ethernet PHY Transceiver** (**RMII Interface**) นำมาต่อเพิ่ม เช่น **LAN8720** หรือ **TLK110 (10/100 Mbps)** เป็นต้น ตัวอย่างบอร์ด **ESP32** ที่มี **Ethernet/LAN Port** ได้แก่

* [**Espressif Systems ESP32-Ethernet-Kit**](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-ethernet-kit.html) (**ESP32-WROVER-B + IP101GRI Transceiver**)&#x20;
* [**LilyGo / TTGO T-Internet-POE Board**](https://github.com/Xinyuan-LilyGO/LilyGO-T-ETH-POE) **(ESP32-WROOM + LAN8720A Transceiver)**&#x20;
* [**Wireless Tag** **WT32-ETH01**](http://www.wireless-tag.com/portfolio/wt32-eth01/) **(ESP32-based WT32-S1 Wi-SoC + LAN8720A transceiver)**

สำหรับ **ESP32** การเชื่อมต่อด้วย **Wi-Fi** คงเป็นวิธีที่ง่ายและสะดวกที่สุด โดยทั่วไปแล้ว เราจะโปรแกรมให้อุปกรณ์ **ESP32** ทำงานในโหมด **STA** และเชื่อมต่อกับอุปกรณ์ **Wi-Fi Access Point** หรือ **Router** ในระบบเครือข่าย **WLAN** และทำให้สามารถเชื่อมต่อไปยังอินเทอร์เน็ตได้

ถ้าจะให้ **ESP32** ทำงานในโหมด **AP** หรือ **Soft-AP** ก็อนุญาตให้อุปกรณ์อื่นเชื่อมต่อผ่าน **Wi-Fi** เข้ามายัง **ESP32** ได้ เราอาจจะเห็นตัวอย่างการใช้งาน **ESP32** ที่ทำงานในโหมดดังกล่าวและทำหน้าที่เป็น **Light-weight Web Server** ในตัวด้วย ทำให้คอมพิวเตอร์อื่นสามารถเชื่อมต่อผ่าน **Wi-Fi** มายัง **ESP32** และผู้ใช้สามารถเปิด **Web Browser** เพื่อเข้าสู่หน้าเว็บของ **ESP32** เช่น ใช้ในการตั้งค่าอุปกรณ์ผ่านหน้าเว็บ หรือดูสถานะบางอย่างของระบบ เป็นต้น

อีกตัวเลือกหนึ่งที่น่าสนใจสำหรับ **ESP32** คือ การสื่อสารแบบไร้สายในรูปแบบที่เรียกว่า [**ESP-NOW**](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html) โดยไม่จำเป็นต้องใช้ **Wi-Fi Infrastructure** แต่ใช้รูปแบบการทำงานแบบ **Peer-to-Peer** และในปัจจุบัน เราก็สามารถใช้ [**ESP-NOW** (**Micropython Port**](https://github.com/glenn20/micropython/blob/espnow-g20/docs/library/espnow.rst)) ร่วมกับไมโครไพธอนได้เช่นกัน

## 2. ESP32 Wi-Fi Scan

เริ่มต้นตัวอย่างแรกด้วยการสแกนหาอุปกรณ์หรือเครือข่ายไร้สาย **Wi-Fi** โดยจะต้องทำให้ **ESP32** เปิดใช้งานในโหมด **STA** ก่อน และสามารถใช้คำสั่งจากไลบรารี `network` ดังนี้ โดยทำการสแกนซ้ำไปเรื่อย ๆ และถ้าต้องการหยุดหรือจบการทำงาน ให้กด **Ctrl+C** ในหน้าต่าง **Interactive Shell** ของ **REPL**

```python
import network
import utime as time

# open WiFi interface in STA mode 
wifi = network.WLAN( network.STA_IF )

# activate the WiFi interface (if_up)
wifi.active( True )

print ('Press Ctrl+C to stop WiFi scanning...')
try:
    while True:
        # scan WiFi
        scan_results = wifi.scan()
        # print scan results (a list of tuples)
        print(60*'=')
        for ap in scan_results:
            print( ap )
        print(60*'-')
except KeyboardInterrupt:
    print('Terminated...')
# deactivate the WiFi interface
wifi.active(False)
```

เมื่อสแกนเครือข่าย **Wi-Fi** ข้อมูลสำหรับแต่ละอุปกรณ์ที่พบ จะประกอบด้วยชื่ออ้างอิงในการเชื่อมต่อ (**SSID**), หมายเลขของอุปกรณ์ (**MAC address**), ช่องสัญญาณความถี่ (**Channel**), ค่าตัวชี้วัดความแรงของสัญญาณที่รับได้ (**RSSI**), โหมดการเข้ารหัสสำหรับความปลอดภัย (**Authentication Mode**) และมีการซ่อนชื่อ **SSID** หรือไม่ (**Hidden**) ตามลำดับ

ลองมาดูตัวอย่างการเขียนโค้ดไมโครไพธอนที่ใช้สำหรับสแกนระบบเครือข่าย **Wi-Fi** ที่อยู่โดยรอบ แล้วแปลงผลที่ได้ให้เป็นข้อมูลแบบ **JSON** เช่น แสดงชื่อ **SSID** รหัสเครื่อง **(MAC Address)** โหมดการตั้งค่าความปลอดภัย (**Authentication Mode**) และช่องสัญญาณ เป็นต้น

```python
import network
import ujson as json

param_names = ['essid','mac','channel','rssi','authmode','hidden']

authmodes = ['OPEN','WEP','WPA-PSK',
             'WPA2-PSK','WPA/WPA2-PSK','MAX']

def mac_bytes_to_hex_str( mac_bytes ):
    return ':'.join(['%02X' % x for x in mac_bytes])

def convert_to_json( found_list ):
    results = {'count': len(found_list), 'found_list': []}
    for ap in found_list:
        pairs = dict(zip(param_names,ap))
        for name in pairs:
            value = pairs.get(name)
            if type(value)==bytes:
                if name=='mac' and len(value)==6:
                    value = mac_bytes_to_hex_str( value )
                else:
                    value = value.decode('ascii')
            else:
                try:
                    value = int(value)
                    if name=='authmode' and 0 <= value <= 5:
                        value = authmodes[value]
                except (ValueError, TypeError):
                    pass
            pairs[name] = value
        results['found_list'].append( pairs )
    return results# open WiFi interface in STA mode

# activate the WiFi interface
wifi = network.WLAN( network.STA_IF )
wifi.active(True)
# scan WiFi and get the results as a list of tuples
scan_results = wifi.scan()
# deactivate the WiFi interface
wifi.active(False)
# convert a list of tuples to json data
results = convert_to_json( scan_results )

if results.get('count') > 0:
    for item in results.get('found_list'):
        print( json.dumps(item) ) # show JSON string
else:
    print( 'No WiFi found')
```

## **3. Wi-Fi Connection in STA Mode** <a href="#a9e3" id="a9e3"></a>

การเชื่อมต่อกับเครือข่าย **Wi-Fi** ในโหมด **STA** จะต้องระบุชื่อ **SSID** และรหัสผ่าน (**Password**) สำหรับเชื่อมต่ออุปกรณ์ไร้สาย เช่น **Wireless Access Point / Router** ตามตัวอย่างโค้ดต่อไปนี้

ในตัวอย่างนี้ มีการสร้างฟังก์ชันชื่อ `connect_wifi()` เพื่อใช้ในการเชื่อมต่อ **Wi-Fi (STA Mode)** เมื่อเรียกใช้ จะต้องระบุอาร์กิวเมนต์เป็นโครงสร้างข้อมูลแบบ **dictionary** อ้างอิงโดยตัวแปร `WIFI_CFG` ที่มีชื่อสมาชิก `‘ssid’` และ `‘pwd’` ตามลำดับ ดังนั้นให้กำหนดค่าให้ถูกต้องเมื่อนำไปใช้งาน

เมื่อเรียกใช้ฟังก์ชันนี้ และสามารถเชื่อมต่อได้สำเร็จ จะได้ค่ากลับคืนเป็นอ็อบเจกต์ที่ไม่ใช่ `None` และเราก็สามารถใช้คำสั่ง `wifi.ifconfig()` เพื่อดูข้อมูลเกี่ยวกับการเชื่อมต่อเครือข่าย เช่น หมายเลขของไอพีที่ได้รับ (**IP address**) หมายเลขของไอพีของอุปกรณ์ **Gateway** เป็นต้น

```python
import network
import utime as time

# Specify the SSID and password of your WiFi network
WIFI_CFG = { 'ssid': "XXXXXX", 'pwd': "XXXXXXXXX" }

def connect_wifi( wifi_cfg, max_retries=10 ):
    # use WiFi in station mode (not AP)
    wifi_sta = network.WLAN( network.STA_IF )
    # activate the WiFi interface (up)
    wifi_sta.active(True)
    # connect to the specified WiFi AP
    wifi_sta.connect( wifi_cfg['ssid'], wifi_cfg['pwd'] )
    retries = 0
    while not wifi_sta.isconnected():
        retries = retries + 1
        if retries >= max_retries:
            return None
        time.sleep_ms(500)
    return wifi_sta

# try to connect the network
wifi = connect_wifi( WIFI_CFG )

if wifi is None:
    print( 'WiFi connection failed' )
else:
    ipaddr, netmask, gateway, dns = wifi.ifconfig()
    print("============================")
    print("IP address  :", ipaddr)
    print("Net mask    :", netmask)
    print("Gateway     :", gateway)
    print("DNS server  :", dns)
    print("----------------------------")
```

## **4. Wi-Fi in AP Mode + Simple HTTP Server** <a href="#cc19" id="cc19"></a>

ตัวอย่างถัดไป สาธิตการเปิดใช้งาน **Wi-Fi** ในโหมด **AP** แบบ **Open** (ไม่มีการป้องกันด้วยรหัสผ่าน) โดยการสร้างและเรียกใช้ฟังก์ชัน `start_wifi_ap()` จากนั้นเมื่อ **ESP32** ทำงานเป็น **Wi-Fi AP** แล้ว (จะมีหมายเลขไอพีตรงกับ `192.168.4.1`  และเลือกใช้ช่องสัญญาณหมายเลข 11) ยังได้มีการสร้างและเรียกใช้ฟังก์ชัน `start_web_server()` เพื่อให้สามารถทำงานเป็น **Web Server** (**HTTP protocol**) อย่างง่ายได้ด้วย แสดงข้อความเป็นหน้าเว็บได้

ในตัวอย่างนี้ ถ้าต้องการจบการทำงานของ **Web Server** เราจะใช้วิธีตรวจสอบการกดปุ่ม ดังนั้นจะต้องมีการต่อวงจรกดปุ่มภายนอกแบบ **Active-Low** และเลือกใช้ขา **GPIO23** เป็นอินพุตสำหรับปุ่มกด

```python
import network
import utime as time 
import usocket as socket
from machine import Pin

AP_CFG = { 'essid': 'MyAP', # SSID for your AP
           'authmode': network.AUTH_OPEN, 'channel': 11, }

def start_wifi_ap( ap_cfg ):
    # open WiFi in AP mode
    ap = network.WLAN( network.AP_IF )
    # activate AP interface (up)
    ap.active(True)
    # configure the AP interface
    try:
        ap.config( **ap_cfg )
    except Exception as ex:
        print ( 'Cannot set AP configuration...' )
    if ap.active():
       return ap
    else:
       return False

def start_web_server( btn=None ):
    global sock
    # create a socket (STREAM TCP socket) for network connection
    sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
    # set socket option: reuse address
    sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 )
    # use socket in non-blocking mode
    sock.setblocking( False )
    # get the server address (localhost)
    addr = socket.getaddrinfo('0.0.0.0',80)[0][-1]
    # bind the socket to port 80 (HTTP)
    sock.bind( addr )
    # listen for an incoming connection
    sock.listen(1)
    while True:
        try:
            # waiting for connection
            conn,addr = sock.accept() 
        except OSError:
            if btn and btn.value()==0: # check button
               sock.close() # close socket
               break        # break the loop
            else:
               continue
        # read incoming request data
        request = conn.recv(1024) 
        print( 'Content = %s' % str(request) )
        # send HTML as response
        html = '<html><head></head><body>'
        html += '<h1>Welcome to ESP32...</h1><br>'
        html += '<b>Client from {}<b><br>'.format(str(addr))
        html += '</body></html>'
        conn.send( html ) # send HTML response
        conn.close() # close HTTP connection
        time.sleep_ms(10)

sock = None
ap = start_wifi_ap( AP_CFG )
ipaddr = ap.ifconfig()[0]
print ( 'IP address:', ipaddr ) # 192.168.4.1

BTN_GPIO = const(23) # use GPIO23 for push button
btn = Pin( BTN_GPIO, Pin.IN, Pin.PULL_UP )

try:
   start_web_server( btn ) # start web server
except KeyboardInterrupt:
    pass
finally:
    if sock:
        sock.close()
    ap.active(False) # turn off WiFi AP
```

ถ้าจะเปลี่ยนจาก **Open** เป็นการตั้งรหัสป้องกันด้วยวิธี **WPA2-PSK** ก็กำหนดค่าสำหรับ **Configuration** ตามตัวอย่างดังนี้

`AP_CFG = { 'essid': 'MyAP', 'password': 'YOUR_STRONG_PASSWORD',` \
&#x20;          `'authmode': network.AUTH_WPA2_PSK, 'channel':11, }`

## 5. Getting Date & Time From NTP Server&#x20;

ตัวอย่างถัดไปสาธิตการเปิดใช้งาน **ESP32** ในโหมด **STA** และเชื่อมต่อกับอุปกรณ์ **Wi-Fi AP** ที่สามารถเชื่อมต่อไปยังอินเทอร์เน็ตได้ และใช้คำสั่ง `ntptime.settime()` เพื่อเชื่อมต่อกับคอมพิวเตอร์ในอินเทอร์เน็ตที่ทำหน้าที่เป็น **NTP Server**  และอัปเดทเวลาปัจจุบันสำหรับการทำงานของวงจรของ **RTC (Real-Time Clock)** ของ **ESP32**

```python
import network
import utime as time
import ntptime
import machine

# Specify the SSID and password of your WiFi network
WIFI_CFG = { 'ssid': "XXXX", 'pwd': "XXXXXXX" }

def connect_wifi( wifi_cfg ):
    wifi_sta = network.WLAN( network.STA_IF )
    wifi_sta.active(True)
    wifi_sta.connect( wifi_cfg['ssid'], wifi_cfg['pwd']  )
    while not wifi_sta.isconnected():
        time.sleep(1.0)
    print(wifi_sta.ifconfig())

def sync_ntp():
    while True:
        try:
            # synchronize RTC with NTP server
            ntptime.settime()
            break
        except OSError:
            print('NTP server: connection timeout...')

connect_wifi( WIFI_CFG )
sync_ntp()

rtc = machine.RTC()
tm = rtc.datetime() # get current RTC datetime (now)
tz_offset = +7 # timezone offset (GMT+7)
hh, mm, ss = (tm[4]+tz_offset) % 24, tm[5], tm[6]
print( 'Time: {}:{}:{}'.format( hh, mm, ss ) )
y, m, d = tm[0], tm[1], tm[2]
print( 'Date: {}-{:02d}-{:02d}'.format( y, m, d ) )
```

## 6. HTTP GET: Getting Datetime Using WorldTime API <a href="#id-1317" id="id-1317"></a>

ตัวอย่างถัดไปสาธิตการเปิดใช้งาน **ESP32** ในโหมด **STA** และเชื่อมต่อกับอุปกรณ์ **Wi-Fi AP** ที่สามารถเชื่อมต่อไปยังอินเทอร์เน็ตได้ และร้องขอข้อมูลแบบ **HTTP Request / GET Method** ไปยัง [**World Time API**](http://worldtimeapi.org/) ซึ่งเป็นผู้ให้บริการข้อมูลเกี่ยวกับวันและเวลาในปัจจุบันตามโซนเวลา (**Timezone**) ที่ระบุไว้ ข้อมูลที่ได้รับกลับมานั้น จะอยู่ในรูปแบบของ **JSON String**

```python
import network
import utime as time
import ujson as json
import urequests as requests

URL = "http://worldtimeapi.org/api/timezone/Asia/Bangkok"

# Specify the SSID and password of your WiFi network
WIFI_CFG = { 'ssid': "XXXX", 'pwd': "XXXXXXX" }

def connect_wifi( wifi_cfg, max_retries=10 ):
    # use WiFi in station mode (not AP)
    wifi_sta = network.WLAN( network.STA_IF )
    # activate the WiFi interface (up)
    wifi_sta.active(True)
    # connect to the specified WiFi AP
    wifi_sta.connect( wifi_cfg['ssid'], wifi_cfg['pwd']  )
    retries = 0
    while not wifi_sta.isconnected():
        retries = retries + 1
        if retries >= max_retries:
            return None
        time.sleep_ms(500)
    return wifi_sta

def get_worldtime_data( url ):
    resp = requests.get( url )
    if resp.status_code==200:  # request ok
        try:
            data = json.loads(resp.text)
        except Exception as ex:
            print ('JSON data error...' )
            return
        print( 'Timezone:', data['timezone'] )
        print( 'Epoch time (Jan 1, 1970):', int(data['unixtime']) ) 
        print( 'UTC   datetime:', data['utc_datetime'] )
        print( 'Local datetime:', data['datetime'] )
        print( 'UTC offset   (hours):', data['utc_offset'] )
        print( 'UTC offset (seconds):', int(data['raw_offset']) )
    else:
        print ('HTTP request error...')

# try to connect the network
wifi = connect_wifi( WIFI_CFG )
if wifi is not None:
    get_worldtime_data( URL )
else:
    print('No WiFi connection')
```

ตัวอย่างเอาต์พุตที่ได้

`Timezone: Asia/Bangkok` \
`Epoch time (Jan 1, 1970): 1609939805` \
`UTC   datetime: 2021-01-06T13:30:05.138524+00:00` \
`Local datetime: 2021-01-06T20:30:05.138524+07:00` \
`UTC offset   (hours): +07:00` \
`UTC offset (seconds): 25200`&#x20;

## 7. HTTPS GET: Thailand's COVID-19 Status Update <a href="#id-16e2" id="id-16e2"></a>

ตัวอย่างถัดไป สาธิตการดึงข้อมูลเกี่ยวกับผู้ป่วย **COVID-19** สำหรับประเทศไทย ด้วยวิธี **HTTPS GET** จาก **API** ของเว็บ [covid19.th-stat.com](http://covid19.th-stat.com/) ข้อมูลที่ได้จะอยู่ในรูปของ **JSON String**

```python
import network
import utime as time
import urequests as requests

URL = "https://covid19.th-stat.com/api/open/today"

# Specify the SSID and password of your WiFi network
WIFI_CFG = { 'ssid': "XXXX", 'pwd': "XXXXXXX" }
WIFI_CFG = { 'ssid': "esl_ap", 'pwd': "cnch2687" }

COVID19_NAMES = [
    ('Confirmed',    u'ยอดผู้ป่วยสะสม'),
    ('Recovered',    u'ผู้ป่วยรักษาหายแล้ว'),
    ('Hospitalized', u'ผู้ป่วยรักษาตัวในโรงพยาบาล'),
    ('Deaths',       u'ผู้เสียชีวิตสะสม'),
    ('NewConfirmed', u'ผู้ป่วยตรวจพบเพิ่มวันนี้'),
    ('NewRecovered', u'ผู้ป่วยรักษาหาย'),
    ('NewDeaths',    u'ผู้เสียชีวิตวันนี้'),
    ('UpdateDate',   u'อัปเดตล่าสุด') ]

def get_covid19_data( url ):
    data = None
    try:
        resp = requests.get( url )
        data = resp.json()
        for name in COVID19_NAMES[:-1]:
            if name[0] in data:
                value = str(data[name[0]])
                print('{}:\t\t\t\t{}'.format(name[1],value) )
        name = COVID19_NAMES[-1]
        update = data[ name[0] ]
        print( '{}: {}'.format(name[1], update)  )
    except Exception as ex:
        print('error:', ex)
    return data

def connect_wifi( wifi_cfg ):
    wifi_sta = network.WLAN( network.STA_IF )
    wifi_sta.active(True)
    wifi_sta.connect( wifi_cfg['ssid'], wifi_cfg['pwd']  )
    while not wifi_sta.isconnected():
        time.sleep(1.0)
    print(wifi_sta.ifconfig())

connect_wifi( WIFI_CFG )
import gc
gc.collect()
get_covid19_data( URL )
```

ตัวอย่างข้อความเอาต์พุตที่ได้

`ยอดผู้ป่วยสะสม: 9331` \
`ผู้ป่วยรักษาหายแล้ว: 4418` \
`ผู้ป่วยรักษาตัวในโรงพยาบาล: 4847` \
`ผู้เสียชีวิตสะสม: 66` \
`ผู้ป่วยตรวจพบเพิ่มวันนี้: 365` \
`ผู้ป่วยรักษาหาย: 21` \
`ผู้เสียชีวิตวันนี้: 1` \
`อัปเดตล่าสุด: 06/01/2021 12:25`

## 8. Getting Air Quality & Weather Data from AirVisual

ตัวอย่างถัดไปแสดงการขอข้อมูลเกี่ยวกับสภาพอากาศและดัชนีชี้วัดคุณภาพอากาศจาก [**Airvisual Platform**](https://www.iqair.com/) ซึ่งใช้วิธีสื่อสารแบบ **HTTPS** ตามรูปแบบ [**AirVisual REST API**](https://api-docs.iqair.com/) ที่ทางบริษัทกำหนดไว้ และจะได้ข้อมูลกลับมาเป็น **JSON Data**&#x20;

การเรียกใช้ **REST API** ในกรณีนี้ จะต้องมี **API Key** โดยจะต้องสมัครสมาชิกเพื่อใช้งาน โดยเลือกแบบฟรี (**Community**) และระบุสถานที่หรือ Location (ในตัวอย่างนี้คือ เมืองนนทบุรี ประเทศไทย)

```python
import sys
import network
import ujson as json
import utime as time
import urequests as requests

JSON_CONFIG_FILE = 'config.json'
AIRVISUAL_URL    = 'https://api.airvisual.com/v2/city'

config = {}
try:
    with open( JSON_CONFIG_FILE ) as json_file:
        config = json.load(json_file)
except OSError as ex:
    print('Cannot open JSON file')
    sys.exit(-1)

sta_if = network.WLAN( network.STA_IF )
sta_if.active( True )

sta_if.connect( config['ssid'], config['pwd'] )
while not sta_if.isconnected():
    time.sleep(1.0)
# show IP address assigned by DHCP server
#print( 'Connected:', sta_if.ifconfig()[0] )

# Fetch JSON data by using the AirVisual API
url_template = AIRVISUAL_URL + '?city={}&state={}&country={}&key={}'
url = url_template.format( 
    'Mueang%20Nonthaburi',
    'Nonthaburi',
    'Thailand',
    config['airvisual_api_key'] )

resp = None
try:
    resp = requests.get( url )
except OSError:
    print('Cannot fetch data')

if resp and resp.status_code == 200: # OK
    json_data = resp.json()
    if json_data['status'] == 'success':
        data = json_data['data']
        location = (data['state'],data['city'],data['country'])
        polution = data['current']['pollution']
        us_aqi = polution['aqius']
        ts = polution['ts'] # '2020-05-19T02:00:00.000Z'
        weather = data['current']['weather']
        tp = weather['tp'] # temperature (deg.C)
        hu = weather['hu'] # humidity (%)
        pr = weather['pr'] # pressure (mBar or hPa)
        print('Location: {}, {}, {}'.format(*location) )
        print( 'US AQI      : {}'.format(us_aqi) )
        print(('Temperature : {} deg.C\n' +
               'Humidity    : {} %RH\n' +
               'Pressure    : {} mBar').format(tp,hu,pr))
else:
    print (resp.reason)
```

ในการทำงานของโค้ดนี้ จะต้องมีไฟล์ `config.json` ซึ่งได้บันทึกเก็บไว้ใน **MicroPython Flash Storage** และมีข้อความแบบ **JSON** ตามลักษณะดังนี้

`{ "ssid": "XXXXXXX", "pwd": "XXXXXXXXX",` \
&#x20; `"airvisual_api_key": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX" }`

ตัวอย่างข้อความเอาต์พุต มีดังนี้

`Location: Nonthaburi, Mueang Nonthaburi, Thailand` \
`US AQI      : 126` \
`Temperature : 29 deg.C` \
`Humidity    : 55 %RH` \
`Pressure    : 1011 mBar`

## **9. Getting Weather Data from OpenWeatherMap** <a href="#id-0a64" id="id-0a64"></a>

[**OpenWeatherMap**](https://openweathermap.org/) (<https://openweathermap.org/>) มีบริการ **API** ให้ดึงข้อมูลด้วยโพรโทคอล **HTTP/HTTPS (**&#xE14;ูตัวอย่างการใช้ **API** และตัวอย่างข้อมูลในรูปแบบ **JSON** ได้จาก <https://openweathermap.org/current>)

ผู้ใช้จะต้องสมัครสมาชิกก่อนจึงจะสามารถใช้งานได้ฟรี (แต่มีข้อจำกัดและเงื่อนไขในการใช้งาน) เพื่อให้ได้ **APPID** ซึ่งเป็นข้อความแบบเลขฐานสิบหกจำนวน 32 หลัก

ในตัวอย่างนี้ เราจะดึงข้อมูลสภาพภูมิอากาศ และเลือกนำมาแสดงเฉพาะ อุณหภูมิ (**Temperature**) ความชื้นสัมพัทธ์ (**Relative Humidity**) และความกดอากาศ (**Pressure**) และเลือกเมืองเป็น จังหวัดนนทบุรี (**Nonthaburi, TH**) แล้วนำมาแสดงผลเป็นข้อความเอาต์พุตผ่านทาง **REPL**

```python
import sys
import network
import ujson as json
import utime as time
import urequests as requests

# URL for openweathermap
API_URL = 'https://api.openweathermap.org/data/2.5/weather'

JSON_CONFIG_FILE = 'config.json'

config = {}
try:
    with open( JSON_CONFIG_FILE ) as json_file:
        config = json.load(json_file)
except OSError as ex:
    print('Cannot open JSON file')
    sys.exit(-1)

sta_if = network.WLAN( network.STA_IF )
sta_if.active( True )
sta_if.connect( config['ssid'], config['pwd'] )
while not sta_if.isconnected():
    time.sleep(1.0)

#########################################################

def get_weather_data( APPID, city ):
    results = {}
    url = API_URL+'?units=metric&APPID='+APPID+'&q='+city
    try:
        data = requests.get(url).json()
    except OSError:
        print ('Cannot get data from OpenWeatherMap..')
        return results
    code = data.get('cod')
    #print( json.dumps(data) )
    if code == 200: # response OK
        city = data['name']
        results['p'] = data['main']['pressure'] # hPa
        results['t'] = data['main']['temp']     # deg.C
        results['h'] = data['main']['humidity'] # %
    elif code == 401:
        print('Error Code:', code)
        print( data['message'] )
    return results

city_name = 'Mueang Nonthaburi, TH'
weather_data = {
    't': (' - Temperature :','deg.C'),
    'h': (' - Humidity    :','%RH'),
    'p': (' - Pressure    :','hPa'), }

appid = config['openweather_appid']
results = get_weather_data( appid, city_name )
print( city_name ) # show city name
for key in weather_data:
    name, unit = weather_data[key]
    value = results[key]
    print('{} {} {}'.format(name,value,unit) )
```

ในการทำงานของโค้ดนี้ จะต้องมีไฟล์ `config.json` ซึ่งได้บันทึกเก็บไว้ใน **MicroPython Flash Storage** และมีข้อความแบบ **JSON** ตามลักษณะดังนี้

`{ "ssid": "XXXXXXX", "pwd": "XXXXXXXXX",` \
&#x20; `"openweather_appid": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }`

ตัวอย่างข้อความเอาต์พุต มีดังนี้

`Mueang Nonthaburi, TH`\
&#x20;`- Temperature : 29.53 deg.C`\
&#x20;`- Pressure    : 1011 hPa`\
&#x20;`- Humidity    : 54 %RH`

## 10. Getting Air Quality Data from Air4Thai

ตัวอย่างถัดไป สาธิตการดึงข้อมูลเกี่ยวกับคุณภาพอากาศในเขตกรุงเทพจากเว็บ [**Air4Tha**](http://air4thai.pcd.go.th)i ใช้ **HTTP GET** แล้วนำข้อมูลบางส่วนซึ่งเป็น **JSON Data** มาแสดงผลเป็นข้อความเอาต์พุตทาง **REPL**

ในตัวอย่างนี้ได้เลือกสถานีตรวจวัดที่มี **Station ID** ตรงกับ `bkp95t` ซึ่งเป็นเขตบางซื่อของกรุงเทพ ฯ

```python
import sys
import network
import ujson as json
import utime as time
import urequests as requests

STATION_ID = 'bkp95t'
API_URL = 'http://air4thai.pcd.go.th/services/getNewAQI_JSON.php'

JSON_CONFIG_FILE = 'config.json'

config = {}
try:
    with open( JSON_CONFIG_FILE ) as json_file:
        config = json.load(json_file)
except OSError as ex:
    print('Cannot open JSON file')
    sys.exit(-1)

sta_if = network.WLAN( network.STA_IF )
sta_if.active( True )
sta_if.connect( config['ssid'], config['pwd'] )
while not sta_if.isconnected():
    time.sleep(1.0)

#########################################################

def get_air4thai_data( url, station_id ):
    try:
        api_url = url + '?stationID=' + station_id
        json_data = requests.get(api_url).json()
    except OSError:
        print ('Cannot get data from air4thai...')
        return results
    #print( json.dumps(data) )
    station = json_data['areaEN']
    print( 'Station: {}'.format(station) )
    data = json_data['LastUpdate']
    print( 'Last update:', data['date'], data['time'] )
    print( 'PM25:', data['PM25']['value'], data['PM25']['unit'] )
    print( 'AQI:', data['AQI']['aqi'] )

get_air4thai_data( API_URL, STATION_ID ) 
```

ตัวอย่างข้อความเอาต์พุต มีดังนี้

`Station: Bang Sue, Bangkok` \
`Last update: 2021-01-06 00:00` \
`PM25: 41 µg/m³` \
`AQI: 63`

## 11. ส่งข้อความแจ้งเตือนผ่าน LINE Notify API

อีกตัวอย่างหนึ่งพบเห็นได้คือ การให้อุปกรณ์ประเภทไมโครคอนโทรลเลอร์สำหรับงาน **IoT** สามารถส่งข้อความแจ้งเตือน หรือ **Chat Message** ไปยังผู้ใช้ **LINE App** โดยใช้บริการที่เรียกว่า [**LINE Notify API**](https://notify-bot.line.me/doc/en/) ซึ่งมีการเริ่มใช้งานมาตั้งแต่ปีค.ศ. **2016**

ถ้าจะใช้งานได้นั้น ผู้ใช้จะต้องไปตั้งค่าการใช้งานผ่านหน้าเว็บที่ <https://notify-bot.line.me/en/> เพื่อให้ได้ **Access Token** มาใช้งาน

1. ทำขั้นตอน **Login** เข้าใช้งานด้วย **email address** และ **password** แล้วยืนยันผ่าน **LINE App** บน **Smartphone**
2. ไปที่เมนู [**MyPage**](https://notify-bot.line.me/my/)
3. กดปุ่ม **Generate Token** ในส่วนที่เรียกว่า "**Generate access token (For developers)"**
4. จากนั้นจะมี **Pop-up window** ให้กรอกข้อมูล โดยจะต้องระบุ **Token Name** (ชื่อที่จะปรากฎเมื่อส่งข้อมูลไปยัง **LINE**) และเลือกว่า จะส่งข้อความ **Chat** ไปยังกลุ่ม **LINE** ใด ซึ่งผู้ใช้ได้สร้างหรืออยู่ในกลุ่มดังกล่าว หรือจะส่งหาตัวเองก็ได้โดยให้เลือก **"1-on-1 chat with LINE notify"**  แล้วจึงกดปุ่ม **Generate Token**
5. เมื่อมีการจับคู่และสร้าง **Token** แล้ว ในหน้า **MyPage** ในส่วนที่เรียกว่า "**Connected services**" จะมีรายการการเชื่อมต่อ **From** (ชื่อ **Token Name** ตามที่ได้ตั้งไว้ ) **To** (ชื่อกลุ่มไลน์ตามที่ได้เลือก หรือ ชื่อบัญชีของผู้ใช้เอง)&#x20;

การส่งข้อความไปยัง **LINE Notify API** จะใช้วิธี **HTTPS POST Method** โดยระบุ **URL** เป็น <https://notify-api.line.me/api/notify> และในส่วน **HTTPS Header** จะมีต้องการระบุ `'Authorization'` ร่วมกับ `'Bearer '` ตามด้วย **Access Token**&#x20;

ตัวอย่างโค้ดสำหรับไมโครไพธอนที่ทดลองใช้งานกับ **ESP32** มีดังนี้ และเป็นการทดลองส่งข้อความ เช่น ระบุค่าอุณหภูมิและความชื้นในบ้าน (เป็นตัวอย่างสมมุติ)

```python
import utime as time
import ujson as json
import usocket as socket
import ussl as ussl
import network
import urllib.parse

JSON_CONFIG_FILE = 'config.json'

config = {}
try:
    with open( JSON_CONFIG_FILE ) as json_file:
        config = json.load(json_file)
except OSError as ex:
    print('Cannot open JSON file')
    sys.exit(-1)

sta_if = network.WLAN( network.STA_IF )
sta_if.active( True )

sta_if.connect( config['ssid'], config['pwd'] )
while not sta_if.isconnected():
    print('waiting for Wi-Fi connection')
    time.sleep(1.0)

#########################################################
def https_post( url, headers={}, data='' ):
    proto, _, host, path = url.split("/", 3)
    assert( proto == 'https:' )
    port = 443
    
    if headers is None:
        headers = {}
    required_headers = {
        'Host'          : host,
        'User-Agent'    : 'MicroPython',
        'Cache-Control' : 'no-cache',
        'User-Agent'    : 'MicroPython',
        'Content-Type'  : 'application/x-www-form-urlencoded' }
    for key in required_headers.keys():
        if key not in headers:
            headers[key] = required_headers[key]

    # get address info of the host machine
    ai = socket.getaddrinfo( host, port, 0, socket.SOCK_STREAM )[0]
    # create a secure socket 
    s = socket.socket( ai[0], ai[1], ai[2] )
    s.connect(ai[-1])
    s.settimeout(2.0)
    s = ussl.wrap_socket(s, server_hostname=host)
    # write header lines
    s.write(b'POST /%s HTTP/1.1\r\n' % path)
    for k in headers:
        s.write(k)
        s.write(b': ')
        s.write(headers[k])
        s.write(b'\r\n' )
    s.write( b'Content-Length: %d\r\n' % (len(data)) )
    s.write( b'\r\n' )
    # write data
    s.write(data)
    
    # receive the response from the server
    status_code = 0
    eof_count = 0
    resp_text = ''
    while True:
        try:
            line = s.readline().decode().strip()
            if line == '':
                eof_count += 1
                if eof_count >= 2:
                    break
            if line.startswith( 'HTTP' ):
                status_code = line.split(' ')[1]
            elif line.startswith('{') and eof_count==1:
                resp_text += line
        except OSError:
            break
    s.close()
    return (status_code, resp_text)

#########################################################

token = config['line_notify_token']
msg = {'message': u'ในบ้าน อุณหภูมิ 27.5 °C ความชื้น 55.7%'}
headers = {'Authorization' : 'Bearer %s' % token}
data = '{}={}'.format( 'message', urllib.parse.quote(msg['message']) )
url = 'https://notify-api.line.me/api/notify'
status_code, resp_text = https_post( url, headers, data )
if status_code == '200':
    print( resp_text )
```

ข้อมูลที่ใช้สำหรับการตั้งค่า **Wi-Fi** และ **LINE Token Access** จะถูกเก็บอยู่ในไฟล์ `config.json` ใน **Flash File Storage** ของตัวอุปกรณ์ไมโครไพธอน ตัวอย่างเช่น

`{ "ssid": "my_wifi_ssid",` \
&#x20; `"pwd": "my_wifi_password",` \
&#x20; `"line_notify_token": "sSVjJ3qoX17pdw5UETGfi3EhLxaCzTxxxxxxxxxxxx" }`&#x20;

เมื่อส่งข้อความไปยัง **LINE Notify Service** จะได้ข้อความตอบกลับมาเป็น **JSON String** ถ้าทำงานได้ถูกต้องจะได้ข้อความ

`{"status":200,"message":"ok"}`&#x20;

![รูปภาพ: ตัวอย่างข้อความที่ได้รับบนสมาร์ทโฟน](https://969412697-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MIHfYo9IV3uTFm2tkDn%2F-MQe6RXdP9xWGgH3VIhi%2F-MQeBTG5lxfiw8ACZTWr%2Fline_notify_micropython.png?alt=media\&token=f75da918-7383-405c-8a26-5fa78f239991)

แต่ถ้าเกิดความผิดพลาด จะได้ข้อความ เช่น ในกรณีที่ไม่ได้ระบุ **Authorization** ในส่วน **Header** ของ **HTTPS POST**

`{"status":401,"message":"Missing authorization header"}`&#x20;

หรือถ้าระบุ **Access Token** ไม่ถูกต้อง ก็จะได้ข้อความ

&#x20;`{"status":401,"message":"Invalid access token"}`

**ข้อสังเกต:** เนื่องจากว่า มีการใช้ข้อความเป็นภาษาไทยด้วย ซึ่งบันทึกเป็น **UTF-8** และเมื่อจะส่งออกไปโดยใช้ **HTTPS POST** จึงต้องมีการแปลงข้อมูลก่อน โดยใช้คำสั่ง `urllib.parse.quote()`&#x20;

ดังนั้นจึงจะต้องติดตั้งไลบรารี `urllib.parse` ของ [mciropython-lib](https://github.com/micropython/micropython-lib) โดยสามารถทำได้ง่ายใน **MicroPython REPL** โดยใช้คำสั่ง `upip` ดังนี้ (และการทำงานของ **MicroPython** ใน **ESP32**  จะต้องเคยเชื่อมต่อกับ **Wi-Fi** ได้สำเร็จแล้ว)

&#x20;`>>> import upip` \
&#x20;`>>> upip.install('urllib.parse')`

และจะมีการดาวน์โหลดไฟล์ (**.tar.gz**) ที่เกี่ยวข้องจากเว็บ **micropython.org** แล้วเก็บลงใน `/lib`

**ปัญหาที่พบ:** แต่การใช้งานไลบรารี `urllib.parse` จะพบปัญหา วิธีแก้คือ ให้ลบไฟล์ `re.py` และ `ffilib.py` ออกไป จากนั้นกด **Ctrl+D** เพื่อเริ่มต้นการทำงานของ **MicroPython** ใหม่อีกครั้ง

![รูปภาพ: การติดตั้งไลบรารีผ่านทาง REPL ของ Thonny IDE](https://969412697-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MIHfYo9IV3uTFm2tkDn%2F-MQe4Q2NghZ2kac7s4EN%2F-MQe5G8WVFd9wvgvparO%2Fthonny_ide_urllib_upip.png?alt=media\&token=b57a4e2d-3a5e-40e5-a314-bcc89830a7d2)

เราสามารถลองใช้ไลบรารี [`urequests`](https://github.com/micropython/micropython-lib/blob/master/urequests/urequests.py) ซึ่งเป็นโมดูลแบบ **built-in** สำหรับไมโครไพธอน และใช้คำสั่ง `urequests.post()` สำหรับส่งข้อมูลแบบ **POST** ได้ แต่จากการที่ได้ลองใช้กับ **LINE Notify API** พบว่า มีความผิดพลาดเกิดขึ้นในการรับข้อมูลตอบกลับ (**Response**) แต่ก็ได้รับข้อความใน **LINE App**

```python
import network
import sys
import ujson as json
import urllib.parse
import urequests as requests

JSON_CONFIG_FILE = 'config.json'

config = {}
try:
    with open( JSON_CONFIG_FILE ) as json_file:
        config = json.load(json_file)
except OSError as ex:
    print('Cannot open JSON file')
    sys.exit(-1)

sta_if = network.WLAN( network.STA_IF )
sta_if.active( True )

sta_if.connect( config['ssid'], config['pwd'] )
while not sta_if.isconnected():
    print('waiting for Wi-Fi connection')
    time.sleep(1.0)

#########################################################

token = config['line_notify_token']
url = 'https://notify-api.line.me/api/notify'
msg = {'message': u'ในบ้าน อุณหภูมิ 29.8 °C ความชื้น 69.0%' }
headers = {'Authorization' : 'Bearer %s' % token,
           'Content-Type'  : 'application/x-www-form-urlencoded' }
data = '{}={}'.format( 'message', urllib.parse.quote(msg['message']) )
try:
    resp = requests.post( url, headers=headers, data=data )
    print(resp.status_code, resp.text)
except Exception as ex:
    print(ex)
```

## 12. ตรวจสอบการเชื่อมต่อในเครือข่ายด้วย Ping

ตัวอย่างถัดไปสาธิตการตรวจสอบการเชื่อมต่อกับเครื่องคอมพิวเตอร์หรืออุปกรณ์ในเครือข่าย โดยใช้คำสั่ง **ping** ซึ่งเป็นการส่งแพคเกจตามรูปแบบ **ICMP (Internet Control Message Protocol)**

![รูปภาพ: โครงสร้าง IPv4 ICMP Packet (Source: Wikipedia)](https://969412697-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-MIHfYo9IV3uTFm2tkDn%2F-MQelnAjWIYvE8G0Us6F%2F-MQelxeu5W5IOTeyuRRn%2Ficmp_packet_ipv4.png?alt=media\&token=e3186a85-7fdf-41d2-9087-bc8cfcb6e809)

โค้ดตัวอย่างต่อไปนี้ ดัดแปลงมาจากโค้ด [`uping.py`](https://gist.github.com/shawwwn/91cc8979e33e82af6d99ec34c38195fb) (**Micro-Ping for MicroPython**) ที่ได้มีการแชร์ไว้ใน [gist.github.com](https://gist.github.com/shawwwn/91cc8979e33e82af6d99ec34c38195fb)

```python
import network
import utime as time
import ujson as json

########################################################

def checksum(data): # CRC16 
    if len(data) % 2 == 1:
        data += b'\x00'
    chksum = 0
    for pos in range(0, len(data), 2):
        hi_byte = data[pos]
        lo_byte = data[pos + 1]
        chksum += (hi_byte << 8) + lo_byte
    while chksum >= 0x10000:
        chksum = (chksum & 0xffff) + (chksum >> 16)
    chksum = ~chksum & 0xffff
    return chksum

def ping(host, count=4, timeout=5000,
         interval=10, quiet=False, size=64):
    
    import utime 
    import uselect
    import uctypes 
    import usocket
    import ustruct
    import urandom
    
    # prepare packet
    assert( (size >= 16),   "packet size too small")
    assert( (size <= 1000), "packet size too big")
    pkt = b'Q'*(size)
    pkt_desc = {
        "type"     : uctypes.UINT8  | 0,
        "code"     : uctypes.UINT8  | 1,
        "checksum" : uctypes.UINT16 | 2,
        "id"       : uctypes.UINT16 | 4,
        "seq"      : uctypes.INT16  | 6,
        "timestamp": uctypes.UINT64 | 8,
    } # packet header descriptor
    h = uctypes.struct( uctypes.addressof(pkt),
                        pkt_desc, uctypes.BIG_ENDIAN )
    h.type = 8 # ICMP_ECHO_REQUEST
    h.code = 0
    h.checksum = 0
    h.id = urandom.getrandbits(16)
    h.seq = 1
    # init socket
    sock = usocket.socket(usocket.AF_INET, usocket.SOCK_RAW, 1)
    sock.setblocking(0)
    sock.settimeout(timeout/1000.0)
    addr = usocket.getaddrinfo(host, 1)[0][-1][0] # ip address
    sock.connect((addr, 1))
    if not quiet:
        print("PING %s (%s): %u data bytes" % (host, addr, len(pkt)))
    seqs = list(range(1, count+1))
    c = 1
    t = 0
    n_trans = 0
    n_recv = 0
    finish = False
    while t < timeout:
        if t==interval and c<=count:
            # send packet
            h.checksum = 0
            h.seq = c
            h.timestamp = utime.ticks_us()
            h.checksum  = checksum(pkt)
            if sock.send(pkt) == size:
                n_trans += 1
                t = 0 # reset timeout
            else:
                seqs.remove(c)
            c += 1

        # recv packet
        while True:
            socks,_,_ = uselect.select([sock],[],[],0)
            if socks:
                # receive incoming packet as response
                resp = socks[0].recv(1024)
                # create a memoryview object
                resp_mv = memoryview(resp)
                h2 = uctypes.struct(uctypes.addressof(resp_mv[20:]),
                                    pkt_desc, uctypes.BIG_ENDIAN)
                # validate checksum
                calc_chksum = checksum(bytes(resp_mv[24:]))
                chksum = ustruct.unpack("!H", resp_mv[22:24])[0]
                chksum_ok = chksum == calc_chksum
                assert chksum_ok, 'Checksum failed...'
                
                seq = h2.seq
                if h2.type==0 and h2.id==h.id and (seq in seqs): # 0: ICMP_ECHO_REPLY
                    t_elasped = (utime.ticks_us()-h2.timestamp) / 1000
                    ttl = ustruct.unpack('!B', resp_mv[8:9])[0] # time-to-live
                    n_recv += 1
                    if not quiet:
                        args = (len(resp), addr, seq, ttl, t_elasped)
                        print("{} bytes from {}: icmp_seq={}, ttl={}, time={:.1f} ms".format(*args))
                    seqs.remove(seq)
                    if len(seqs) == 0:
                        finish = True
                        break
            else:
                break
        if finish:
            break
        utime.sleep_ms(1)
        t += 1
    # close
    sock.close()
    ret = (n_trans, n_recv)
    if not quiet:
        args = (n_trans, n_recv)
        print("{} packets transmitted, {} packets received".format(*args))
    return (n_trans, n_recv)

########################################################
JSON_CONFIG_FILE = 'config.json'

config = {}
try:
    with open( JSON_CONFIG_FILE ) as json_file:
        config = json.load(json_file)
except OSError as ex:
    print('Cannot open JSON file')
    sys.exit(-1)

sta_if = network.WLAN( network.STA_IF )
sta_if.active( True )

sta_if.connect( config['ssid'], config['pwd'] )
while not sta_if.isconnected():
    print('waiting for Wi-Fi connection')
    time.sleep(1.0)

ping('8.8.8.8',count=5, timeout=5000, interval=200,
     quiet=False, size=44)
########################################################
```

ตัวอย่างข้อความเอาต์พุต

`PING 8.8.8.8 (8.8.8.8): 44 data bytes` \
`64 bytes from 8.8.8.8: icmp_seq=1, ttl=112, time=139.7 ms` \
`64 bytes from 8.8.8.8: icmp_seq=2, ttl=112, time=64.4 ms` \
`64 bytes from 8.8.8.8: icmp_seq=3, ttl=112, time=150.3 ms` \
`64 bytes from 8.8.8.8: icmp_seq=4, ttl=112, time=61.7 ms` \
`64 bytes from 8.8.8.8: icmp_seq=5, ttl=112, time=158.2 ms` \
`5 packets transmitted, 5 packets received`&#x20;

## กล่าวสรุป

เราได้เห็นตัวอย่างการเขียนโค้ดภาษาไมโครไพธอนสำหรับ **ESP32** เพื่อเชื่อมต่อกับระบบเครือข่ายไร้สายผ่าน **Wi-Fi** และสามารถใช้โพรโตคอลได้ อย่างเช่น **HTTP/HTTPS**&#x20;

{% hint style="info" %}
**เผยแพร่ภายใต้ลิขสิทธิ์**\
**Attribution-ShareAlike 4.0 International (**[**CC BY-SA 4.0**](https://creativecommons.org/licenses/by-sa/4.0/)**)**
{% endhint %}
