勉強しないとな~blog

ちゃんと勉強せねば…な電気設計エンジニアです。

M5Stampでhttpサーバー

M5Stampの続き。
今回もサンプルプロジェクトから。

やりたいこと

前にM5StampでWi-Fiが使えることが確認できたので、サーバー機能を入れたりして、子供のおもちゃをIoT化したときの機能の一つとして使いたい。

サンプルプロジェクト確認

サンプルプロジェクトの中に、http_serverがある。これを使ってみたい。

http_serverの中にもサンプルが色々あるが、simpleを使ってみる。

HTTP基本

その前にHTTPがよくわかっていないので調べる。

とりあえずこのあたりのサイトで、HTTPの簡単な中身。

HTTP の概要 - HTTP | MDN

HTTP

2つ目のサイトは、更新日不明だが、"1998年現在"とか書いてあったりしてかなり古めだが、Google検索で上のほうに来てたので、今でも参考になるということか。

プロジェクト作成、ビルド、実行

今までの手順通り。

  • "ESP-IDF: Set Espressif device target"でターゲットを"esp32c3"に設定。
  • "ESP-IDF: Select port to use (COM, tty, usbserial)"でデバイス接続したCOMポートを指定。(デバイス接続しておく必要がある)
  • "ESP-IDF: Select Flash Method"で、書き込み方法を"UART"に指定。
  • "ESP-IDF: SDK Configuration editor"でプロジェクト設定。
    Example Configurationを見ると、WiFi SSIDとパスワードの設定あり。
    "Get ssid and password from stdin"で、stdinからSSIDとパスワード設定もできるようなので、これを試してみる。
  • "Save"して、"ESP-IDF: Build, Flash and start a monitor on your device"でビルドと実行。

実行

実行すると、ターミナルウィンドウに、SSIDとパスワード入力を求める表示が出るので、入力。

なんだかうまくつながらなかったようで、"Wi-Fi disconnected, trying to reconnect..."が何回か出て、自分で再起動したよう。
最終的に、同じSSID、パスワード入力画面になった。

ここでのSSID、パスワードの入力のしかたがよくわからなかったが、結局SSIDの後にスペースを一度入力して、続けてパスワードを入力したらつながった。

接続後、IPを取得(192.168.1.11)してポート80にサーバーが立ち上がったよう。

クライアント側からテスト

テスト方法

READMEを見ると、pythonでscripts/client.pyを実行する、curlコマンドを使う、という2つのテスト方法が書かれている。

前者は、スクリプトで全部勝手にやると何が起こっているかわかりにくそうだし、そもそもscripts/client.pyがどこにいるかもよくわからない。

ので、後者のcurlコマンド実行するほうで。

curlコマンドでテスト

PowerShellcurlコマンド使えるのかよくわからないが、やってみる。

(base) PS C:\work\esp-idf_test\simple_httpd_server\simple> curl 192.168.1.11:80/hello                     


StatusCode        : 200
StatusDescription : OK
Content           : Hello World!
RawContent        : HTTP/1.1 200 OK
                    Custom-Header-1: Custom-Value-1
                    Custom-Header-2: Custom-Value-2
                    Content-Length: 12
                    Content-Type: text/html

                    Hello World!
Forms             : {}
Headers           : {[Custom-Header-1, Custom-Value-1], [Custom-Header-2, Custom-Value-2], [Content-Lengt 
                    h, 12], [Content-Type, text/html]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : System.__ComObject
RawContentLength  : 12



(base) PS C:\work\esp-idf_test\simple_httpd_server\simple>

できた。HTTPの中身が確認できる。

このときのM5Stamp側の出力はこんな感じ。

I (324197) example: Found header => Host: 192.168.1.11
I (324197) example: Request headers lost

ここでは、HTTPのGETメソッドを実行することができた。

その他試してみたり調べたりして分かったこと。

  • PowerShellでは、Invoke-WebRequestコマンドレットでHTTPリクエストを出せるとのこと
  • さっきやった感じ、PowerShellでもcurlコマンドが使えたっぽかったが、Invoke-WebRequestへのエイリアスとして設定されているだけとのこと。
    Invoke-webRequestとcurlコマンドでHTTPリクエスト | SugiBlog
    実際、PowerShellで、curlでのPOSTメソッドをやってみようとしたが、ダメだった。引数のフォーマットが違うからではないかと思われる
  • 今使ってるPCにgitをインストールしたときにbashもセットアップされていて、こっちでcurlコマンドを使うことはできた。
  • Invoke-WebRequestcurlでは実行結果の表示に違いあり。

色々実行結果

PowerShell(Invoke-WebRequest) or bash(curl)、GETメソッド or POSTメソッドの組み合わせで試してみた。

PowerShell、GETメソッド

PowerShellcurlコマンドを打ったときと同じ結果になった。

(base) PS C:\work\esp-idf_test\simple_httpd_server\simple> Invoke-WebRequest 192.168.1.11:80/hello        
                                 
   
StatusCode        : 200
StatusDescription : OK
Content           : Hello World!
RawContent        : HTTP/1.1 200 OK
                    Custom-Header-1: Custom-Value-1
                    Custom-Header-2: Custom-Value-2
                    Content-Length: 12
                    Content-Type: text/html

                    Hello World!
Forms             : {}
Headers           : {[Custom-Header-1, Custom-Value-1], [Custom-Header-2, Custom-Value-2], [Content-Lengt 
                    h, 12], [Content-Type, text/html]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : System.__ComObject
RawContentLength  : 12

M5Stamp側。当然こちらも同じ。

I (133427) example: Found header => Host: 192.168.1.11
I (133427) example: Request headers lost

PowerShell、POSTメソッド

curlコマンドだと、ファイルを送る例がプロジェクトのREADMEに書かれていたが、Invoke-WebRequestの場合のやり方がわからなかった。適当な文字列を送っている。

(base) PS C:\work\esp-idf_test\simple_httpd_server\simple> Invoke-WebRequest -Method POST -Body @{key="tekitou"} 192.168.1.11:80/echo                                                                               
                                                 
   
StatusCode        : 200
StatusDescription : OK
Content           : key=tekitou
RawContent        : HTTP/1.1 200 OK
                    Transfer-Encoding: chunked
                    Content-Type: text/html

                    key=tekitou
Forms             : {}
Headers           : {[Transfer-Encoding, chunked], [Content-Type, text/html]}
Images            : {}
InputFields       : {}
Links             : {}
ParsedHtml        : System.__ComObject
RawContentLength  : 11

M5Stamp側。

I (822307) example: =========== RECEIVED DATA ==========
I (822307) example: key=tekitou
I (822307) example: ====================================

bash、GETメソッド

a@dell MINGW64 /c/work/esp-idf_test/simple_httpd_server/simple
$ curl 192.168.1.11:80/hello
Hello World!

必要最小限の結果表示になった。
-vオプションで、もう少し経過が表示される。

知っておくとちょっと便利!curl コマンドの使い方をご紹介 | SIOS Tech. Lab

a@dell MINGW64 /c/work/esp-idf_test/simple_httpd_server/simple
$ curl 192.168.1.11:80/hello -v
*   Trying 192.168.1.11:80...
* Connected to 192.168.1.11 (192.168.1.11) port 80 (#0)
> GET /hello HTTP/1.1
> Host: 192.168.1.11
> User-Agent: curl/8.1.2
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Content-Length: 12
< Custom-Header-1: Custom-Value-1
< Custom-Header-2: Custom-Value-2
<
Hello World!* Connection #0 to host 192.168.1.11 left intact

Invoke-WebRequestの結果と似ているが、もう少し時系列的な情報が出ているよう。

M5Stamp側は特に表示は変わらない。

I (1053277) example: Found header => Host: 192.168.1.11
I (1053287) example: Request headers lost

bash、POSTメソッド

bashでは、ファイルの中身を送ることができた。
プロジェクト内の適当なファイル(main/CMakeLists.txt)を送ってみた。

a@dell MINGW64 /c/work/esp-idf_test/simple_httpd_server/simple
$ curl -X POST --data-binary @main/CMakeLists.txt 192.168.1.11:80/echo
set(requires "")
if(${IDF_TARGET} STREQUAL "linux")
    list(APPEND requires esp_stubs esp-tls esp_http_server protocol_examples_common nvs_flash)
endif()
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS "."
                    REQUIRES ${requires})

echoのところに送っているが、送った内容がそのまま返されている。今回はファイルの中身。

上と同様に、-vオプションで詳細が表示される。

a@dell MINGW64 /c/work/esp-idf_test/simple_httpd_server/simple
$ curl -X POST --data-binary @main/CMakeLists.txt 192.168.1.11:80/echo -v
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 192.168.1.11:80...
* Connected to 192.168.1.11 (192.168.1.11) port 80 (#0)
> POST /echo HTTP/1.1
> Host: 192.168.1.11
> User-Agent: curl/8.1.2
> Accept: */*
> Content-Length: 278
> Content-Type: application/x-www-form-urlencoded
>
< HTTP/1.1 200 OK
< Content-Type: text/html
< Transfer-Encoding: chunked
< 
set(requires "")
if(${IDF_TARGET} STREQUAL "linux")
    list(APPEND requires esp_stubs esp-tls esp_http_server protocol_examples_common nvs_flash)
endif()
idf_component_register(SRCS "main.c"
                    INCLUDE_DIRS "."
                    REQUIRES ${requires})
* Connection #0 to host 192.168.1.11 left intact

a@dell MINGW64 /c/work/esp-idf_test/simple_httpd_server/simple
$

M5Stamp側。

I (1376567) example: =========== RECEIVED DATA ==========
I (1376567) example: set(requires "")
if(${IDF_TARGET} STREQUAL "linux")
    list(APPEND requires esp_stubs esp-tls esp
I (1376577) example: ====================================
I (1376577) example: =========== RECEIVED DATA ==========
I (1376587) example: _http_server protocol_examples_common nvs_flash)
endif()
idf_component_register(SRCS "main.c"

I (1376597) example: ====================================
I (1376597) example: =========== RECEIVED DATA ==========
I (1376607) example:                  INCLUDE_DIRS "."
                    REQUIRES ${requires})

I (1376627) example: ====================================

いくつかにデータが区切られて送られている。

参考: M5Stamp出力全部

一応載せておく。
何度かM5Stamp上での実行をやり直していて、最終的にうまくできたときのログ出力になっている。

クリックで開く/閉じる


(base) PS C:\work\esp-idf_test\simple_httpd_server\simple> set IDF_PATH=C:\Users\a\esp\esp-idf
(base) PS C:\work\esp-idf_test\simple_httpd_server\simple> c:\work\esp-idf_env\.espressif\python_env\idf5.1_py3.11_env\Scripts\python.exe C:\Users\a\esp\esp-idf\tools\idf_monitor.py -p COM3 -b 115200 --toolchain-prefix riscv32-esp-elf- --target esp32c3 c:\work\esp-idf_test\simple_httpd_server\simple\build\simple.elf
--- WARNING: GDB cannot open serial ports accessed as COMx
--- Using \\.\COM3 instead...
--- esp-idf-monitor 1.3.3 on \\.\COM3 115200 ---
--- Quit: Ctrl+] | Menu: Ctrl+T | Help: Ctrl+T followed by Ctrl+H ---
ESP-ROM:esp32c3-api1-20210207
Build:Feb  7 2021
rst:0x1 (POWERON),boot:0xc (SPI_FAST_FLASH_BOOT)
SPIWP:0xee
mode:DIO, clock div:1
load:0x3fcd5820,len:0x1704
load:0x403cc710,len:0x968
load:0x403ce710,len:0x2f68
entry 0x403cc710
I (30) boot: ESP-IDF v5.1.1 2nd stage bootloader
I (30) boot: compile time Nov 14 2023 06:54:17
I (30) boot: chip revision: v0.3
I (33) boot.esp32c3: SPI Speed      : 80MHz
I (38) boot.esp32c3: SPI Mode       : DIO
I (43) boot.esp32c3: SPI Flash Size : 2MB
I (47) boot: Enabling RNG early entropy source...
I (53) boot: Partition Table:
I (56) boot: ## Label            Usage          Type ST Offset   Length
I (64) boot:  0 nvs              WiFi data        01 02 00009000 00006000
I (71) boot:  1 phy_init         RF data          01 01 0000f000 00001000
I (79) boot:  2 factory          factory app      00 00 00010000 00100000
I (86) boot: End of partition table
I (90) esp_image: segment 0: paddr=00010020 vaddr=3c0a0020 size=237c8h (145352) map
I (122) esp_image: segment 1: paddr=000337f0 vaddr=3fc91600 size=02854h ( 10324) load
I (124) esp_image: segment 2: paddr=0003604c vaddr=40380000 size=09fcch ( 40908) load
I (135) esp_image: segment 3: paddr=00040020 vaddr=42000020 size=97adch (621276) map
I (235) esp_image: segment 4: paddr=000d7b04 vaddr=40389fcc size=07518h ( 29976) load
I (246) boot: Loaded app from partition at offset 0x10000
I (247) boot: Disabling RNG early entropy source...
I (258) cpu_start: Unicore app
I (258) cpu_start: Pro cpu up.
I (267) cpu_start: Pro cpu start user code
I (267) cpu_start: cpu freq: 160000000 Hz
I (267) cpu_start: Application information:
I (270) cpu_start: Project name:     simple
I (275) cpu_start: App version:      1
I (279) cpu_start: Compile time:     Nov 14 2023 06:53:57
I (285) cpu_start: ELF file SHA256:  3b03a5999661a97d...
I (291) cpu_start: ESP-IDF:          v5.1.1
I (296) cpu_start: Min chip rev:     v0.3
I (301) cpu_start: Max chip rev:     v0.99 
I (306) cpu_start: Chip rev:         v0.3
I (310) heap_init: Initializing. RAM available for dynamic allocation:
I (318) heap_init: At 3FC97F20 len 000447F0 (273 KiB): DRAM
I (324) heap_init: At 3FCDC710 len 00002950 (10 KiB): STACK/DRAM
I (331) heap_init: At 50000010 len 00001FD8 (7 KiB): RTCRAM
I (338) spi_flash: detected chip: generic
I (341) spi_flash: flash io: dio
W (345) spi_flash: Detected size(4096k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
I (359) sleep: Configure to isolate all GPIO pins in sleep state
I (365) sleep: Enable automatic switching of GPIO sleep configuration
I (373) app_start: Starting scheduler on CPU0
I (377) main_task: Started on CPU0
I (377) main_task: Calling app_main()
I (387) example_connect: Start example_connect.
I (387) pp: pp rom version: 9387209
I (387) net80211: net80211 rom version: 9387209
I (407) wifi:wifi driver task: 3fca08fc, prio:23, stack:6656, core=0
I (407) wifi:wifi firmware version: ce9244d
I (407) wifi:wifi certification version: v7.0
I (407) wifi:config NVS flash: enabled
I (417) wifi:config nano formating: disabled
I (417) wifi:Init data frame dynamic rx buffer num: 32
I (417) wifi:Init management frame dynamic rx buffer num: 32
I (427) wifi:Init management short buffer num: 32
I (427) wifi:Init dynamic tx buffer num: 32
I (437) wifi:Init static tx FG buffer num: 2
I (437) wifi:Init static rx buffer size: 1600
I (447) wifi:Init static rx buffer num: 10
I (447) wifi:Init dynamic rx buffer num: 32
I (447) wifi_init: rx ba win: 6
I (457) wifi_init: tcpip mbox: 32
I (457) wifi_init: udp mbox: 6
I (467) wifi_init: tcp mbox: 6
I (467) wifi_init: tcp tx win: 5744
I (467) wifi_init: tcp rx win: 5744
I (477) wifi_init: tcp mss: 1440
I (477) wifi_init: WiFi IRAM OP enabled
I (487) wifi_init: WiFi RX IRAM OP enabled
I (487) phy_init: phy_version 970,1856f88,May 10 2023,17:44:12
I (537) wifi:mode : sta (68:67:25:b2:53:44)
I (537) wifi:enable tsf
I (537) example_connect: Please input ssid password:
I (82457) example_connect: Connecting to XXXXXXXXXXXXX...
I (82457) example_connect: Waiting for IP(s)
I (84867) wifi:new:<5,0>, old:<1,0>, ap:<255,255>, sta:<5,0>, prof:1
I (85547) wifi:state: init -> auth (b0)
I (85547) wifi:state: auth -> assoc (0)
I (85557) wifi:state: assoc -> run (10)
I (85567) wifi:connected with XXXXXXXXXXXXX, aid = 12, channel 5, BW20, bssid = 48:3e:5e:7c:4a:53
I (85567) wifi:security: WPA2-PSK, phy: bgn, rssi: -63
I (85577) wifi:pm start, type: 1

I (85577) wifi:set rx beacon pti, rx_bcn_pti: 0, bcn_timeout: 25000, mt_pti: 0, mt_time: 10000
I (85587) wifi:AP's beacon interval = 102400 us, DTIM period = 1
I (86717) wifi:<ba-add>idx:0 (ifx:0, 48:3e:5e:7c:4a:53), tid:6, ssn:1, winSize:64
I (87387) example_connect: Got IPv6 event: Interface "example_netif_sta" address: fe80:0000:0000:0000:6a67:25ff:feb2:5344, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (87597) esp_netif_handlers: example_netif_sta ip: 192.168.1.11, mask: 255.255.255.0, gw: 192.168.1.1
I (87597) example_connect: Got IPv4 event: Interface "example_netif_sta" address: 192.168.1.11
I (87607) example_common: Connected to example_netif_sta
I (87617) example_common: - IPv4 address: 192.168.1.11,
I (87617) example_common: - IPv6 address: fe80:0000:0000:0000:6a67:25ff:feb2:5344, type: ESP_IP6_ADDR_IS_LINK_LOCAL
I (87627) example: Starting server on port: '80'
I (87637) example: Registering URI handlers
I (89487) wifi:<ba-add>idx:1 (ifx:0, 48:3e:5e:7c:4a:53), tid:0, ssn:1, winSize:64
I (133427) example: Found header => Host: 192.168.1.11
I (133427) example: Request headers lost
I (243947) example: =========== RECEIVED DATA ==========
I (243947) example: foo=main%2FCMakeLists.txt
I (243947) example: ====================================
I (822307) example: =========== RECEIVED DATA ==========
I (822307) example: key=tekitou
I (822307) example: ====================================
I (1053277) example: Found header => Host: 192.168.1.11
I (1053287) example: Request headers lost
I (1150567) example: Found header => Host: 192.168.1.11
I (1150567) example: Request headers lost
I (1376567) example: =========== RECEIVED DATA ==========
I (1376567) example: set(requires "")
if(${IDF_TARGET} STREQUAL "linux")
    list(APPEND requires esp_stubs esp-tls esp
I (1376577) example: ====================================
I (1376577) example: =========== RECEIVED DATA ==========
I (1376587) example: _http_server protocol_examples_common nvs_flash)
endif()
idf_component_register(SRCS "main.c"

I (1376597) example: ====================================
I (1376597) example: =========== RECEIVED DATA ==========
I (1376607) example:                  INCLUDE_DIRS "."
                    REQUIRES ${requires})

I (1376627) example: ====================================
I (1515027) example: =========== RECEIVED DATA ==========
I (1515027) example: set(requires "")
if(${IDF_TARGET} STREQUAL "linux")
    list(APPEND requires esp_stubs esp-tls esp
I (1515037) example: ====================================
I (1515047) example: =========== RECEIVED DATA ==========
I (1515057) example: _http_server protocol_examples_common nvs_flash)
endif()
idf_component_register(SRCS "main.c"

I (1515067) example: ====================================
I (1515067) example: =========== RECEIVED DATA ==========
I (1515077) example:                  INCLUDE_DIRS "."
                    REQUIRES ${requires})

I (1515087) example: ====================================

以上

サーバー機能使えそうなことは分かったが、HTTPとかもうちょっと勉強しないとできないかもしれない。

まだまだM5Stamp色々いじってみたい。