2025年4月24日 星期四

MicroPython 學習筆記 : 用於 ESP8266 開發板的 esptools

這幾天完成手上所有 ESP32 開發板的 esptools.py 測試, 結論是所有 ESP32 開發板用 esptools.py 傳送文字與網路圖片到 Telegram, 以及詢問 GPT 都沒問題, 但傳送本機圖片則必須要有 SPI RAM (PSRAM) 才行, 例如 ESP32 WROVER 或 ESP32 S3 開發板. 

剩下 ESP8266 開發板還沒測, 今天找了一塊 D1 Mini 板子測試, 結果如下 : 

MPY: soft reboot
MicroPython v1.25.0 on 2025-04-15; ESP module with ESP8266
Type "help()" for more information.

冷開機後上傳 config.py 與 esptools.py (從 ESP32 那邊複製過來), 先查初始記憶體 :

>>> import gc  
>>> print(gc.mem_free())    
33712

ESP8266 開發板配備 160KB SRAM, 其中一半給 MicroPython 用, 剩下一半約 80KB 給程式使用, 但實際上目前剩下 33KB 而已. 

>>> import config  
>>> print(gc.mem_free())      
32800

匯入 config 後剩下 32.8KB.

>>> import esptools   
>>> print(gc.mem_free())   
28544   

匯入 esptools 後剩下 28.5KB.

>>> ssid=config.SSID   
>>> password=config.PASSWORD   
>>> token=config.TELEGRAM_TOKEN    
>>> chat_id=config.TELEGRAM_CHAT_ID 
>>> ip=esptools.connect_wifi(ssid, password)   
network config: ('192.168.77.84', '255.255.255.0', '192.168.77.150', '192.168.77.150')
>>> print(gc.mem_free())   
26144

連線 WiFi 後只剩 26KB 左右. 試試看傳訊息到 Telegram : 

>>> text='Hello! 你好!'    
>>> esptools.telegram_text(token, chat_id, text)   

Exception occurred : -40
False

結果拋出 -40 例外, 查詢 ChatGPT 原因為記憶體不足所致. ESP8266 因為 SRAM 記憶體有限, 無法進行需要耗費較大記憶體的作業, 所以我將 esptools.py 模組刪減為如下的 ESP8266 版 :

# esptools.py for ESP8266, 2025-04-24 updated (adapted from xtools.py)
from machine import Pin, RTC, unique_id
import urandom, time, network, urequests, ubinascii
import ntptime, ujson
import uos

def get_id():
    return ubinascii.hexlify(unique_id()).decode('utf8')

def get_mac():
    sta=network.WLAN(network.STA_IF)
    mac=sta.config('mac')
    return ubinascii.hexlify(mac, ':').decode('utf8')

def get_num(x):
    return float(''.join(filter(lambda c: c.isdigit() or c == ".", x)))

def random_in_range(low=0, high=1000):
    return urandom.getrandbits(32) % (high - low) + low

def map_range(x, in_min, in_max, out_min, out_max):
    return int((x-in_min) * (out_max-out_min) / (in_max-in_min) + out_min)
   
def connect_wifi(ssid, password, led=2, timeout=20):
    wifi_led=Pin(led, Pin.OUT, value=1)
    sta=network.WLAN(network.STA_IF)
    if not sta.active():
        sta.active(True)   # 確保已啟動 WiFi
    start_time=time.time() # 記錄時間判斷是否超時
    if not sta.isconnected():
        print("Connecting to network...")
        sta.connect(ssid, password)
        while not sta.isconnected() and time.time() - start_time <= timeout:
            wifi_led.value(0)
            time.sleep_ms(300)
            wifi_led.value(1)
            time.sleep_ms(300)
        if not sta.isconnected():
            print("Wifi connecting timeout!")
            return None
    if sta.isconnected():
        for _ in range(25):  # 連線成功 : 快閃 5 秒
            wifi_led.value(0)
            time.sleep_ms(100)
            wifi_led.value(1)
            time.sleep_ms(100)
        print("network config:", sta.ifconfig())
        return sta.ifconfig()[0] 

def scan_ssid():
    sta=network.WLAN(network.STA_IF)
    sta.active(True)
    aps=sta.scan()
    for ap in aps:
        ssid=ap[0].decode()
        mac=ubinascii.hexlify(ap[1], ':').decode()
        rssi=str(ap[3]) + 'dBm'
        print(f'{ssid} {mac} {rssi}')

def show_error(led=2, final_state=0):
    led=Pin(led, Pin.OUT)   # D1 mini built-in D4=LED 2
    for i in range(3):
        led.value(1)
        time.sleep(0.5)
        led.value(0)
        time.sleep(0.5)
    led.value(final_state)    

def webhook_post(url, value, led=2):
    try:
        r=urequests.post(url, data=value)
        if r.status_code == 200:
            print("Webhook invoked")
        else:
            print("Webhook failed")
            show_error(led)
    finally:
        r.close()  # 釋放資源
    return r

def webhook_get(url, led=2):
    try:
        r=urequests.get(url)
        if r.status_code == 200:
            print("Webhook invoked")
        else:
            print("Webhook failed")
            show_error(led)
    finally:
        r.close()  # 釋放資源
    return r

def urlencode(params):
    # 將字典的鍵值對轉換為 URL 編碼的字串 (k=v) 並以 & 連接多個鍵值對
    kv=['{}={}'.format(k, v) for k, v in params.items()]
    return '&'.join(kv)

def tw_now():
    try: # 從 NTP 取得 UTC 時戳加 8 為台灣時間, 若成功設定 RTC
        print('Querying NTP server and set RTC time ... ', end='')
        utc=ntptime.time() # 取得 UTC 時戳
        print('OK.')
        t=time.localtime(utc + 28800) # 傳回台灣時間的元組
        RTC().datetime(t[0:3] + (0,) + t[3:6] + (0,))
    except Exception as e:  # 加入例外處理
        print(f'Failed. {e}')
    return strftime()  # 傳回目前之日期時間字串 YYYY-mm-dd HH:MM:SS 

def strptime(dt_str):
    t=time.mktime((
        int(dt_str[0:4]), int(dt_str[5:7]), int(dt_str[8:10]),
        int(dt_str[11:13]), int(dt_str[14:16]), int(dt_str[17:19]),
        0, 0))
    return time.localtime(t)
    
def strftime(dt=None, format_str="%Y-%m-%d %H:%M:%S"):
    if dt is None:
        dt=time.localtime()
    return format_str.replace("%Y", str(dt[0])) \
                     .replace("%m", "{:02d}".format(dt[1])) \
                     .replace("%d", "{:02d}".format(dt[2])) \
                     .replace("%H", "{:02d}".format(dt[3])) \
                     .replace("%M", "{:02d}".format(dt[4])) \
                     .replace("%S", "{:02d}".format(dt[5]))


將 Telegram 與 GPT 等函式刪除後, esptools.py 瘦身為 5KB 左右. 注意, 此處 strptime() 已改為較精簡省記憶體的寫法.  上傳精簡後的 esptools.py 重開機測試 : 

MPY: soft reboot
MicroPython v1.25.0 on 2025-04-15; ESP module with ESP8266
Type "help()" for more information.

>>> import gc   
>>> print(gc.mem_free())    
33712
>>> import config   
>>> print(gc.mem_free())    
32800
>>> import esptools   
>>> print(gc.mem_free())    
28912 
>>> ssid=config.SSID    
password=config.PASSWORD 
>>> ip=esptools.connect_wifi(ssid, password)   
network config: ('192.168.77.84', '255.255.255.0', '192.168.77.150', '192.168.77.150')
>>> print(gc.mem_free())    
27808
>>> esptools.tw_now()   
Querying NTP server and set RTC time ... OK.
'2025-04-24 11:01:44'

可見連線 WiFi 後記憶體多出了 1.7KB 左右. 總之, ESP8266 屬於低階物聯網控制器, 較高階之專案要用 ESP32. 

沒有留言 :