ESP-01S实现本地网页AP配网

一、前言

借由之前做过的STM32机器人项目,我们可以用相同的方式实现本地AP配网,只不过这一次我们使用的是ESP-01S模块。相较于之前使用的ESP32-CAM,ESP-01S的资源明显要小的多得多的多。虽然小多少我不知道,懒得搜,但是至少我们可以知道ESP-01S模块板载的Flash大小一般分为512k和1M大小的,01S一般是1M大小。所以思路就很清晰了,理论上只要想办法让Microdot在极小资源的ESP-01S上跑起来就能实现相同的效果了。

二、实践过程

好在MicroPython固件是可以直接到官方网站下载的,所以第一步我们可以直接到官网下载固件。

固件下载地址

根据自己的实际情况下载对应的固件,我下载使用的是1MB版本的固件。下载完成后将固件烧录到模块当中,我使用的是Mu Editor自带的固件下载功能,实际上也是使用esptool.py下载,只不过Mu Editor自带的有GUI界面下载比较方便。关于固件烧录的细节网上有详细教程,这里不做过多赘述。

固件烧录完成之后就可以准备开发了,我使用的开发工具是Arduino Lab for MicroPython。

Arduino Lab for MicroPython下载地址

电脑最好再准备一个串口助手以方便调试。

三、开发过程

可以直接从我原来的STM32机器人项目拉取原来在ESP32-CAM的代码,接下来就准备将文件上传至模块当中。

但是且慢!由于ESP-01S模块那点小的可怜的内存,因此我们需要下载下来的.py文件进行预编译成.mpy文件以节省模块的内存开销,至于如何预编译以及预编译的原因,可以查看MicroPython的官方文档。

MicroPython .mpy 文件官方文档说明

MicroPython官方提供了专门的工具为我们首先预编译,因此我们需要先去拉取MicroPython的Github官方代码仓库,其中readme文档中也有提到关于预编译工具,关于预编译的过程我选择在Ubuntu环境下进行。

将代码拉取到本地后,cd到mpy-cross目录中,在终端中使用make命令编译mpy-cross工具。

编译完成后,cd到build目录当中,将准备预编译成.mpy的.py文件置于文件夹当中,这里我选择的是用户Download文件夹。

接下来我们需要准备预编译的.py文件,如果只需要AP配网的功能,则仅需要以下文件:

/
├─ main.html
├─ main.py
├─ success_page.html
├─ wifi_info.html
└─ lib
   ├─ microdot.py
   └─ uasyncio
       ├─ core.py
       ├─ stream.py
       └─ __init__.py

由于ESP-01S资源紧张,原来的Microdot直接上传是无法运行的,终端会提示MemoryError,因此在预编译之前,需要对microdot.py文件进行修改。通过AI的帮助,将源码中的默认缓冲区大小进行调整,修改后的函数如下,将源文件中的对应函数直接替换即可:

def body_iter(self):
        if hasattr(self.body, '__anext__'):
            # 如果是异步生成器,直接返回
            return self.body

        response = self

        class iter:
            ITER_UNKNOWN = 0
            ITER_FILE_OBJ = 2
            ITER_NO_BODY = -1

            def __aiter__(self):
                if response.body:
                    self.i = self.ITER_UNKNOWN
                else:
                    self.i = self.ITER_NO_BODY
                return self

            async def __anext__(self):
                if self.i == self.ITER_NO_BODY:
                    await self.aclose()
                    raise StopAsyncIteration

                if self.i == self.ITER_UNKNOWN:
                    if hasattr(response.body, 'read'):
                        self.i = self.ITER_FILE_OBJ
                    else:
                        # 如果是字符串或字节串,返回一次就结束
                        self.i = self.ITER_NO_BODY
                        result = response.body
                        await self.aclose()
                        raise StopAsyncIteration(result)

                if self.i == self.ITER_FILE_OBJ:
                    # 自适应缓冲区大小
                    size = getattr(response, 'send_file_buffer_size', 128)
                    size = min(size, 256)  # 限制最大值

                    while size >= 32:
                        try:
                            buf = response.body.read(size)
                            if iscoroutine(buf):
                                buf = await buf
                            if not buf:
                                self.i = self.ITER_NO_BODY
                                await self.aclose()
                                raise StopAsyncIteration
                            return buf
                        except MemoryError:
                            print("MemoryError: reducing buffer size to", size // 2)
                            size = size // 2

                    print("MemoryError: all buffer sizes failed")
                    self.i = self.ITER_NO_BODY
                    await self.aclose()
                    raise StopAsyncIteration

            async def aclose(self):
                if hasattr(response.body, 'close'):
                    result = response.body.close()
                    if iscoroutine(result):
                        await result

        return iter()

同时将源码中开头的import asyncio修改成import uasyncio,以便于使用准备好的MicroPython uasyncio库。

其中uasyncio文件夹内的文件也来自MicroPython的Github官方代码仓库,在/extmod/asyncio目录下。

修改main.py文件,将原来负责发送命令以及传输图像的代码删除,保留AP配网的部分:

from lib.microdot import Microdot, send_file, redirect
import network
import time

app = Microdot()

@app.route('/')
def main(request):
  return send_file('main.html')

@app.route('/wifi_info')
def wifi_info(request):
  return send_file('wifi_info.html')

@app.get('/connect_wifi')
def success_page(request):
  WIFI_Connect()

# get wifi info
@app.post('/success_page')
def handle_form_submission(request):
    global ssid
    global password
    form_data = request.form
    ssid = form_data.get('ssid')
    password = form_data.get('password')
    print(ssid, password)
    # return redirect('/success_page')
    return send_file('success_page.html')

def AP_Connect():
    print("ap_mode")
    global AP_State
    AP_State = network.WLAN(network.AP_IF)
    AP_State.active(True)
    # AP_State.config(essid = 'Robots Config AP', authmode=network.AUTH_WPA_WPA2_PSK, password = '12345678')
    AP_State.config(essid = 'Robots Config AP')

    while AP_State.isconnected() != True:
        time.sleep(1)
        print("device_no_connect")
    print("have_device_connected")
    time.sleep(1)

def WIFI_Connect():
    # AP_State.active(False)
    # 连接wifi
    sta_if = network.WLAN(network.STA_IF)
    if not sta_if.isconnected():
        print('connecting to network...')
        sta_if.active(True)
        sta_if.connect(ssid, password)
        # sta_if.connect("RM2100_2.4G", "21802366")
        while not sta_if.isconnected():
            pass
    print("connect!")

if __name__ == '__main__':
  AP_Connect()
  app.run(port=80)

至此文件修改完成。

预编译的示例命令如下,该命令只针对单个.py文件,依次对需要预编译的文件都执行一遍:

./mpy-cross ~/Downloads/microdot.py

将预编译完成的文件按照上文提到的文件路径一一上传至ESP-01S当中,使用方式和ESP32-CAM相同:

  • 1、连接AP热点
  • 2、浏览器访问192.168.4.1
  • 3、输入wifi名称和密码
  • 4、点击提交并确认连接

至此完成整个本地网页AP配网流程。