mirror of
https://github.com/esphome/esphome.git
synced 2025-08-10 12:27:46 +00:00
delete
This commit is contained in:
@@ -1,182 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
Test script for ESP-IDF web server multipart OTA upload functionality.
|
|
||||||
This script can be run manually to test OTA uploads to a running device.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
from pathlib import Path
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import requests
|
|
||||||
|
|
||||||
|
|
||||||
def test_multipart_ota_upload(host, port, firmware_path):
|
|
||||||
"""Test OTA firmware upload using multipart/form-data"""
|
|
||||||
base_url = f"http://{host}:{port}"
|
|
||||||
|
|
||||||
print(f"Testing OTA upload to {base_url}")
|
|
||||||
|
|
||||||
# First check if server is reachable
|
|
||||||
try:
|
|
||||||
resp = requests.get(f"{base_url}/", timeout=5)
|
|
||||||
if resp.status_code != 200:
|
|
||||||
print(f"Error: Server returned status {resp.status_code}")
|
|
||||||
return False
|
|
||||||
print("✓ Server is reachable")
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Error: Cannot reach server - {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Check if firmware file exists
|
|
||||||
if not Path(firmware_path).exists():
|
|
||||||
print(f"Error: Firmware file not found: {firmware_path}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Prepare multipart upload
|
|
||||||
print(f"Uploading firmware: {firmware_path}")
|
|
||||||
print(f"File size: {Path(firmware_path).stat().st_size} bytes")
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(firmware_path, "rb") as f:
|
|
||||||
files = {"firmware": ("firmware.bin", f, "application/octet-stream")}
|
|
||||||
|
|
||||||
# Send OTA update request
|
|
||||||
resp = requests.post(f"{base_url}/ota/upload", files=files, timeout=60)
|
|
||||||
|
|
||||||
if resp.status_code in [200, 201, 204]:
|
|
||||||
print(f"✓ OTA upload successful (status: {resp.status_code})")
|
|
||||||
if resp.text:
|
|
||||||
print(f"Response: {resp.text}")
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"✗ OTA upload failed with status {resp.status_code}")
|
|
||||||
print(f"Response: {resp.text}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Error during upload: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def test_ota_with_wrong_content_type(host, port):
|
|
||||||
"""Test that OTA upload fails gracefully with wrong content type"""
|
|
||||||
base_url = f"http://{host}:{port}"
|
|
||||||
|
|
||||||
print("\nTesting OTA with wrong content type...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Send plain text instead of multipart
|
|
||||||
headers = {"Content-Type": "text/plain"}
|
|
||||||
resp = requests.post(
|
|
||||||
f"{base_url}/ota/upload",
|
|
||||||
data="This is not a firmware file",
|
|
||||||
headers=headers,
|
|
||||||
timeout=10,
|
|
||||||
)
|
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
|
||||||
print(
|
|
||||||
f"✓ Server correctly rejected wrong content type (status: {resp.status_code})"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"✗ Server accepted wrong content type (status: {resp.status_code})")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def test_ota_with_empty_file(host, port):
|
|
||||||
"""Test that OTA upload fails gracefully with empty file"""
|
|
||||||
base_url = f"http://{host}:{port}"
|
|
||||||
|
|
||||||
print("\nTesting OTA with empty file...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Send empty file
|
|
||||||
files = {"firmware": ("empty.bin", b"", "application/octet-stream")}
|
|
||||||
resp = requests.post(f"{base_url}/ota/upload", files=files, timeout=10)
|
|
||||||
|
|
||||||
if resp.status_code >= 400:
|
|
||||||
print(
|
|
||||||
f"✓ Server correctly rejected empty file (status: {resp.status_code})"
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
print(f"✗ Server accepted empty file (status: {resp.status_code})")
|
|
||||||
return False
|
|
||||||
|
|
||||||
except requests.exceptions.RequestException as e:
|
|
||||||
print(f"Error: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def create_test_firmware(size_kb=10):
|
|
||||||
"""Create a dummy firmware file for testing"""
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
|
|
||||||
# ESP32 firmware magic bytes
|
|
||||||
f.write(b"\xe9\x08\x02\x20")
|
|
||||||
# Add padding
|
|
||||||
f.write(b"\x00" * (size_kb * 1024 - 4))
|
|
||||||
return f.name
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Test ESP-IDF web server OTA functionality"
|
|
||||||
)
|
|
||||||
parser.add_argument("--host", default="localhost", help="Device hostname or IP")
|
|
||||||
parser.add_argument("--port", type=int, default=8080, help="Web server port")
|
|
||||||
parser.add_argument(
|
|
||||||
"--firmware", help="Path to firmware file (if not specified, creates test file)"
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--skip-error-tests", action="store_true", help="Skip error condition tests"
|
|
||||||
)
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
# Create test firmware if not specified
|
|
||||||
firmware_path = args.firmware
|
|
||||||
if not firmware_path:
|
|
||||||
print("Creating test firmware file...")
|
|
||||||
firmware_path = create_test_firmware(100) # 100KB test file
|
|
||||||
print(f"Created test firmware: {firmware_path}")
|
|
||||||
|
|
||||||
all_passed = True
|
|
||||||
|
|
||||||
# Test successful OTA upload
|
|
||||||
if not test_multipart_ota_upload(args.host, args.port, firmware_path):
|
|
||||||
all_passed = False
|
|
||||||
|
|
||||||
# Test error conditions
|
|
||||||
if not args.skip_error_tests:
|
|
||||||
time.sleep(1) # Small delay between tests
|
|
||||||
|
|
||||||
if not test_ota_with_wrong_content_type(args.host, args.port):
|
|
||||||
all_passed = False
|
|
||||||
|
|
||||||
time.sleep(1)
|
|
||||||
|
|
||||||
if not test_ota_with_empty_file(args.host, args.port):
|
|
||||||
all_passed = False
|
|
||||||
|
|
||||||
# Clean up test firmware if we created it
|
|
||||||
if not args.firmware:
|
|
||||||
import os
|
|
||||||
|
|
||||||
os.unlink(firmware_path)
|
|
||||||
print("\nCleaned up test firmware")
|
|
||||||
|
|
||||||
print(f"\n{'All tests passed!' if all_passed else 'Some tests failed!'}")
|
|
||||||
return 0 if all_passed else 1
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
@@ -1,70 +0,0 @@
|
|||||||
# Testing ESP-IDF Web Server OTA Functionality
|
|
||||||
|
|
||||||
This directory contains tests for the ESP-IDF web server OTA (Over-The-Air) update functionality using multipart form uploads.
|
|
||||||
|
|
||||||
## Test Files
|
|
||||||
|
|
||||||
- `test_ota.esp32-idf.yaml` - ESPHome configuration with OTA enabled for ESP-IDF
|
|
||||||
- `test_no_ota.esp32-idf.yaml` - ESPHome configuration with OTA disabled
|
|
||||||
- `test_ota_disabled.esp32-idf.yaml` - ESPHome configuration with web_server ota: false
|
|
||||||
- `test_multipart_ota.py` - Manual test script for OTA functionality
|
|
||||||
- `test_esp_idf_ota.py` - Automated pytest for OTA functionality
|
|
||||||
|
|
||||||
## Running the Tests
|
|
||||||
|
|
||||||
### 1. Compile and Flash Test Device
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Compile the OTA-enabled configuration
|
|
||||||
esphome compile tests/components/web_server/test_ota.esp32-idf.yaml
|
|
||||||
|
|
||||||
# Flash to device
|
|
||||||
esphome upload tests/components/web_server/test_ota.esp32-idf.yaml
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Run Manual Tests
|
|
||||||
|
|
||||||
Once the device is running, you can test OTA functionality:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Test with default settings (creates test firmware)
|
|
||||||
python tests/components/web_server/test_multipart_ota.py --host <device-ip>
|
|
||||||
|
|
||||||
# Test with real firmware file
|
|
||||||
python tests/components/web_server/test_multipart_ota.py --host <device-ip> --firmware <path-to-firmware.bin>
|
|
||||||
|
|
||||||
# Skip error condition tests (useful for production devices)
|
|
||||||
python tests/components/web_server/test_multipart_ota.py --host <device-ip> --skip-error-tests
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Run Automated Tests
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Run pytest suite
|
|
||||||
pytest tests/component_tests/web_server/test_esp_idf_ota.py
|
|
||||||
```
|
|
||||||
|
|
||||||
## What's Being Tested
|
|
||||||
|
|
||||||
1. **Multipart Upload**: Tests that firmware can be uploaded using multipart/form-data
|
|
||||||
2. **Error Handling**:
|
|
||||||
- Wrong content type rejection
|
|
||||||
- Empty file rejection
|
|
||||||
- Concurrent upload handling
|
|
||||||
3. **Large Files**: Tests chunked processing of larger firmware files
|
|
||||||
4. **Boundary Parsing**: Tests various multipart boundary formats
|
|
||||||
|
|
||||||
## Implementation Details
|
|
||||||
|
|
||||||
The ESP-IDF web server uses the `multipart-parser` library to handle multipart uploads. Key components:
|
|
||||||
|
|
||||||
- `MultipartReader` class for parsing multipart data
|
|
||||||
- Chunked processing to handle large files without excessive memory use
|
|
||||||
- Integration with ESPHome's OTA component for actual firmware updates
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
1. **Connection Refused**: Make sure the device is on the network and the IP is correct
|
|
||||||
2. **404 Not Found**: Ensure OTA is enabled in the configuration (`ota: true` in web_server)
|
|
||||||
3. **Upload Fails**: Check device logs for detailed error messages
|
|
||||||
4. **Timeout**: Large firmware files may take time, increase timeout if needed
|
|
Reference in New Issue
Block a user