MicroPython: Wi-Fi Менеджер для ESP32 (совместимо с ESP8266)

В этом руководстве мы покажем, как использовать Wi-Fi Manager на ESP32 с прошивкой MicroPython. Wi-Fi Manager позволяет подключать ESP32 к различным точкам доступа (разным сетям) без необходимости жёстко прописывать учётные данные и загружать новый код на плату.

MicroPython: Wi-Fi Manager с ESP32 ESP8266

Это руководство также полностью совместимо с платой ESP8266. Однако, поскольку библиотека Wi-Fi Manager использует довольно много памяти, вы можете столкнуться с ошибкой памяти при сохранении скрипта на плату. По нашему опыту, перезагрузка платы после загрузки скрипта устраняет ошибку, и проект начинает работать после этого. Мы рекомендуем использовать ESP32, но вы также можете продолжить это руководство, используя плату ESP8266.

Как работает Wi-Fi Manager

С Wi-Fi Manager вам больше не нужно жёстко прописывать учётные данные сети (SSID и пароль). ESP32 настроит точку доступа (Access Point), которую вы сможете использовать для настройки сетевых учётных данных, или автоматически подключится к известной сохранённой сети.

Настройка Wi-Fi клиента через WiFi Manager MicroPython

Вот как работает процесс:

  • При первой загрузке ESP32 настраивается как точка доступа (Access Point);

  • Вы можете подключиться к этой точке доступа, установив соединение с сетью WiFiManager и перейдя на IP-адрес 192.168.4.1;

  • Откроется веб-страница, позволяющая выбрать и настроить сеть;

  • ESP32 сохраняет учётные данные сети, чтобы позже подключиться к этой сети (режим Station);

  • После установки нового SSID и пароля ESP32 перезагружается, переходит в режим Station и пытается подключиться к ранее сохранённой сети;

  • Если соединение установлено, процесс завершён успешно. В противном случае ESP32 снова будет настроен как точка доступа для ввода новых учётных данных сети.

Для настройки Wi-Fi Manager на ESP32 с использованием MicroPython мы будем использовать библиотеку WiFiManager от tayfunulu. На странице библиотеки на GitHub вы можете найти следующую диаграмму, которая показывает обзор того, как всё работает.

Диаграмма Wi-Fi Manager для ESP с MicroPython

Источник изображения

Необходимые условия

Для выполнения этого руководства вам нужна прошивка MicroPython, установленная на вашу плату ESP. Вам также нужна IDE для написания и загрузки кода на плату. Мы предлагаем использовать Thonny IDE или uPyCraft IDE:

Необходимые компоненты

Для этого руководства вам понадобится ESP32 (или плата ESP8266):

Библиотека WiFiManager для MicroPython

Библиотека для настройки Wi-Fi Manager на ESP32 не входит в стандартную библиотеку MicroPython по умолчанию. Поэтому вам нужно загрузить следующую библиотеку на вашу плату ESP (сохраните её с точным именем wifimgr.py).

import network
import socket
import ure
import time

ap_ssid = "WifiManager"
ap_password = "tayfunulu"
ap_authmode = 3  # WPA2

NETWORK_PROFILES = 'wifi.dat'

wlan_ap = network.WLAN(network.AP_IF)
wlan_sta = network.WLAN(network.STA_IF)

server_socket = None

def get_connection():
    """return a working WLAN(STA_IF) instance or None"""

    # First check if there already is any connection:
    if wlan_sta.isconnected():
        return wlan_sta

    connected = False
    try:
        # ESP connecting to WiFi takes time, wait a bit and try again:
        time.sleep(3)
        if wlan_sta.isconnected():
            return wlan_sta

        # Read known network profiles from file
        profiles = read_profiles()

        # Search WiFis in range
        wlan_sta.active(True)
        networks = wlan_sta.scan()

        AUTHMODE = {0: "open", 1: "WEP", 2: "WPA-PSK", 3: "WPA2-PSK", 4: "WPA/WPA2-PSK"}
        for ssid, bssid, channel, rssi, authmode, hidden in sorted(networks, key=lambda x: x[3], reverse=True):
            ssid = ssid.decode('utf-8')
            encrypted = authmode > 0
            print("ssid: %s chan: %d rssi: %d authmode: %s" % (ssid, channel, rssi, AUTHMODE.get(authmode, '?')))
            if encrypted:
                if ssid in profiles:
                    password = profiles[ssid]
                    connected = do_connect(ssid, password)
                else:
                    print("skipping unknown encrypted network")
            else:  # open
                connected = do_connect(ssid, None)
            if connected:
                break

    except OSError as e:
        print("exception", str(e))

    # start web server for connection manager:
    if not connected:
        connected = start()

    return wlan_sta if connected else None

def read_profiles():
    with open(NETWORK_PROFILES) as f:
        lines = f.readlines()
    profiles = {}
    for line in lines:
        ssid, password = line.strip("\n").split(";")
        profiles[ssid] = password
    return profiles

def write_profiles(profiles):
    lines = []
    for ssid, password in profiles.items():
        lines.append("%s;%s\n" % (ssid, password))
    with open(NETWORK_PROFILES, "w") as f:
        f.write(''.join(lines))

def do_connect(ssid, password):
    wlan_sta.active(True)
    if wlan_sta.isconnected():
        return None
    print('Trying to connect to %s...' % ssid)
    wlan_sta.connect(ssid, password)
    for retry in range(200):
        connected = wlan_sta.isconnected()
        if connected:
            break
        time.sleep(0.1)
        print('.', end='')
    if connected:
        print('\nConnected. Network config: ', wlan_sta.ifconfig())

    else:
        print('\nFailed. Not Connected to: ' + ssid)
    return connected

def send_header(client, status_code=200, content_length=None ):
    client.sendall("HTTP/1.0 {} OK\r\n".format(status_code))
    client.sendall("Content-Type: text/html\r\n")
    if content_length is not None:
      client.sendall("Content-Length: {}\r\n".format(content_length))
    client.sendall("\r\n")

def send_response(client, payload, status_code=200):
    content_length = len(payload)
    send_header(client, status_code, content_length)
    if content_length > 0:
        client.sendall(payload)
    client.close()

def handle_root(client):
    wlan_sta.active(True)
    ssids = sorted(ssid.decode('utf-8') for ssid, *_ in wlan_sta.scan())
    send_header(client)
    client.sendall("""\
        <html>
            <h1 style="color: #5e9ca0; text-align: center;">
                <span style="color: #ff0000;">
                    Wi-Fi Client Setup
                </span>
            </h1>
            <form action="configure" method="post">
                <table style="margin-left: auto; margin-right: auto;">
                    <tbody>
    """)
    while len(ssids):
        ssid = ssids.pop(0)
        client.sendall("""\
                        <tr>
                            <td colspan="2">
                                <input type="radio" name="ssid" value="{0}" />{0}
                            </td>
                        </tr>
        """.format(ssid))
    client.sendall("""\
                        <tr>
                            <td>Password:</td>
                            <td><input name="password" type="password" /></td>
                        </tr>
                    </tbody>
                </table>
                <p style="text-align: center;">
                    <input type="submit" value="Submit" />
                </p>
            </form>
            <p>&nbsp;</p>
            <hr />
            <h5>
                <span style="color: #ff0000;">
                    Your ssid and password information will be saved into the
                    "%(filename)s" file in your ESP module for future usage.
                    Be careful about security!
                </span>
            </h5>
            <hr />
            <h2 style="color: #2e6c80;">
                Some useful infos:
            </h2>
            <ul>
                <li>
                    Original code from <a href="https://github.com/cpopp/MicroPythonSamples"
                        target="_blank" rel="noopener">cpopp/MicroPythonSamples</a>.
                </li>
                <li>
                    This code available at <a href="https://github.com/tayfunulu/WiFiManager"
                        target="_blank" rel="noopener">tayfunulu/WiFiManager</a>.
                </li>
            </ul>
        </html>
    """ % dict(filename=NETWORK_PROFILES))
    client.close()

def handle_configure(client, request):
    match = ure.search("ssid=([^&]*)&password=(.*)", request)

    if match is None:
        send_response(client, "Parameters not found", status_code=400)
        return False
    # version 1.9 compatibility
    try:
        ssid = match.group(1).decode("utf-8").replace("%3F", "?").replace("%21", "!")
        password = match.group(2).decode("utf-8").replace("%3F", "?").replace("%21", "!")
    except Exception:
        ssid = match.group(1).replace("%3F", "?").replace("%21", "!")
        password = match.group(2).replace("%3F", "?").replace("%21", "!")

    if len(ssid) == 0:
        send_response(client, "SSID must be provided", status_code=400)
        return False

    if do_connect(ssid, password):
        response = """\
            <html>
                <center>
                    <br><br>
                    <h1 style="color: #5e9ca0; text-align: center;">
                        <span style="color: #ff0000;">
                            ESP successfully connected to WiFi network %(ssid)s.
                        </span>
                    </h1>
                    <br><br>
                </center>
            </html>
        """ % dict(ssid=ssid)
        send_response(client, response)
        time.sleep(1)
        wlan_ap.active(False)
        try:
            profiles = read_profiles()
        except OSError:
            profiles = {}
        profiles[ssid] = password
        write_profiles(profiles)

        time.sleep(5)

        return True
    else:
        response = """\
            <html>
                <center>
                    <h1 style="color: #5e9ca0; text-align: center;">
                        <span style="color: #ff0000;">
                            ESP could not connect to WiFi network %(ssid)s.
                        </span>
                    </h1>
                    <br><br>
                    <form>
                        <input type="button" value="Go back!" onclick="history.back()"></input>
                    </form>
                </center>
            </html>
        """ % dict(ssid=ssid)
        send_response(client, response)
        return False

def handle_not_found(client, url):
    send_response(client, "Path not found: {}".format(url), status_code=404)

def stop():
    global server_socket

    if server_socket:
        server_socket.close()
        server_socket = None

def start(port=80):
    global server_socket

    addr = socket.getaddrinfo('0.0.0.0', port)[0][-1]

    stop()

    wlan_sta.active(True)
    wlan_ap.active(True)

    wlan_ap.config(essid=ap_ssid, password=ap_password)

    server_socket = socket.socket()
    server_socket.bind(addr)
    server_socket.listen(1)

    print('Connect to WiFi ssid ' + ap_ssid + ', default password: ' + ap_password)
    print('and access the ESP via your favorite web browser at 192.168.4.1.')
    print('Listening on:', addr)

    while True:
        if wlan_sta.isconnected():
            wlan_ap.active(False)
            return True

        client, addr = server_socket.accept()
        print('client connected from', addr)
        try:
            client.settimeout(5.0)

            request = b""
            try:
                while "\r\n\r\n" not in request:
                    request += client.recv(512)
            except OSError:
                pass

            # Handle form data from Safari on macOS and iOS; it sends \r\n\r\nssid=<ssid>&password=<password>
            try:
                request += client.recv(1024)
                print("Received form data after \\r\\n\\r\\n(i.e. from Safari on macOS or iOS)")
            except OSError:
                pass

            print("Request is: {}".format(request))
            if "HTTP" not in request:  # skip invalid requests
                continue

            # version 1.9 compatibility
            try:
                url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/")
            except Exception:
                url = ure.search("(?:GET|POST) /(.*?)(?:\\?.*?)? HTTP", request).group(1).rstrip("/")
            print("URL is {}".format(url))

            if url == "":
                handle_root(client)
            elif url == "configure":
                handle_configure(client, request)
            else:
                handle_not_found(client, url)

        finally:
            client.close()

Посмотреть исходный код

Следуйте инструкциям для IDE, которую вы используете:

    1. Загрузка библиотеки WiFiManager с помощью uPyCraft IDE

    1. Загрузка библиотеки WiFiManager с помощью Thonny IDE

A. Загрузка библиотеки WiFiManager с помощью uPyCraft IDE

В этом разделе показано, как загрузить библиотеку с помощью uPyCraft IDE. Если вы используете Thonny IDE, читайте следующий раздел.

1. Создайте новый файл, нажав кнопку New File.

2. Скопируйте код библиотеки WiFiManager в этот файл. Код библиотеки WiFiManager можно скопировать отсюда.

3. После копирования кода сохраните файл, нажав кнопку Save.

Загрузка библиотеки wifimgr с помощью uPyCraft IDE MicroPython

4. Назовите этот новый файл «wifimgr.py» и нажмите ok.

Сохранение библиотеки wifimgr.py в uPyCraft IDE

5. Нажмите кнопку Download and Run.

Кнопка Download and Run в uPyCraft IDE

Файл должен быть сохранён в папке устройства с именем «wifimgr.py», как выделено на следующем рисунке.

Библиотека wifimgr успешно загружена на ESP32

Теперь вы можете использовать функционал библиотеки в своём коде, импортировав библиотеку.

B. Загрузка библиотеки WiFiManager с помощью Thonny IDE

Если вы используете Thonny IDE, выполните следующие шаги:

1. Скопируйте код библиотеки в новый файл. Код библиотеки WiFiManager можно скопировать отсюда.

2. Сохраните этот файл как wifimgr.py.

3. Перейдите в Device > Upload current script with the current name.

Загрузка библиотеки wifimgr с помощью Thonny IDE MicroPython

Вот и всё. Библиотека загружена на вашу плату. Чтобы убедиться, что загрузка прошла успешно, в Shell введите:

%lsdevice

Команда должна вернуть файлы, сохранённые на вашей плате. Одним из них должен быть файл wifimgr.py.

Библиотека wifimgr.py успешно сохранена на ESP32 MicroPython

После загрузки библиотеки на вашу плату вы можете использовать функционал библиотеки в своём коде, импортировав библиотеку.

Код – Настройка Wi-Fi Manager на ESP32

Следующий код реализует Wi-Fi Manager на ESP32. Мы добавим возможности Wi-Fi Manager к предыдущему проекту веб-сервера на MicroPython. К концу руководства вы сможете реализовать Wi-Fi Manager в своих собственных проектах.

Создайте файл main.py и скопируйте следующий код.

# Complete project details at https://RandomNerdTutorials.com

import wifimgr
from time import sleep
import machine

try:
  import usocket as socket
except:
  import socket

led = machine.Pin(2, machine.Pin.OUT)

wlan = wifimgr.get_connection()
if wlan is None:
    print("Could not initialize the network connection.")
    while True:
        pass  # you shall not pass :D

# Main Code goes here, wlan is a working network.WLAN(STA_IF) instance.
print("ESP OK")

def web_page():
  if led.value() == 1:
    gpio_state="ON"
  else:
    gpio_state="OFF"

  html = """<html><head> <title>ESP Web Server</title> <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" href="data:,"> <style>html{font-family: Helvetica; display:inline-block; margin: 0px auto; text-align: center;}
  h1{color: #0F3376; padding: 2vh;}p{font-size: 1.5rem;}.button{display: inline-block; background-color: #e7bd3b; border: none;
  border-radius: 4px; color: white; padding: 16px 40px; text-decoration: none; font-size: 30px; margin: 2px; cursor: pointer;}
  .button2{background-color: #4286f4;}</style></head><body> <h1>ESP Web Server</h1>
  <p>GPIO state: <strong>""" + gpio_state + """</strong></p><p><a href="/?led=on"><button class="button">ON</button></a></p>
  <p><a href="/?led=off"><button class="button button2">OFF</button></a></p></body></html>"""
  return html

try:
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  s.bind(('', 80))
  s.listen(5)
except OSError as e:
  machine.reset()

while True:
  try:
    if gc.mem_free() < 102000:
      gc.collect()
    conn, addr = s.accept()
    conn.settimeout(3.0)
    print('Got a connection from %s' % str(addr))
    request = conn.recv(1024)
    conn.settimeout(None)
    request = str(request)
    print('Content = %s' % request)
    led_on = request.find('/?led=on')
    led_off = request.find('/?led=off')
    if led_on == 6:
      print('LED ON')
      led.value(1)
    if led_off == 6:
      print('LED OFF')
      led.value(0)
    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.sendall(response)
    conn.close()
  except OSError as e:
    conn.close()
    print('Connection closed')

Посмотреть исходный код

Как работает код

Этот код основан на предыдущем проекте веб-сервера ESP32/ESP8266 на MicroPython. Мы лишь внесли несколько изменений для добавления Wi-Fi Manager.

Чтобы добавить Wi-Fi Manager, нужно импортировать библиотеку, которую вы ранее загрузили на плату.

import wifimgr

Следующие строки кода обрабатывают Wi-Fi Manager за вас:

wlan = wifimgr.get_connection()
if wlan is None:
    print("Could not initialize the network connection.")
    while True:
        pass  # you shall not pass :D

wlan – это рабочий экземпляр network.WLAN(STA_IF), который инициализируется библиотекой. Поэтому вам не нужно включать код для настройки ESP32 в режим Station.

Когда ESP32 впервые настраивается как точка доступа, он оставляет открытый сокет, что приводит к ошибке и аварийному завершению работы ESP32. Чтобы этого не произошло, мы инициализируем и привязываем сокет внутри конструкции try и except.

try:
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  s.bind(('', 80))
  s.listen(5)
except OSError as e:
  machine.reset()

В случае, если остался открытый сокет, мы получим OS error и сбросим ESP32 с помощью machine.reset(). Это «забудет» открытый сокет.

Когда код запускается второй раз, учётные данные сети уже сохранены, поэтому ESP32 не настраивается как точка доступа, проблем с открытыми сокетами нет, и код выполняется без ошибок.

Тестирование WiFiManager

Загрузите файл main.py на ESP32. После этого нажмите кнопку RST (EN) на плате ESP32, чтобы запустить программу.

В Python Shell вы должны увидеть похожее сообщение.

Запуск WiFiManager на ESP32 MicroPython

Это означает, что ESP32 был успешно настроен как точка доступа. Теперь вы можете подключиться к этой точке доступа, чтобы выбрать свою сеть и ввести учётные данные. Для этого выполните следующие шаги.

На вашем компьютере или смартфоне откройте настройки Wi-Fi и подключитесь к сети WifiManager.

Подключение к сети WiFiManager ESP32 MicroPython

Пароль – tayfunulu. Вы можете изменить SSID и пароль по умолчанию в коде библиотеки.

Ввод пароля сети WiFiManager

После успешного подключения к сети WiFiManager откройте браузер и введите 192.168.4.1. Должна загрузиться следующая страница:

Выбор сети Wi-Fi -- WiFiManager MicroPython ESP32

Выберите свою сеть, введите пароль и нажмите Submit. Через несколько секунд вы должны получить сообщение об успехе.

ESP32 успешно подключён к сети Wi-Fi -- WiFiManager

Это сообщение означает, что ваш ESP32 настроен как Wi-Fi станция и подключён к вашей локальной сети. Теперь, чтобы получить доступ к ESP32, вернитесь в настройки Wi-Fi на компьютере или смартфоне и подключитесь к своей сети.

В Python Shell должен быть напечатан IP-адрес ESP32.

IP-адрес ESP32 Station -- WiFiManager MicroPython

Примечание: в реальных условиях у вас, вероятно, не будет доступа к Python Shell. В такой ситуации мы рекомендуем выводить IP-адрес на OLED-дисплей.

Откройте браузер и введите этот IP-адрес. Вы получите доступ к следующему веб-серверу и сможете управлять GPIO ESP32.

Веб-сервер ESP32 управление выходами MicroPython -- WiFiManager

Заключение

С библиотекой WiFiManager вам больше не нужно жёстко прописывать учётные данные сети. ESP32 настраивает точку доступа, которая отображает доступные сети Wi-Fi. Вам нужно лишь выбрать свою сеть и ввести пароль, чтобы настроить ESP32 как Wi-Fi станцию.

Надеемся, что это руководство было полезным. Вам также могут быть интересны: