esp32自行编译micropython固件并支持smartconfig方法 以及代码保护

micropython 自己编译固件有几个好处

  • 只加入自己需要的模块降低硬件资源占用
  • .py文件可以修改中中间码,并烧录到固件内部,
    • 可以方便量产
    • 可一定程度保护代码
    • 更加省略了编译过程节省内存

我这里 只记录 添加 smartconfig的方法,并以S2 mini为例,其他esp32 同理。
简中网络上,几乎没有相关资料,有一些资料也是错的或无法使用。所以这里单独记录分享一下。 你可以直接下载 我编译好的
https://github.com/joyanhui/file.leiyanhui.com/blob/main/esp32/

准备

  • linux系统,我这里是pve lxc运行的ubuntu22.04
  • 科学工具 因为需要拉github
  • 基本的linux基础

环境配置 和 依赖

修改到国内源,这个自己处理

1
2
3
4
5
6
7
8
9
apt install git wget curl make gcc cmake rsync
# ppa安装pythone 3.11  Ubuntu自带的版本太低
apt install software-properties-common
add-apt-repository ppa:deadsnakes/ppa
apt install python3.11 python-is-python3
# pip3
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py --force-reinstall
pip install mpy-cross-v6

克隆代码

1
mkdir mpy-bin && cd mpy-bin

esp-idf

注意 不同的esp32平台 micropython支持的esp-idf版本不同,详情参考 https://github.com/micropython/micropython/tree/master/ports/esp32#readme
linux小白 一步一步操作,注意里面的警告信息

1
2
3
4
git clone -b v4.4.5 --recursive https://github.com/espressif/esp-idf.git esp-idf-v4.4.5
cd esp-idf-v4.4.5/
git checkout v4.4.5
git submodule update --init --recursive

处理环境变量

1
2
./install.sh 
source export.sh

micropython

本文基于 1.20 版本,如果新版失效,请 直接去下载 https://github.com/micropython/micropython/releases/download/v1.20.0/micropython-1.20.0.zip 不要 git clone最新的

1
2
3
4
5
cd ..
git clone https://github.com/micropython/micropython.git
cd micropython
make -C mpy-cross
cd ports/esp32

测试默认编译micropython固件

1
2
make submodules
make  # make BOARD=LOLIN_S2_MINI

直接make的话 是 esp32 ,我这里是 s2 ,并且是 esp32 s2 mini开发板 所以添加参数 BOARD=LOLIN_S2_MINI 这样就可以编译 了,编译后 会有提示 ./build-XXXX/ firmware.bin 这个文件是合并后的,可以直接烧到0x1000

推送到 nas

我这里推送到nas里面,方便其他机器使用

1
rsync -avzut --progress  ./build-LOLIN_S2_MINI/firmware.bin    root@10.1.1.200:/mnt/nas/temp/firmware-my.bin 

烧录

在连接esp32的机器上从nas复制bin文件过来然后下入

1
2
esptool  --port COM3  erase_flash #擦除
esptool --port COM3   --baud 1000000 write_flash -z 0x1000 firmware-my.bin # 写入从nas 拷贝过来的 新固件

烧录完成后,重启板,thonny 链接上,输入 help(‘modules’) 查看模块

添加 smartconfig模块

1
cd ~/mpy-bin/micropython/ports/esp32

需要两个地方,1是 添加一个smarconfig.c文件,2 是修改还 ports/esp32/main/CMakeLists.txt 把 smarconfig.c 加进去

CMakeLists.txt

查看 cat ~/mpy-bin/micropython/ports/esp32/main/CMakeLists.txt 目前版本1.20版本 需要修改的内容在 54-92行 格式如下

1
2
3
4
5
6
set(MICROPY_SOURCE_PORT
    main.c
    ....
    machine_sdcard.c
    ...
)

我们需要在 machine_sdcard.c 后面添加一个 smarconfig.c,然后在 machine_sdcard.c的同目录下 创建一个 smarconfig.c (内容查看后文)
修改后的 CMakeLists.txt 内容格式如下

1
2
3
4
5
6
7
set(MICROPY_SOURCE_PORT
    main.c
    ....
    machine_sdcard.c
    smarconfig.c
    ...
)

同理,可以在这里删掉不用的模块

smarconfig.c

1
nano ~/mpy-bin/micropython/ports/esp32/machine_sdcard.c

内容参考后文。 找

重新编译

1
2
make submodules
make BOARD=LOLIN_S2_MINI

上级测试

新固件烧录后,烧录完成后,重启板,thonny 链接上,输入 help('modules') 查看模块 已经有 smarconfig 运行一个 main.py 测试,测试代码参考后文。 启动后,找一个支持smarconfig的手机app或者微信小程序 配网测试 我用的微信小程序#小程序://一键配网/047fob5XqOB14hk 复制发送到手机微信,聊天窗口点开即可。配网成功

添加自定义py文件

优点:1 保护代码 2 方便量产 内容单独另起一一个文章 添加自定义py文件到固件

内容

smarconfig.c

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include "py/obj.h"
#include "py/runtime.h"

#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_smartconfig.h"

static EventGroupHandle_t s_wifi_event_group;

static const int ESPTOUCH_DONE_BIT = BIT1;
static const char *TAG = "smartconfig";

static int found_ssid_and_password = false;
static uint8_t ssid[33] = {0};
static uint8_t password[65] = {0};
static uint8_t type = -1;
static uint8_t token = 0;

static void smartconfig_task(void * parm);


static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data)
{
    if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) {
        ESP_LOGI(TAG, "Scan done");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) {
        ESP_LOGI(TAG, "Found channel");
    } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) {
        ESP_LOGI(TAG, "Got SSID and password");

        smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data;

        memcpy(ssid, evt->ssid, sizeof(evt->ssid));
        memcpy(password, evt->password, sizeof(evt->password));
        type = evt->type;
        token = evt->token;

        ESP_LOGI(TAG, "SSID:%s", ssid);
        ESP_LOGI(TAG, "PASSWORD:%s", password);
        ESP_LOGI(TAG, "TYPE:%d", type);
        ESP_LOGI(TAG, "TOKEN:%d", token);

        found_ssid_and_password = true;

        xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT);
    }
}

static void initialise_wifi(void)
{
    s_wifi_event_group = xEventGroupCreate();

    ESP_ERROR_CHECK(esp_event_loop_create_default());
    ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));

    xTaskCreate(smartconfig_task, "smartconfig_task", 4096, NULL, 3, NULL);
}

static void smartconfig_task(void * parm)
{
    EventBits_t uxBits;
    ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS));
    smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_smartconfig_start(&cfg));

    while (1) {
        uxBits = xEventGroupWaitBits(s_wifi_event_group, ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); 
        if(uxBits & ESPTOUCH_DONE_BIT) {
            ESP_LOGI(TAG, "smartconfig over");
            ESP_ERROR_CHECK(esp_smartconfig_stop());
            vTaskDelete(NULL);
        }
    }
}

STATIC mp_obj_t smartconfig_start(void)
{
    initialise_wifi();
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(smartconfig_start_obj, smartconfig_start);

STATIC mp_obj_t smartconfig_success(void)
{
    return mp_obj_new_bool(found_ssid_and_password);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(smartconfig_success_obj, smartconfig_success);

STATIC mp_obj_t smartconfig_info(void)
{
    mp_obj_t info[] = {
        mp_obj_new_str((const char *)ssid, strlen((const char *)ssid)),
        mp_obj_new_str((const char *)password, strlen((const char *)password)),
        mp_obj_new_int(type),
        mp_obj_new_int(token)
    };

    return mp_obj_new_tuple(4, info);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(smartconfig_info_obj, smartconfig_info);


STATIC const mp_rom_map_elem_t smartconfig_module_globals_table[] = {
    {MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_smartconfig)},
    {MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&smartconfig_start_obj)},
    {MP_ROM_QSTR(MP_QSTR_success), MP_ROM_PTR(&smartconfig_success_obj)},
    {MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&smartconfig_info_obj)},
    {MP_ROM_QSTR(MP_QSTR_TYPE_UNKNOWN), MP_ROM_INT(-1)},
    {MP_ROM_QSTR(MP_QSTR_TYPE_ESPTOUCH), MP_ROM_INT(SC_TYPE_ESPTOUCH)},
    {MP_ROM_QSTR(MP_QSTR_TYPE_AIRKISS), MP_ROM_INT(SC_TYPE_AIRKISS)},
};

STATIC MP_DEFINE_CONST_DICT(smartconfig_module_globals, smartconfig_module_globals_table);

const mp_obj_module_t smartconfig_user_cmodule = {
    .base = {&mp_type_module},
    .globals = (mp_obj_dict_t *)&smartconfig_module_globals,
};

MP_REGISTER_MODULE(MP_QSTR_smartconfig, smartconfig_user_cmodule);

main.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import network
import socket
import smartconfig
import time


def inet_pton(ip_str:str):
    '''convert ip address from string to bytes'''
    ip_bytes = b''
    ip_segs = ip_str.split('.')

    for seg in ip_segs:
        ip_bytes += int(seg).to_bytes(1, 'little')

    return ip_bytes

def send_ack(local_ip, local_mac):
    '''sent ack_done event to phone'''
    udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    data = smartconfig.info()[3].to_bytes(1, 'little') + local_mac
    port = 10000 # airkiss port

    if smartconfig.info()[2] == smartconfig.TYPE_ESPTOUCH:
        data += inet_pton(local_ip)
        port = 18266 # esptouch port

    print(
f"""- sending ack:
    type: {'esptouch' if smartconfig.info()[2] == smartconfig.TYPE_ESPTOUCH else 'airkiss'}
    port: {port}
    data: {data}
    length: {len(data)}
"""
    )

    for _ in range(20):
        time.sleep_ms(100)
        try:
            udp.sendto(data, ('255.255.255.255', port))
        except OSError:
            pass

    print('- ack was sent')

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

print('- start smartconfig...')
smartconfig.start()

print('- waiting for success...')

while not smartconfig.success():
    time.sleep_ms(500)

print('- got smartconfig info')
ssid, password, sc_type, token = smartconfig.info()
print(smartconfig.info())

print('- connecting to wifi...')
station.connect(ssid, password)

while not station.isconnected():
    time.sleep_ms(500)
print('- wifi connected')

while station.ifconfig()[0] == '0.0.0.0':
    time.sleep_ms(500)
print('- got ip')
print(station.ifconfig())

send_ack(station.ifconfig()[0], station.config('mac'))
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计