< All Topics
Print

【通訊】WiFi AP模式 | dual2s-mini | MPY-LLM

簡介:

AP模式(Access Point Mode)即成為一個無線接入點,使其他Wi-Fi設備可以連接並通過該設備進行通信。這種模式適合應用於無線網絡控制、物聯網設備等場景中。以下是AP模式的原理與設定方式:

AP模式的原理

  1. 無線網絡模式:在AP模式下,dual2s-mini會自成為無線網絡的「熱點」,並提供SSID(服務集標識符)和密碼,以便其他設備可以掃描並連接到它。
  2. IP分配:當ESP32/ESP8266作為無線接入點時,它會自動分配一個IP地址範圍(通常為 192.168.4.x),並將自己設為網關(默認IP為 192.168.4.1)。連接的設備會在此範圍內自動獲得IP地址,形成一個局部網絡。
  3. 本地通信:一旦設備連接到ESP32/ESP8266的AP網絡,便可以通過這個IP範圍進行通信。這樣ESP32/ESP8266可以充當HTTP伺服器或WebSocket伺服器來處理請求,例如從網頁端發送命令,控制LED或其他外接設備。
  4. 安全性:AP模式可以設置Wi-Fi密碼和加密方式(例如WPA2),確保只有授權設備能夠連接。

MPY AP模式基本範例:

1.啟動AP模式

使用 network.WLAN(network.AP_IF) 來啟用AP模式。以下是基本的設置範例:

import network
import socket

# 創建 AP 物件
ap = network.WLAN(network.AP_IF)
ap.active(True)  # 啟用 AP 模式

# 設定 AP 的 SSID 和密碼
ap.config(essid="dual2s_AP", password="12345678", authmode=3, channel=6, max_clients=5, hidden=False)

# 設定靜態 IP 位址
new_ip = "192.168.10.1"  # 新的靜態 IP 位址
netmask = "255.255.255.0"  # 子網掩碼
gateway = "192.168.10.1"   # 閘道器
dns = "8.8.8.8"            # DNS 伺服器
ap.ifconfig((new_ip, netmask, gateway, dns))

# 輸出設定結果
print("SSID:", ap.config("essid"))
print("認證模式:", ap.config("authmode"))
print("頻道:", ap.config("channel"))
print("最大客戶端數量:", ap.config("max_clients"))
print("IP 地址:", ap.ifconfig()[0])

2. 建立簡單的Web伺服器

在AP模式下,dual2s-mini可作為伺服器來處理網頁或其他控制請求。這樣可以使用瀏覽器直接訪問dual2s-mini的IP地址來控制它。

import socket

def serve():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind(('', 80))
    s.listen(5)
    while True:
        conn, addr = s.accept()
        request = conn.recv(1024)
        print('Received request from %s' % str(addr))
        
        # 簡單的HTML回應
        response = """<!DOCTYPE html>
        <html><body><h1>dual2s-mini AP Mode!</h1></body></html>"""
        
        conn.send('HTTP/1.1 200 OK\n')
        conn.send('Content-Type: text/html\n')
        conn.send('Connection: close\n\n')
        conn.sendall(response)
        conn.close()

# 啟動伺服器
serve()

3. 建立控制ws2812b的AP模式

import network
import socket

from machine import Pin
import neopixel

# 創建 AP 物件
ap = network.WLAN(network.AP_IF)
ap.active(True)  # 啟用 AP 模式

# 設定 AP 的 SSID 和密碼
ap.config(essid="dual2s_AP", password="12345678", authmode=3, channel=6, max_clients=5, hidden=False)

# 設定靜態 IP 位址
# new_ip, netmask, gateway, dns
ap.ifconfig(("192.168.10.1", "255.255.255.0", "192.168.10.1", "8.8.8.8"))

# 輸出設定結果
print("SSID:", ap.config("essid"))
print("IP 地址:", ap.ifconfig()[0])

#----------------------------------------------------------------------------------
# HTML頁面內容
html = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>【 dual2s-mini 】</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 0;
            padding: 0;
            background-color: #f0f0f0;
        }
        .button {
            display: inline-block;
            margin: 20px;
            padding: 20px 40px;
            font-size: 20px;
            font-weight: bold;
            color: white;
            border: none;
            border-radius: 10px;
            cursor: pointer;
        }
        .on {
            background-color: #4CAF50; /* 綠色 */
        }
        .off {
            background-color: #f44336; /* 紅色 */
        }
        .button:hover {
            opacity: 0.9;
        }
    </style>
</head>
<body>
    <h1>【 dual2s-mini 】</h1>
    <h1>全彩LED ws2812b Control </h1>
    <button class="button on" onclick="sendRequest('/on')">Turn ON</button>
    <button class="button off" onclick="sendRequest('/off')">Turn OFF</button>
    <script>
        function sendRequest(path) {
            fetch(path).then(response => {
                console.log('Request sent to ' + path);
            }).catch(error => {
                console.error('Error:', error);
            });
        }
    </script>
</body>
</html>
"""
#-------------------------------------------------------------------

# 設置WS2812B
led_pin = Pin(2, Pin.OUT)
np = neopixel.NeoPixel(led_pin, 1)  # 假設只有1顆WS2812B LED

def set_led(color):
    """
    控制WS2812B顏色
    :param color: (R, G, B) 顏色值
    """
    np[0] = color
    np.write()
#----------------------------------------------------------------------------------   

# 建立HTTP伺服器
def start_server():
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
    s = socket.socket()
    s.bind(addr)
    s.listen(5)
    print('Listening on', addr)

    while True:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024).decode('utf-8')
        print('Request:', request)

        # 處理GET請求
        if 'GET /on' in request:
            set_led((0, 255, 0))  # 綠色
        elif 'GET /off' in request:
            set_led((0, 0, 0))  # 關閉LED

        # 回傳HTML
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(html)
        cl.close()

# 啟動伺服器
start_server()


4. Get命令控制超音波與全彩LED:

import network
import socket
import json
import time
from machine import Pin, time_pulse_us
import neopixel

# 創建 AP 物件
ap = network.WLAN(network.AP_IF)
ap.active(True)  # 啟用 AP 模式

# 設定 AP 的 SSID 和密碼
ap.config(essid="dual2s_AP", password="12345678", authmode=3, channel=6, max_clients=5, hidden=False)

# 設定靜態 IP 位址
# new_ip, netmask, gateway, dns
ap.ifconfig(("192.168.10.1", "255.255.255.0", "192.168.10.1", "8.8.8.8"))

# 輸出設定結果
print("SSID:", ap.config("essid"))
print("IP 地址:", ap.ifconfig()[0])

#----------------------------------------------------------------------------------
# HTML頁面內容
html = """
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>【 dual2s-mini 】</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            margin: 0;
            padding: 0;
            background-color: #f0f0f0;
        }
        .button {
            display: inline-block;
            margin: 20px;
            padding: 20px 40px;
            font-size: 20px;
            font-weight: bold;
            color: white;
            border: none;
            border-radius: 10px;
            cursor: pointer;
        }
        .on {
            background-color: #4CAF50; /* 綠色 */
        }
        .off {
            background-color: #f44336; /* 紅色 */
        }
        .usc {
            background-color: #f44300; /*  */    
        }
        .button:hover {
            opacity: 0.9;
        }
    </style>
</head>
<body>
    <h1>【 dual2s-mini 】</h1>
    
    <h1>Ultrasonic Distance</h1>
    <p id="distance">Distance: -- cm</p>
    <button class="button usc" onclick="measureDistance()">Measure</button>

    <h1>全彩LED ws2812b Control </h1>
    <button class="button on" onclick="sendRequest('/on')">Turn ON</button>
    <button class="button off" onclick="sendRequest('/off')">Turn OFF</button>
    <script>
    
        function sendRequest(path) {
            fetch(path).then(response => {
                console.log('Request sent to ' + path);
            }).catch(error => {
                console.error('Error:', error);
            });
        }
        
        async function measureDistance() {
            const response = await fetch('/measure');
            const data = await response.json();
            const distanceElement = document.getElementById('distance');
            if (data.distance >= 0) {
                distanceElement.textContent = `Distance: ${data.distance} cm`;
            } else {
                distanceElement.textContent = "Distance: Measurement Error";
            }
        }

    </script>
</body>
</html>
"""
#-------------------------------------------------------------------
TRIG = Pin(27, Pin.OUT)
ECHO = Pin(13, Pin.IN)
def measure_distance():
    """
    啟動超聲波模組,測量距離。
    回傳距離(單位:厘米)。
    """
    TRIG.off()
    time.sleep_us(2)
    TRIG.on()
    time.sleep_us(10)
    TRIG.off()

    # 偵測ECHO的高電平時間
    duration = time_pulse_us(ECHO, 1, 30000)  # 最長等待30ms
    if duration < 0:
        return -1  # 超時

    # 計算距離(單位:cm)
    distance = (duration / 2) * 0.0343
    return round(distance, 2)


# 設置WS2812B
led_pin = Pin(2, Pin.OUT)
np = neopixel.NeoPixel(led_pin, 1)  # 假設只有1顆WS2812B LED

def set_led(color):
    """
    控制WS2812B顏色
    :param color: (R, G, B) 顏色值
    """
    np[0] = color
    np.write()
#-------------------------------------------------------------------

# 建立HTTP伺服器
def start_server():
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
    s = socket.socket()
    s.bind(addr)
    s.listen(5)
    print('Listening on', addr)

    while True:
        cl, addr = s.accept()
        print('Client connected from', addr)
        request = cl.recv(1024).decode('utf-8')
        print('Request:', request)

        # 處理GET請求
        if 'GET /on' in request:
            set_led((0, 255, 0))  # 綠色
            cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
            cl.send(html)
        elif 'GET /off' in request:
            set_led((0, 0, 0))  # 關閉LED
            cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
            cl.send(html)
        elif 'GET /measure' in request:
            distance = measure_distance()
            response = json.dumps({'distance': distance})
            print('nick : ' + response) 
            cl.send('HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n')
            cl.send(response)    
        else:
            cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
            cl.send(html)            
        

        cl.close()

# 啟動伺服器
start_server()



6. 建立控制按鍵

以下是一個完整的MicroPython程式,展示如何在ESP32的AP模式中,建立HTML網頁介面,包含以下功能:

  1. 按鈕觸發 電壓偵測(GPIO36)。
  2. 按鈕觸發 超音波模組測距(Trig=GPIO27,Echo=GPIO13)。
  3. 控制 全彩LED (WS2812B,GPIO2),可透過RGB文字框輸入顏色值,按下按鈕後生效。
  4. 啟動與停止按鈕,控制所有功能。
import network
import socket
from machine import Pin, ADC, time_pulse_us
import neopixel
import time
import json

# 硬體初始化
# 電壓偵測
adc = ADC(Pin(36))
adc.width(ADC.WIDTH_12BIT)  # 設置ADC解析度為12位
adc.atten(ADC.ATTN_11DB)    # 電壓範圍0-3.6V

# 超音波模組
TRIG = Pin(27, Pin.OUT)
ECHO = Pin(13, Pin.IN)

def measure_distance():
    TRIG.off()
    time.sleep_us(2)
    TRIG.on()
    time.sleep_us(10)
    TRIG.off()
    duration = time_pulse_us(ECHO, 1, 30000)
    if duration < 0:
        return -1
    distance = (duration / 2) * 0.0343  # 計算距離 (cm)
    return round(distance, 2)

# 全彩LED
LED_PIN = Pin(2, Pin.OUT)
np = neopixel.NeoPixel(LED_PIN, 1)

def set_led(color):
    np[0] = color
    np.write()

def turn_off_led():
    np[0] = (0, 0, 0)
    np.write()

# WiFi AP模式啟動
SSID = "ESP32_AP"
PASSWORD = "12345678"

ap = network.WLAN(network.AP_IF)
ap.active(True)
ap.config(essid=SSID, password=PASSWORD)
print("AP Mode: {} - {}".format(SSID, ap.ifconfig()[0]))

# HTML頁面
html = """
<!DOCTYPE html>
<html>
<head>
    <title>ESP32 Control Panel</title>
    <style>
        body { font-family: Arial, sans-serif; text-align: center; background-color: #f0f0f0; }
        button { margin: 10px; padding: 15px 30px; font-size: 18px; cursor: pointer; border: none; border-radius: 8px; }
        button.start { background-color: #4CAF50; color: white; }
        button.stop { background-color: #f44336; color: white; }
        button.action { background-color: #2196F3; color: white; }
        input { margin: 10px; padding: 10px; font-size: 16px; width: 80px; text-align: center; }
    </style>
</head>
<body>
    <h1>ESP32 Control Panel</h1>
    <h2>Voltage Detection</h2>
    <button class="action" onclick="fetchData('/voltage')">Get Voltage</button>
    <p id="voltage">Voltage: -- V</p>
    
    <h2>Ultrasonic Distance</h2>
    <button class="action" onclick="fetchData('/distance')">Get Distance</button>
    <p id="distance">Distance: -- cm</p>
    
    <h2>RGB LED Control</h2>
    <input type="text" id="r" placeholder="R" maxlength="3"> 
    <input type="text" id="g" placeholder="G" maxlength="3"> 
    <input type="text" id="b" placeholder="B" maxlength="3">
    <button class="action" onclick="setColor()">Set Color</button>
    <button class="stop" onclick="fetchData('/led?color=0,0,0')">Turn Off LED</button>
    
    <h2>Global Control</h2>
    <button class="start" onclick="fetchData('/start')">Start</button>
    <button class="stop" onclick="fetchData('/stop')">Stop</button>
    
    <script>
        async function fetchData(path) {
            const response = await fetch(path);
            const data = await response.json();
            if (path.includes('/voltage')) {
                document.getElementById('voltage').innerText = `Voltage: ${data.voltage} V`;
            } else if (path.includes('/distance')) {
                document.getElementById('distance').innerText = `Distance: ${data.distance} cm`;
            }
        }

        async function setColor() {
            const r = document.getElementById('r').value || 0;
            const g = document.getElementById('g').value || 0;
            const b = document.getElementById('b').value || 0;
            await fetch(`/led?color=${r},${g},${b}`);
        }
    </script>
</body>
</html>
"""

# 啟動HTTP伺服器
def start_server():
    addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
    s = socket.socket()
    s.bind(addr)
    s.listen(5)
    print('Listening on', addr)

    running = True

    while running:
        cl, addr = s.accept()
        request = cl.recv(1024).decode('utf-8')
        print("Request:", request)

        # 分析請求
        if 'GET /voltage' in request:
            voltage = round(adc.read() * 3.6 / 4095, 2)
            response = json.dumps({'voltage': voltage})
            cl.send('HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n' + response)

        elif 'GET /distance' in request:
            distance = measure_distance()
            response = json.dumps({'distance': distance})
            cl.send('HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n' + response)

        elif 'GET /led' in request:
            try:
                color_str = request.split('GET /led?color=')[1].split(' ')[0]
                r, g, b = map(int, color_str.split(','))
                set_led((r, g, b))
            except:
                turn_off_led()
            cl.send('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nOK')

        elif 'GET /start' in request:
            running = True
            cl.send('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nStarted')

        elif 'GET /stop' in request:
            running = False
            turn_off_led()
            cl.send('HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nStopped')

        else:
            cl.send('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n' + html)

        cl.close()

# 啟動伺服器
start_server()
Table of Contents