ESP32 が Web インターフェースを LittleFS から配信している場合、開発中に更新した HTML/JS ファイルをプッシュする方法が必要。4 つの小さな HTTP エンドポイント——list、upload、CRC32、delete——を使えば、数行の Python で rsync 風の同期を構築できる:CRC32 が異なるファイルだけアップロードし、ローカルに存在しなくなったリモートファイルを削除する。
ファームウェアのエンドポイント
GET /api/files— リモートファイルをサイズ付きで一覧PUT /api/files/put?path=…— ファイルをアップロードGET /api/file-crc?path=…— リモートファイルの CRC32 + サイズDELETE /api/files/delete?path=…— ファイルを削除
同期スクリプト
sync_webinterface.py
import urllib.parse
import zlib
from pathlib import Path
import requests
BASE_URL = "http://10.56.11.162"
def compute_crc32(path: Path) -> int:
crc = 0
with path.open("rb") as f:
while chunk := f.read(65536):
crc = zlib.crc32(chunk, crc)
return crc & 0xFFFFFFFF
def get_device_file_crc(path: str):
url = f"{BASE_URL}/api/file-crc?path={urllib.parse.quote(path, safe='')}"
r = requests.get(url, timeout=10)
if r.status_code == 200:
j = r.json()
return j.get("crc32_u32"), j.get("size")
return None, None
def upload_file(path: str, local_path: Path) -> bool:
url = f"{BASE_URL}/api/files/put?path={urllib.parse.quote(path, safe='')}"
# 完全にメモリに読み込む:requests は Content-Length を送る必要があり、
# chunked Transfer-Encoding ではだめ(esp_http_server が拒否する)。
r = requests.put(url, data=local_path.read_bytes(), timeout=30)
return r.status_code in (200, 201)
def list_device_files() -> list[str]:
r = requests.get(f"{BASE_URL}/api/files", timeout=10)
return [f["path"] for f in r.json().get("files", [])]
def delete_device_file(path: str) -> bool:
url = f"{BASE_URL}/api/files/delete?path={urllib.parse.quote(path, safe='')}"
return requests.delete(url, timeout=10).status_code == 200
def sync(local_dir: Path):
local_files = {
p.relative_to(local_dir).as_posix(): p
for p in local_dir.rglob("*") if p.is_file()
}
for rel, full in local_files.items():
local_crc = compute_crc32(full)
dev_crc, _ = get_device_file_crc(rel)
if dev_crc is None:
print(f"{rel}: missing on device, uploading")
upload_file(rel, full)
elif dev_crc != local_crc:
print(f"{rel}: CRC mismatch (local=0x{local_crc:08X} "
f"device=0x{dev_crc:08X}), uploading")
upload_file(rel, full)
else:
print(f"{rel}: up-to-date")
for dev in list_device_files():
if dev not in local_files:
if dev == "nodes.json":
continue # 保護されたシステムファイル
print(f"{dev}: deleted on device")
delete_device_file(dev)全部再アップロードではなく CRC32 を使う理由
LittleFS は書き込み回数に限りのあるフラッシュ上にあり、ESP32 への Wi-Fi/イーサネット経由のアップロードは遅い(約 100 kbit/s 級)。先に CRC を比較すれば、変更のないファイルは完全なアップロードではなく小さな GET 1 回で済む。esp_rom_crc32_le(最後の ~crc ^ 0xFFFFFFFF 修正込み)は zlib.crc32 と一致するので、比較は純粋な整数の等価チェックになる。
落とし穴
- chunked ボディを絶対送らない —
bytes(またはファイルオブジェクト)を渡してrequestsがContent-Lengthを送るようにする;req->content_lenを読むesp_http_serverハンドラは chunked 転送で失敗する。 pathパラメータを URL エンコード(urllib.parse.quote)して、サブディレクトリ内のファイルが動くようにする。- システムファイルをサーバ側で保護(例:
nodes.json)し、同期でもスキップする。
関連記事
この記事が役に立った場合は、TechOverflow での新しい記事の調査と公開を支援するため、「Buy Me a Coffee」でのご支援、または PayPal 経由でのご寄付をご検討ください