2017年5月16日 星期二

MicroPython on ESP8266 (五) : WiFi 連線與 WebREPL 測試

最近大部分閒暇時間都花在測試 ESP-01 模組上的 MicroPython, 這兩周完成了 Python 資料型態, 包括數值, 序列, 字典與集合等常用資料型別的測試. 接下來還有程式結構, 內建與自訂函數, 物件與模組等項目要測試, 但我已等不及要先進行 ESP8266 模組最重要的測試項目 : WiFi 連線測試了.

以下使用新版 ESP-01 模組 (1MB Flash) 進行測試, 舊版的 512KB 板有空再測試補充在後面. MicroPython 的網路與 WiFi 教學文件參考 :

http://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/network_basics.html
http://docs.micropython.org/en/latest/esp8266/esp8266/tutorial/intro.html#wifi

此系列前面四篇測試參考 :

MicroPython on ESP8266 (二) : 數值型別測試
# MicroPython on ESP8266 (三) : 序列型別測試
MicroPython on ESP8266 (四) : 字典與集合型別測試

MicroPython 文件參考 :

MicroPython tutorial for ESP8266  (官方教學)
http://docs.micropython.org/en/latest/micropython-esp8266.pdf
http://docs.micropython.org/en/latest/pyboard/library/usocket.html#class-socket
http://docs.micropython.org/en/v1.8.7/esp8266/library/usocket.html#module-usocket
https://gist.github.com/xyb/9a4c8d7fba92e6e3761a (驅動程式)

一. 建立 WiFi 連線 :

ESP8266 的 WiFi 網路介面有三種工作模式 :
  1. STA 模式 (Station) : ESP8266 可連線至一路由器 (AP)
  2. AP 模式 (Access Point) : 作為熱點讓其他設備連線至 ESP8266 
  3. STA+AP模式 : 混和模式 (同時開啟 STA 與 AP 功能)
MicroPython for ESP8266 1.8.7 版韌體預設只開啟 AP 模式, 為了使用筆電抓取測試紀錄, 以下根據上面官網教學文件將 ESP-01 模組的 STA 模式也打開, 變成 STA+AP 混和模式.

MicroPython 的網路函式庫放在 network 模組中, 可用來設定 ESP8266 的 WiFi 連線, 欲設定ESP8266 的 WiFi 連線須先匯入 network 模組 :

>>> import network

呼叫 network 模組的 network.WLAN() 函數並傳入模式參數 (network.STA_IF 或 network.AP_IF) 會傳回該模式的 WiFi 介面物件, 例如 :

>>> import network
>>> sta = network.WLAN(network.STA_IF)
>>> ap = network.WLAN(network.AP_IF)

呼叫介面物件的 active() 方法 (不帶參數) 會傳回該介面的狀態, 例如 :

>>> sta.active()     #不帶參數就是讀取介面狀態
False
>>> ap.active()
True

可見剛燒好的韌體預設是開啟 AP 模式, 關閉 STA 模式. 呼叫介面物件的 ifconfig() 方法會傳回包含此 AP 網路介面的網址, 網路遮罩, 閘道器, 以及 DNS 所組成的 tuple, 例如 :

>>> ap.ifconfig()
('192.168.4.1', '255.255.255.0', '192.168.4.1', '208.67.222.222')

可見此 AP 的網址為 192.168.4.1, 網路遮罩為 255.255.255.0, 閘道器為 192.168.4.1, DNS 為 208.67.222.222.

如果要開啟或關閉 WiFi 介面, 只要在 active() 方法中傳入 True 或 False :

>>> sta.active(True)               #開啟 STA 模式
#5 ets_task(4020ed88, 28, 3fff9f88, 10)
>>> sta.active()                      #查詢 STA 模式是否開啟
True

打開 STA 模式後可用 scan() 方法掃描附近有哪些基地台 :

>>> sta.scan()
[(b'family', b'l\x19\x8f\xb7\x9ay', 1, -87, 3, 0), (b'KT220', b'\xa8N?\xa3\x8fh', 1, -91, 4, 0), (b'EDIMAX-tony', b'\x80\x1f\x02-Z\x9e', 11, -47, 4, 0), (b'ching', b'T\xb8\n\x84\xf4p', 2, -89, 4, 0), (b'CHT5668', b'\xd8\xfe\xe3\\\xc0\xfb', 11, -87, 3, 0), (b'edimax.setup', b't\xda8\x15\x16\x00', 3, -80, 2, 0), (b'dlink', b'\x00$\x01\xad\xa0e', 6, -87, 0, 0), (b'andy', b'\xfcu\x16\x02a`', 6, -84, 4, 0), (b'TP-LINK_601A04', b"\xe8\xde'`\x1a\x04", 7, -73, 4, 0), (b'Melissa-eason', b'\x04\x8d8\xa5H\xea', 9, -91, 1, 0), (b'Home', b'\xc4\xe9\x84f3\xc1', 10, -73, 3, 0), (b'DSL-6641K', b'\xf0}h\xfc\x0e<', 11, -93, 2, 0)]

然後就可以用 connect() 方法連線基地台 (路由器), 下面是連線我的手機熱點 :

>>> sta.connect('H30-L02-webbot', '1234567890')      #連線 AP (路由器)

查看手機的基地台設定, 可以看到 ESP8266 (MAC 位址) 已經連線到手機熱點了 :



呼叫 ifconfig() 方法會傳回此 STA 介面的網址, 網路遮罩, 閘道器, 以及 DNS 的 tuple :

>>> sta.ifconfig()
('192.168.43.72', '255.255.255.0', '192.168.43.1', '192.168.43.1')

可見 ESP-01 連上基地台後獲得派發網址 192.168.43.72, 網路遮罩為 255.255.255.0, 閘道器為 192.168.43.1, 而 DNS 伺服器網址為 192.168.43.1.

連線完成後呼叫 isconnected() 方法會傳回 True, 表示已經連上基地台了 :

>>> sta.isconnected()
True

注意, 以上的連線設定會寫入 ESP8266 內建的的 FLASH 裡面, 即使關機也不會消失, 下次重開機時會自動連線上次設定的基地台.


二. 利用 WebREPL 遠端連線操控 ESP8266 :

除了使用 USB 連線直接用串列埠進入 REPL 介面操控 ESP8266 外, 還可以透過 WiFi 連線, 使用網頁式的 REPL 介面操控 ESP8266. 這樣只要讓 ESP8266 模組與 PC 連上同一個路由器, 就可以在 PC 上透過 WiFi 以無頭 (Headless) 模式操控 ESP8266. 即使沒有無線基地台可用也沒關係, MicroPython 韌體預設開啟 ESP8266 的 AP 模式, 亦即 ESP8266 本身就自帶熱點 (IP 固定為 192.168.4.1), PC 可連線此 AP 來使用 webrepl 功能.

參考下列教學文件 :

# WebREPL - a prompt over WiFi
# WebREPL (web browser interactive prompt)
# Adafruit : Access WebREPL
# Running MicroPython on the ESP8266
An Introduction to MicroPython

WebREPL 可以在 Intranet 環境 (無 Internet 連線時) 使用, 但要先到 GitHub 下載 WebREPL Client 客戶端 :

https://github.com/micropython/webrepl

按右邊 "Clone and download" 鈕, 再按 "Download zip" 鈕下載 zip 檔後解壓縮, 用 Chrome 或 FireFox 開啟裡面的 webrepl.html 網頁檔, 即可使用 WebREPL 功能.


由於 ESP8266 的 MicroPython 韌體預設開啟 AP 模式, 因此將 PC (我是用筆電) 連線 ESP8266 的 AP, 其 SSID 為 MicroPython-xxxxxx, 後面的 xxxxxx 是 MAC 位址最後 6 個數字, 密碼是 micropythoN (注意, 最後的 N 是大寫).


參考 :

"After a fresh install and boot the device configures itself as a WiFi access point (AP) that you can connect to. The ESSID is of the form MicroPython-xxxxxx where the x’s are replaced with part of the MAC address of your device (so will be the same everytime, and most likely different for all ESP8266 chips). The password for the WiFi is micropythoN (note the upper-case N). Its IP address will be 192.168.4.1 once you connect to its network. WiFi configuration will be discussed in more detail later in the tutorial."

然後要先去 REPL 介面開啟 webrepl 功能 :

>>> import webrepl_setup

Adafruit 教學文件應該會出現是否要啟動 webrepl 功能的詢問選單, 選擇 Enable 的話還要設定連線密碼並重啟系統才會生效. 但是我測試的結果卻是出現錯誤, 但第二次輸入匯入指令又不再顯示錯誤, 真是奇怪 :

b▒#5 ets_task(40100164, 3, 3fff8398, 4)
could not open file 'boot.py' for reading
could not open file 'main.py' for reading

MicroPython v1.8.7-7-gb5a1a20a3 on 2017-01-09; ESP module with ESP8266
Type "help()" for more information.
>>> import webrepl_setup
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "webrepl_setup.py", line 111, in <module>
  File "webrepl_setup.py", line 80, in main
  File "webrepl_setup.py", line 49, in get_daemon_status
OSError: [Errno 2] ENOENT
>>> import webrepl_setup
>>> import webrepl_setup
>>>

我在下面這篇文章發現原來 webrepl.start() 可以傳一個 password 參數進去手動更新密碼 (密碼最長是 9 個字元) :

# esp8266 webrepl_setup crashes #2605


這樣就可以啟動 webrepl 了 :

>>> import webrepl_setup
>>> import webrepl
>>> webrepl.start(password="1234567890")    #密碼太長 (最長 9 碼)
Traceback (most recent call last):
  File "  File "webrepl.py", line 69, in start
ValueError:
>>> webrepl.start(password="123456")       
WebREPL daemon started on ws://192.168.4.1:8266         #AP 模式網址
WebREPL daemon started on ws://192.168.43.72:8266     #STA 模式網址
Started webrepl in manual override mode


由於我同時開啟 ESP8266 的 AP 與 STA 模式, 因此會開啟兩個 webrepl 的 Daemon 程序, 其中 192.168.4.1 (固定 IP) 是 AP 模式網址, 而 192.168.43.72 (此為路由器 DHCP 指派, 不固定) 則是 STA 模式網址.

首先測試 AP 模式, 上面我已經用筆電連線 ESP8266 的 192.168.4.1 AP 了, 這樣就可以在 PC 上開啟 WebREPL Client 網頁 (即下載之 webrepl.html), 其預設 IP 就是 ESP8266 AP 的網址 192.168.4.1, 直接按 Connect 鈕就會在下方的方框中出現 "Welcome to webrepl!" 以及 "Password:" 提示號, 這時輸入上面設定的密碼按 Enter 就會出現 REPL 提示號了 :


這時在 REPL 介面上會顯示來自 PC 的連線訊息 :

WebREPL connection from: ('192.168.4.2', 2535)


在 webrepl 網頁上輸入 print('Hello') 同時 REPL 介面也會同步顯示輸入的指令與回應 : 

print('Hello')
Hello

上面這種連線 ESP8266 AP 的方式適合用在沒有 WiFi 路由器場合, 如果有一台無線基地台, 則除了可連線 ESP8266 的 AP 外, 可將 ESP8266 模組設定在 STA 或 STA+AP 模式連線此基地台, 這樣 ESP8266 會獲得路由器指派一個 IP (如上面範例的 192.168.43.72). PC 也可以利用這個 STA 模式的 IP 來連線 webrepl, 這時在 WebREPL 網頁的 IP 就必須改為 STA 模式的 IP (此處是 192.168.43.72) :



這時 REPL 介面上顯示的是來自 PC 在同一網段的 IP (此處是 192.168.43.73) :

WebREPL connection from: ('192.168.43.73', 2416)
print('Hello!')
Hello!

這種方式的好處就是 PC 在連線 webrepl 的同時, 仍然保有存取 Internet 的能力. 前面連線 ESP8266 AP 的方式會讓 PC 無法存取 Internet.

在 STA 或 STA+AP 模式下, 除了可開啟下載至本機的 WebREPL client 網頁連線 ESP8266 外, PC 也可以直接連線到 WebREPL 在 Internet 的公眾網頁進行連線 :

http://micropython.org/webrepl/

是的沒錯, 在 Internet 上也是可以存取到 ESP8266 喔.

這個 WebREPL 功能具有很重大的意義, 只要將上面啟動 webrepl 的程式碼寫在 boot.py 裡面, 讓 ESP8266 模組一啟動就開啟 webrepl 功能, 理論上我們就可以不需要透過 USB 串列埠的硬體連接, 只要利用 WiFi 就能以 webrepl 的方式對 ESP8266 模組進行遠端存取了 (跟樹莓派的無頭存取 Headless access 異曲同工). 參考官網文件 :

# Start up scripts

"There are two files that are treated specially by the ESP8266 when it starts up: boot.py and main.py. The boot.py script is executed first (if it exists) and then once it completes the main.py script is executed. You can create these files yourself and populate them with the code that you want to run when the device starts up."

大意是說 MicroPython 在 ESP8266 上有兩個特殊檔案 : boot.py 與 main.py, 開機時會先去執行 boot.py (存在的話), 然後再執行 main.py. 因此我們可以把連線網路, 開啟 webrepl 等環境設定用的程式碼寫在 boot.py(類似 Arduino 的 setup 函數的功能), 而將主要的應用邏輯寫在 main.py (類似 Arduino 的 loop 函數的功能). 下面這篇 Adafruit 的文章對根目錄下的這兩個檔案用途解釋得很清楚 :

# Adafruit : Boot Scripts

不管是重啟系統或重開機, 這兩個檔案一定會被執行 (先 boot.py 再 main.py).

但目前遇到的問題是, 當使用 WEBREPL 網頁右方的傳檔功能時, 馬上就顯示 WEBREPL disconnected! 原因為何尚待進一步調查.

事實上按照下列文件去操作內部檔案系統, 我發現 ESP-01 模組似乎無法使用 os.open(), os.close(), os.mkdir(), os.listdir() 等函數, 會產生 OSError, 甚至 reboot :

# The internal filesystem

"If your devices has 1Mbyte or more of storage then it will be set up (upon first boot) to contain a filesystem. This filesystem uses the FAT format and is stored in the flash after the MicroPython firmware."

>>> import os
>>> os.mkdir('dir')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 13] EACCES
>>> os.listdir()
['\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00', '\x00\x00\x00\x00\x00\x00\x00\x00.\x00\x00\x00',
...........................................
(System reboot)

奇怪, 看起來 listdir() 方法沒有報錯, 是有實作且可執行的, 但是為何印出大量 \x00 後就重開機呢? \x00 到底表示檔案系統是啥狀態呢?


~~~ 2017-05-17 補充 :

從 os.listdir() 冒出一堆 \x00 以及上面用 webrepl 傳檔就斷線的情況, 我已在懷疑 1.8.7 版的 MicroPython 韌體在燒入 ESP8266 之後並沒有建立檔案系統, 這或許就是為何開機後出現無法開啟 boot.py 與 main.py 這兩個執行檔的原因了 :

b▒#4 ets_task(40100164, 3, 3fff8398, 4)
could not open file 'boot.py' for reading
could not open file 'main.py' for reading

MicroPython v1.8.7-7-gb5a1a20a3 on 2017-01-09; ESP module with ESP8266
Type "help()" for more information.
>>>

這問題困擾我兩天了, 為何 WebREPL 終於測試成功後, 其傳檔功能卻失敗? 這問題若搞不定, ESP8266 就只能靠串列埠硬敲程式進去執行, 重開機又要重敲, 根本沒有實用價值. 今晚經過漫長的搜尋可能原因, 我用 "micropython esp8266 file permission" 去搜尋, 終於找到下面 MicroPython 論壇中的這篇文章, 給出了答案 :

# File Permission issues - OSError: [Errno 13] EACCES

原來其中這位 robos 遇到跟我一樣的問題, 就是在 os.listdir() 時得到一堆 \x00 頃印. 重點是下面這位 fdushin 先生給出了答案 : 就是重建檔案系統. 他提供了一段 Python 程式碼, 利用 flashbdev 函數在 ESP8266 的 FLASH 中寫入一個 boot.py 檔案, 我依樣畫葫蘆將其程式碼敲進去執行後, 再次執行 os.listdir() 果然列出 boot.py 這檔案了 :

 MicroPython v1.8.7-7-gb5a1a20a3 on 2017-01-09; ESP module with ESP8266
Type "help()" for more information.
>>> from flashbdev import bdev
>>> uos.VfsFat.mkfs(bdev)
>>> vfs = uos.VfsFat(bdev, "")
>>> with open("/boot.py", "w") as f:
...     f.write("""\
...     import gc
...     import webrepl
...     webrepl.start(password="123456")
...     gc.collect()
...     """)
...     f.close()
...
...
...
96
>>>


from flashbdev import bdev
uos.VfsFat.mkfs(bdev)
vfs = uos.VfsFat(bdev)
with open("/boot.py", "w") as f:
    f.write("""\
import gc
import webrepl
webrepl.start(password="123456")
gc.collect()
""")
    f.close()

MicroPython v1.8.7-7-gb5a1a20a3 on 2017-01-09; ESP module with ESP8266
Type "help()" for more information.
>>> import os
>>> os.listdir()
['boot.py']  
>>> import sys  
>>> sys.path  
['', '/lib', '/']

# Re: Corrupted filesystem

# How to install other micropython libs in pyboard?

而且重新於 webrepl 傳送與接收檔案都不會斷線, 都能成功完成送收作業, 真是太開心了! 下圖顯示當利用 http://micropython.org/webrepl/ 連線 ESP8266 時, 在右邊 "Get a file" 框輸入 boot.py, 再按 "Get from device" 就會從 ESP8266 根目錄下載 boot.py 開機檔案, 因為網頁是在 Internet 上, 因此會彈出視窗詢問檔案要儲存在哪個地方 :


OK! 這樣終於搞定檔案上傳下載這個大問題了.

其實在重建檔案系統之前我還試過用 ampy 去傳送檔案, 例如下列指令都可執行, 參考 :

# Ampy failure

D:\test>ampy --port COM4 ls                       (OK, 但輸出一堆小數點)

D:\test>ampy --port COM4 put --help          (OK)

D:\test>ampy --port "COM4" put --help       (OK)

但是下面指令就會報錯 (我已在目前目錄打好 boot.py 檔案) :

D:\test>ampy --port "COM4" put boot.py     (NG)

D:\test>ampy --port "COM4" get boot.py     (NG)

也試過 mpfshell, 結果也是無法傳檔 :

D:\test>mpfshell

** Micropython File Shell v0.8.0, sw@kaltpost.de **
-- Running on Python 3.6 using PySerial 3.3 --

mpfs [/]> open ws:192.168.2.107
webrepl passwd:
Connected to esp8266
mpfs [/]> pwd
/
mpfs [/]> help

Documented commands (type help <topic>):
========================================
EOF  cd     exec  get   lcd  lpwd  md    mput  mrm   put  repl
cat  close  exit  help  lls  ls    mget  mpyc  open  pwd  rm

mpfs [/]> put boot.py  (結果 REPL 輸出一堆 \x00)

原來問題都是因為沒有檔案系統之故. 經過重建之後, ampy 傳檔都沒問題了 :

D:\test>ampy --port COM4 put boot.py

D:\test>ampy --port COM4 ls
boot.py
D:\test>ampy --port COM4 get boot.py
import gc
import webrepl
webrepl.start(password="123456")
gc.collect()

而 mpfshell 的檔案操作也沒問題了 :

mpfs [/]> open ws:192.168.2.107
webrepl passwd:
Connected to esp8266
mpfs [/]> put boot.py
mpfs [/]> cat boot.py
#import esp
#esp.osdebug(None)
import gc
import webrepl
webrepl.start(password="123456")
gc.collect()
mpfs [/]> get boot.py
mpfs [/]> ls

Remote files in '/':

       boot.py

mpfs [/]>

哈! 大功告成! 這是今天最開心的一件事! 這都要感謝 fdushin 不吝提供重建檔案系統的程式碼, 要不然不知道要在這裡卡關多久呢!


~~~ 2017-05-19 補充 :

今天在 Adafruit 看到這篇 :

Disable ESP8266 Debug Output

此文建議把下列程式碼寫入 boot.py, 禁止偵錯訊息輸出到串列埠, 以免干擾 ampy 的操作 :

import esp
esp.osdebug(None)

不過如果還要看到偵錯訊息的話, 就把這兩行 remark 掉, 我目前的 boot.py 是這樣 :

#import esp
#esp.osdebug(None)
import gc
import webrepl
webrepl.start(password="123456") 
gc.collect()

~~~ 20170609 補充 :

其實我們可以把上面連線 WiFi 基地台的程式碼寫成 main.py 裡的函數, 參考 :

http://docs.micropython.org/en/v1.8.7/esp8266/esp8266/quickref.html#networking

我將官網的範例稍作修改, 把原先寫在函數裡面的 ssid 與 pwd 改由參數傳入, 這樣就可以在 REPL 介面呼叫此函數時修改欲連線之 WiFi 基地台了 :

def connect_wifi(ssid, pwd):
    import network
    wlan=network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        print('connecting to network ...')
        wlan.connect(ssid, pwd)
        while not wlan.isconnected():
            pass
    print('Connected:', wlan.ifconfig())

將上面的程式碼存成 main.py, 用 ampy 上傳到 ESP8266 模組 :

D:\test>ampy --port COM4 put main.py

開啟 Putty 連線模組後, 按 Ctrl-D 軟開機就會重新執行 boot.py 與 main.py, 然後就可以在 REPL 介面呼叫此函數時傳入欲連線之基地台 :

PYB: soft reboot
#8 ets_task(40100164, 3, 3fff829c, 4)
MicroPython v1.9-43-gb24ccfc6 on 2017-06-09; ESP module with ESP8266
Type "help()" for more information.
>>> connect_wifi('EDIMAX-tony','1234567890')
Connected: ('192.168.2.112', '255.255.255.0', '192.168.2.1', '168.95.1.1')

由於 ESP8266 本來就是用來連網的, network 模組一定會用到, 因此上面的 main.py 可以將 import network 拿出來, 加上 get_ip() 函數如下 :

import network
def connect_wifi(ssid, pwd):
    sta=network.WLAN(network.STA_IF)
    sta.active(True)
    if not sta.isconnected():
        print('connecting to network ...')
        sta.connect(ssid, pwd)
        while not sta.isconnected():
            pass
    print('Connected:', sta.ifconfig())

def get_ip():
  return network.WLAN(network.STA_IF).ifconfig()[0]

ifconfig() 傳回四元素的串列, 而本身的 IP 在索引 0. 這樣呼叫 get_ip() 即可取得本身的 IP 了 :

PYB: soft reboot
#7 ets_task(40100164, 3, 3fff829c, 4)
MicroPython v1.9-43-gb24ccfc6 on 2017-06-09; ESP module with ESP8266
Type "help()" for more information.
>>> get_ip()
'192.168.2.112'

參考 :

http://micropython.org/webrepl/
# How to setup WebREPL to connect to Python prompt (REPL) of ESP8266 over WIFI network?
# ESP8266 and MicroPython - Part 1
# WebREPL - a prompt over WiFi
# RFC: Not enabling WebREPL by default on boot in 1.8.6
# Current _boot.py not compatible with ESP8266-01 #1986
Adafruit MicroPython Tool - Utility to interact with a MicroPython board over a serial connection
https://forum.micropython.org/viewtopic.php?t=2591
# (lost+found) Running MicroPython on the ESP8266
# 4.2. Assigning a static IP address when booting

2017-06-26 補充 :

今天看到另一個重建檔案系統的方法 :

# Re: Corrupted filesystem

指令如下 :

import uos
import flashbdev
uos.VfsFat.mkfs(flashbdev.bdev)

但不一定有效, 還是上面的方式最有效.

2017-07-05 補充 :

ESP8266 連線基地台操作方式我已實作更好的方法, 就是利用其內建的 AP 所綁定的 192.168.4.1 網址, 利用 WEB 伺服器透過手機來設定, 參考下列文章的測試 5 :

# MicroPython on ESP8266 (十四) : 網頁伺服器測試

沒有留言 :