mirror of
https://gitee.com/beecue/fastbee.git
synced 2025-12-20 01:45:55 +08:00
添加智能灯固件代码
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(tests)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := tests
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import os
|
||||
|
||||
from tiny_test_fw import Utility
|
||||
import ttfw_idf
|
||||
from idf_http_server_test import test as client
|
||||
|
||||
# When running on local machine execute the following before running this script
|
||||
# > make app bootloader
|
||||
# > make print_flash_cmd | tail -n 1 > build/download.config
|
||||
|
||||
# Due to connectivity issues (between runner host and DUT) in the runner environment,
|
||||
# some of the `advanced_tests` are ignored. These tests are intended for verifying
|
||||
# the expected limits of the http_server capabilities, and implement sending and receiving
|
||||
# of large HTTP packets and malformed requests, running multiple parallel sessions, etc.
|
||||
# It is advised that all these tests be run locally, when making changes or adding new
|
||||
# features to this component.
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_http_server_advanced(env, extra_data):
|
||||
# Acquire DUT
|
||||
dut1 = env.get_dut("http_server", "examples/protocols/http_server/advanced_tests", dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
# Get binary file
|
||||
binary_file = os.path.join(dut1.app.binary_path, "tests.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("http_server_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("http_server_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
|
||||
# Upload binary and start testing
|
||||
Utility.console_log("Starting http_server advanced test app")
|
||||
dut1.start_app()
|
||||
|
||||
# Parse IP address of STA
|
||||
Utility.console_log("Waiting to connect with AP")
|
||||
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)IPv4 address: (\d+.\d+.\d+.\d+)"), timeout=30)[0]
|
||||
|
||||
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Started HTTP server on port: '(\d+)'"), timeout=15)[0]
|
||||
result = dut1.expect(re.compile(r"(?:[\s\S]*)Max URI handlers: '(\d+)'(?:[\s\S]*)Max Open Sessions: " # noqa: W605
|
||||
r"'(\d+)'(?:[\s\S]*)Max Header Length: '(\d+)'(?:[\s\S]*)Max URI Length: "
|
||||
r"'(\d+)'(?:[\s\S]*)Max Stack Size: '(\d+)'"), timeout=15)
|
||||
# max_uri_handlers = int(result[0])
|
||||
max_sessions = int(result[1])
|
||||
max_hdr_len = int(result[2])
|
||||
max_uri_len = int(result[3])
|
||||
max_stack_size = int(result[4])
|
||||
|
||||
Utility.console_log("Got IP : " + got_ip)
|
||||
Utility.console_log("Got Port : " + got_port)
|
||||
|
||||
# Run test script
|
||||
# If failed raise appropriate exception
|
||||
failed = False
|
||||
|
||||
Utility.console_log("Sessions and Context Tests...")
|
||||
if not client.spillover_session(got_ip, got_port, max_sessions):
|
||||
Utility.console_log("Ignoring failure")
|
||||
if not client.parallel_sessions_adder(got_ip, got_port, max_sessions):
|
||||
Utility.console_log("Ignoring failure")
|
||||
if not client.leftover_data_test(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.async_response_test(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.recv_timeout_test(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.arbitrary_termination_test(got_ip, got_port):
|
||||
failed = True
|
||||
|
||||
# This test fails a lot! Enable when connection is stable
|
||||
# test_size = 50*1024 # 50KB
|
||||
# if not client.packet_size_limit_test(got_ip, got_port, test_size):
|
||||
# Utility.console_log("Ignoring failure")
|
||||
|
||||
Utility.console_log("Getting initial stack usage...")
|
||||
if not client.get_hello(got_ip, got_port):
|
||||
failed = True
|
||||
|
||||
inital_stack = int(dut1.expect(re.compile(r"(?:[\s\S]*)Free Stack for server task: '(\d+)'"), timeout=15)[0])
|
||||
|
||||
if inital_stack < 0.1 * max_stack_size:
|
||||
Utility.console_log("More than 90% of stack being used on server start")
|
||||
failed = True
|
||||
|
||||
Utility.console_log("Basic HTTP Client Tests...")
|
||||
if not client.get_hello(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.post_hello(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.put_hello(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.post_echo(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.get_echo(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.put_echo(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.get_hello_type(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.get_hello_status(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.get_false_uri(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.get_test_headers(got_ip, got_port):
|
||||
failed = True
|
||||
|
||||
Utility.console_log("Error code tests...")
|
||||
if not client.code_500_server_error_test(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.code_501_method_not_impl(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.code_505_version_not_supported(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.code_400_bad_request(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.code_404_not_found(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.code_405_method_not_allowed(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.code_408_req_timeout(got_ip, got_port):
|
||||
failed = True
|
||||
if not client.code_414_uri_too_long(got_ip, got_port, max_uri_len):
|
||||
Utility.console_log("Ignoring failure")
|
||||
if not client.code_431_hdr_too_long(got_ip, got_port, max_hdr_len):
|
||||
Utility.console_log("Ignoring failure")
|
||||
if not client.test_upgrade_not_supported(got_ip, got_port):
|
||||
failed = True
|
||||
|
||||
Utility.console_log("Getting final stack usage...")
|
||||
if not client.get_hello(got_ip, got_port):
|
||||
failed = True
|
||||
|
||||
final_stack = int(dut1.expect(re.compile(r"(?:[\s\S]*)Free Stack for server task: '(\d+)'"), timeout=15)[0])
|
||||
|
||||
if final_stack < 0.05 * max_stack_size:
|
||||
Utility.console_log("More than 95% of stack got used during tests")
|
||||
failed = True
|
||||
|
||||
if failed:
|
||||
raise RuntimeError
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_http_server_advanced()
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
"tests.c"
|
||||
INCLUDE_DIRS "." "include")
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
#ifndef __HTTPD_TESTS_H__
|
||||
#define __HTTPD_TESTS_H__
|
||||
|
||||
#include <esp_http_server.h>
|
||||
|
||||
extern httpd_handle_t start_tests(void);
|
||||
extern void stop_tests(httpd_handle_t hd);
|
||||
|
||||
#endif // __HTTPD_TESTS_H__
|
||||
@@ -0,0 +1,72 @@
|
||||
/* HTTP Server Tests
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include "tests.h"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server) {
|
||||
ESP_LOGI(TAG, "Stopping webserver");
|
||||
stop_tests(*server);
|
||||
*server = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server == NULL) {
|
||||
ESP_LOGI(TAG, "Starting webserver");
|
||||
*server = start_tests();
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static httpd_handle_t server = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
|
||||
* and re-start it upon connection.
|
||||
*/
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
/* Start the server for the first time */
|
||||
server = start_tests();
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_system.h>
|
||||
#include <esp_http_server.h>
|
||||
|
||||
#include "tests.h"
|
||||
|
||||
static const char *TAG = "TESTS";
|
||||
|
||||
static int pre_start_mem, post_stop_mem;
|
||||
|
||||
struct async_resp_arg {
|
||||
httpd_handle_t hd;
|
||||
int fd;
|
||||
};
|
||||
|
||||
/********************* Basic Handlers Start *******************/
|
||||
|
||||
static esp_err_t hello_get_handler(httpd_req_t *req)
|
||||
{
|
||||
#define STR "Hello World!"
|
||||
ESP_LOGI(TAG, "Free Stack for server task: '%d'", uxTaskGetStackHighWaterMark(NULL));
|
||||
httpd_resp_send(req, STR, strlen(STR));
|
||||
return ESP_OK;
|
||||
#undef STR
|
||||
}
|
||||
|
||||
/* This handler is intended to check what happens in case of empty values of headers.
|
||||
* Here `Header2` is an empty header and `Header1` and `Header3` will have `Value1`
|
||||
* and `Value3` in them. */
|
||||
static esp_err_t test_header_get_handler(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
|
||||
int buf_len;
|
||||
char *buf;
|
||||
|
||||
buf_len = httpd_req_get_hdr_value_len(req, "Header1");
|
||||
if (buf_len > 0) {
|
||||
buf = malloc(++buf_len);
|
||||
if (!buf) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory of %d bytes!", buf_len);
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Memory allocation failed");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
/* Copy null terminated value string into buffer */
|
||||
if (httpd_req_get_hdr_value_str(req, "Header1", buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Header1 content: %s", buf);
|
||||
if (strcmp("Value1", buf) != 0) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Wrong value of Header1 received");
|
||||
free(buf);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Expected value and received value matched for Header1");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error in getting value of Header1");
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Error in getting value of Header1");
|
||||
free(buf);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
free(buf);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Header1 not found");
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Header1 not found");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
buf_len = httpd_req_get_hdr_value_len(req, "Header3");
|
||||
if (buf_len > 0) {
|
||||
buf = malloc(++buf_len);
|
||||
if (!buf) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory of %d bytes!", buf_len);
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Memory allocation failed");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
/* Copy null terminated value string into buffer */
|
||||
if (httpd_req_get_hdr_value_str(req, "Header3", buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Header3 content: %s", buf);
|
||||
if (strcmp("Value3", buf) != 0) {
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Wrong value of Header3 received");
|
||||
free(buf);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Expected value and received value matched for Header3");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Error in getting value of Header3");
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Error in getting value of Header3");
|
||||
free(buf);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
free(buf);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Header3 not found");
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Header3 not found");
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
buf_len = httpd_req_get_hdr_value_len(req, "Header2");
|
||||
buf = malloc(++buf_len);
|
||||
if (!buf) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory of %d bytes!", buf_len);
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Memory allocation failed");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
if (httpd_req_get_hdr_value_str(req, "Header2", buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Header2 content: %s", buf);
|
||||
httpd_resp_send(req, buf, strlen(buf));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Header2 not found");
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Header2 not found");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t hello_type_get_handler(httpd_req_t *req)
|
||||
{
|
||||
#define STR "Hello World!"
|
||||
httpd_resp_set_type(req, HTTPD_TYPE_TEXT);
|
||||
httpd_resp_send(req, STR, strlen(STR));
|
||||
return ESP_OK;
|
||||
#undef STR
|
||||
}
|
||||
|
||||
static esp_err_t hello_status_get_handler(httpd_req_t *req)
|
||||
{
|
||||
#define STR "Hello World!"
|
||||
httpd_resp_set_status(req, HTTPD_500);
|
||||
httpd_resp_send(req, STR, strlen(STR));
|
||||
return ESP_OK;
|
||||
#undef STR
|
||||
}
|
||||
|
||||
static esp_err_t echo_post_handler(httpd_req_t *req)
|
||||
{
|
||||
ESP_LOGI(TAG, "/echo handler read content length %d", req->content_len);
|
||||
|
||||
char* buf = malloc(req->content_len + 1);
|
||||
size_t off = 0;
|
||||
int ret;
|
||||
|
||||
if (!buf) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory of %d bytes!", req->content_len + 1);
|
||||
httpd_resp_send_500(req);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
while (off < req->content_len) {
|
||||
/* Read data received in the request */
|
||||
ret = httpd_req_recv(req, buf + off, req->content_len - off);
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
free (buf);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
off += ret;
|
||||
ESP_LOGI(TAG, "/echo handler recv length %d", ret);
|
||||
}
|
||||
buf[off] = '\0';
|
||||
|
||||
if (req->content_len < 128) {
|
||||
ESP_LOGI(TAG, "/echo handler read %s", buf);
|
||||
}
|
||||
|
||||
/* Search for Custom header field */
|
||||
char* req_hdr = 0;
|
||||
size_t hdr_len = httpd_req_get_hdr_value_len(req, "Custom");
|
||||
if (hdr_len) {
|
||||
/* Read Custom header value */
|
||||
req_hdr = malloc(hdr_len + 1);
|
||||
if (!req_hdr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory of %d bytes!", hdr_len + 1);
|
||||
httpd_resp_send_500(req);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
httpd_req_get_hdr_value_str(req, "Custom", req_hdr, hdr_len + 1);
|
||||
|
||||
/* Set as additional header for response packet */
|
||||
httpd_resp_set_hdr(req, "Custom", req_hdr);
|
||||
}
|
||||
httpd_resp_send(req, buf, req->content_len);
|
||||
free (req_hdr);
|
||||
free (buf);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void adder_free_func(void *ctx)
|
||||
{
|
||||
ESP_LOGI(TAG, "Custom Free Context function called");
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
/* Create a context, keep incrementing value in the context, by whatever was
|
||||
* received. Return the result
|
||||
*/
|
||||
static esp_err_t adder_post_handler(httpd_req_t *req)
|
||||
{
|
||||
char buf[10];
|
||||
char outbuf[50];
|
||||
int ret;
|
||||
|
||||
/* Read data received in the request */
|
||||
ret = httpd_req_recv(req, buf, sizeof(buf));
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
buf[ret] = '\0';
|
||||
int val = atoi(buf);
|
||||
ESP_LOGI(TAG, "/adder handler read %d", val);
|
||||
|
||||
if (! req->sess_ctx) {
|
||||
ESP_LOGI(TAG, "/adder allocating new session");
|
||||
req->sess_ctx = malloc(sizeof(int));
|
||||
req->free_ctx = adder_free_func;
|
||||
*(int *)req->sess_ctx = 0;
|
||||
}
|
||||
int *adder = (int *)req->sess_ctx;
|
||||
*adder += val;
|
||||
|
||||
snprintf(outbuf, sizeof(outbuf),"%d", *adder);
|
||||
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t leftover_data_post_handler(httpd_req_t *req)
|
||||
{
|
||||
/* Only echo the first 10 bytes of the request, leaving the rest of the
|
||||
* request data as is.
|
||||
*/
|
||||
char buf[11];
|
||||
int ret;
|
||||
|
||||
/* Read data received in the request */
|
||||
ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
buf[ret] = '\0';
|
||||
ESP_LOGI(TAG, "leftover data handler read %s", buf);
|
||||
httpd_resp_send(req, buf, strlen(buf));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
extern int httpd_default_send(httpd_handle_t hd, int sockfd, const char *buf, unsigned buf_len, int flags);
|
||||
|
||||
static void generate_async_resp(void *arg)
|
||||
{
|
||||
char buf[250];
|
||||
struct async_resp_arg *resp_arg = (struct async_resp_arg *)arg;
|
||||
httpd_handle_t hd = resp_arg->hd;
|
||||
int fd = resp_arg->fd;
|
||||
#define HTTPD_HDR_STR "HTTP/1.1 200 OK\r\n" \
|
||||
"Content-Type: text/html\r\n" \
|
||||
"Content-Length: %d\r\n"
|
||||
#define STR "Hello Double World!"
|
||||
|
||||
ESP_LOGI(TAG, "Executing queued work fd : %d", fd);
|
||||
|
||||
snprintf(buf, sizeof(buf), HTTPD_HDR_STR,
|
||||
strlen(STR));
|
||||
httpd_default_send(hd, fd, buf, strlen(buf), 0);
|
||||
/* Space for sending additional headers based on set_header */
|
||||
httpd_default_send(hd, fd, "\r\n", strlen("\r\n"), 0);
|
||||
httpd_default_send(hd, fd, STR, strlen(STR), 0);
|
||||
#undef STR
|
||||
free(arg);
|
||||
}
|
||||
|
||||
static esp_err_t async_get_handler(httpd_req_t *req)
|
||||
{
|
||||
#define STR "Hello World!"
|
||||
httpd_resp_send(req, STR, strlen(STR));
|
||||
/* Also register a HTTPD Work which sends the same data on the same
|
||||
* socket again
|
||||
*/
|
||||
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
|
||||
resp_arg->hd = req->handle;
|
||||
resp_arg->fd = httpd_req_to_sockfd(req);
|
||||
if (resp_arg->fd < 0) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Queuing work fd : %d", resp_arg->fd);
|
||||
httpd_queue_work(req->handle, generate_async_resp, resp_arg);
|
||||
return ESP_OK;
|
||||
#undef STR
|
||||
}
|
||||
|
||||
|
||||
static const httpd_uri_t basic_handlers[] = {
|
||||
{ .uri = "/hello/type_html",
|
||||
.method = HTTP_GET,
|
||||
.handler = hello_type_get_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/test_header",
|
||||
.method = HTTP_GET,
|
||||
.handler = test_header_get_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/hello",
|
||||
.method = HTTP_GET,
|
||||
.handler = hello_get_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/hello/status_500",
|
||||
.method = HTTP_GET,
|
||||
.handler = hello_status_get_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/echo",
|
||||
.method = HTTP_POST,
|
||||
.handler = echo_post_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/echo",
|
||||
.method = HTTP_PUT,
|
||||
.handler = echo_post_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/leftover_data",
|
||||
.method = HTTP_POST,
|
||||
.handler = leftover_data_post_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/adder",
|
||||
.method = HTTP_POST,
|
||||
.handler = adder_post_handler,
|
||||
.user_ctx = NULL,
|
||||
},
|
||||
{ .uri = "/async_data",
|
||||
.method = HTTP_GET,
|
||||
.handler = async_get_handler,
|
||||
.user_ctx = NULL,
|
||||
}
|
||||
};
|
||||
|
||||
static const int basic_handlers_no = sizeof(basic_handlers)/sizeof(httpd_uri_t);
|
||||
|
||||
static void register_basic_handlers(httpd_handle_t hd)
|
||||
{
|
||||
int i;
|
||||
ESP_LOGI(TAG, "Registering basic handlers");
|
||||
ESP_LOGI(TAG, "No of handlers = %d", basic_handlers_no);
|
||||
for (i = 0; i < basic_handlers_no; i++) {
|
||||
if (httpd_register_uri_handler(hd, &basic_handlers[i]) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "register uri failed for %d", i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Success");
|
||||
}
|
||||
|
||||
static httpd_handle_t test_httpd_start(void)
|
||||
{
|
||||
pre_start_mem = esp_get_free_heap_size();
|
||||
httpd_handle_t hd;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
/* Modify this setting to match the number of test URI handlers */
|
||||
config.max_uri_handlers = 9;
|
||||
config.server_port = 1234;
|
||||
|
||||
/* This check should be a part of http_server */
|
||||
config.max_open_sockets = (CONFIG_LWIP_MAX_SOCKETS - 3);
|
||||
|
||||
if (httpd_start(&hd, &config) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Started HTTP server on port: '%d'", config.server_port);
|
||||
ESP_LOGI(TAG, "Max URI handlers: '%d'", config.max_uri_handlers);
|
||||
ESP_LOGI(TAG, "Max Open Sessions: '%d'", config.max_open_sockets);
|
||||
ESP_LOGI(TAG, "Max Header Length: '%d'", HTTPD_MAX_REQ_HDR_LEN);
|
||||
ESP_LOGI(TAG, "Max URI Length: '%d'", HTTPD_MAX_URI_LEN);
|
||||
ESP_LOGI(TAG, "Max Stack Size: '%d'", config.stack_size);
|
||||
return hd;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_httpd_stop(httpd_handle_t hd)
|
||||
{
|
||||
httpd_stop(hd);
|
||||
post_stop_mem = esp_get_free_heap_size();
|
||||
ESP_LOGI(TAG, "HTTPD Stop: Current free memory: %d", post_stop_mem);
|
||||
}
|
||||
|
||||
httpd_handle_t start_tests(void)
|
||||
{
|
||||
httpd_handle_t hd = test_httpd_start();
|
||||
if (hd) {
|
||||
register_basic_handlers(hd);
|
||||
}
|
||||
return hd;
|
||||
}
|
||||
|
||||
void stop_tests(httpd_handle_t hd)
|
||||
{
|
||||
ESP_LOGI(TAG, "Stopping httpd");
|
||||
test_httpd_stop(hd);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(file_server)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := file_server
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# Simple HTTP File Server Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
HTTP file server example demonstrates file serving with both upload and download capability, using the `esp_http_server` component of ESP-IDF. The following URIs are provided by the server:
|
||||
|
||||
| URI | Method | Description |
|
||||
|----------------------|---------|-------------------------------------------------------------------------------------------|
|
||||
|`index.html` | GET | Redirects to `/` |
|
||||
|`favicon.ico` | GET | Browsers use this path to retrieve page icon which is embedded in flash |
|
||||
|`/` | GET | Responds with webpage displaying list of files on SPIFFS and form for uploading new files |
|
||||
|`/<file path>` | GET | For downloading files stored on SPIFFS |
|
||||
|`/upload/<file path>` | POST | For uploading files on to SPIFFS. Files are sent as body of HTTP post requests |
|
||||
|`/delete/<file path>` | POST | Command for deleting a file from SPIFFS |
|
||||
|
||||
File server implementation can be found under `main/file_server.c` which uses SPIFFS for file storage. `main/upload_script.html` has some HTML, JavaScript and Ajax content used for file uploading, which is embedded in the flash image and used as it is when generating the home page of the file server.
|
||||
|
||||
## Note
|
||||
|
||||
`/index.html` and `/favicon.ico` can be overridden by uploading files with same pathname to SPIFFS.
|
||||
|
||||
## Usage
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`) go to `Example Configuration` ->
|
||||
1. WIFI SSID: WIFI network to which your PC is also connected to.
|
||||
2. WIFI Password: WIFI password
|
||||
|
||||
* In order to test the file server demo :
|
||||
1. compile and burn the firmware `idf.py -p PORT flash`
|
||||
2. run `idf.py -p PORT monitor` and note down the IP assigned to your ESP module. The default port is 80
|
||||
3. test the example interactively on a web browser (assuming IP is 192.168.43.130):
|
||||
1. open path `http://192.168.43.130/` or `http://192.168.43.130/index.html` to see an HTML web page with list of files on the server (initially empty)
|
||||
2. use the file upload form on the webpage to select and upload a file to the server
|
||||
3. click a file link to download / open the file on browser (if supported)
|
||||
4. click the delete link visible next to each file entry to delete them
|
||||
4. test the example using curl (assuming IP is 192.168.43.130):
|
||||
1. `myfile.html` is uploaded to `/path/on/device/myfile_copy.html` using `curl -X POST --data-binary @myfile.html 192.168.43.130:80/upload/path/on/device/myfile_copy.html`
|
||||
2. download the uploaded copy back : `curl 192.168.43.130:80/path/on/device/myfile_copy.html > myfile_copy.html`
|
||||
3. compare the copy with the original using `cmp myfile.html myfile_copy.html`
|
||||
|
||||
## Note
|
||||
|
||||
Browsers often send large header fields when an HTML form is submit. Therefore, for the purpose of this example, `HTTPD_MAX_REQ_HDR_LEN` has been increased to 1024 in `sdkconfig.defaults`. User can adjust this value as per their requirement, keeping in mind the memory constraint of the hardware in use.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c" "file_server.c"
|
||||
INCLUDE_DIRS "."
|
||||
EMBED_FILES "favicon.ico" "upload_script.html")
|
||||
@@ -0,0 +1,7 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_EMBED_FILES := favicon.ico
|
||||
COMPONENT_EMBED_FILES += upload_script.html
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
@@ -0,0 +1,503 @@
|
||||
/* HTTP File Server Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
#include <sys/unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "esp_vfs.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "esp_http_server.h"
|
||||
|
||||
/* Max length a file path can have on storage */
|
||||
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + CONFIG_SPIFFS_OBJ_NAME_LEN)
|
||||
|
||||
/* Max size of an individual file. Make sure this
|
||||
* value is same as that set in upload_script.html */
|
||||
#define MAX_FILE_SIZE (200*1024) // 200 KB
|
||||
#define MAX_FILE_SIZE_STR "200KB"
|
||||
|
||||
/* Scratch buffer size */
|
||||
#define SCRATCH_BUFSIZE 8192
|
||||
|
||||
struct file_server_data {
|
||||
/* Base path of file storage */
|
||||
char base_path[ESP_VFS_PATH_MAX + 1];
|
||||
|
||||
/* Scratch buffer for temporary storage during file transfer */
|
||||
char scratch[SCRATCH_BUFSIZE];
|
||||
};
|
||||
|
||||
static const char *TAG = "file_server";
|
||||
|
||||
/* Handler to redirect incoming GET request for /index.html to /
|
||||
* This can be overridden by uploading file with same name */
|
||||
static esp_err_t index_html_get_handler(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_status(req, "307 Temporary Redirect");
|
||||
httpd_resp_set_hdr(req, "Location", "/");
|
||||
httpd_resp_send(req, NULL, 0); // Response body can be empty
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Handler to respond with an icon file embedded in flash.
|
||||
* Browsers expect to GET website icon at URI /favicon.ico.
|
||||
* This can be overridden by uploading file with same name */
|
||||
static esp_err_t favicon_get_handler(httpd_req_t *req)
|
||||
{
|
||||
extern const unsigned char favicon_ico_start[] asm("_binary_favicon_ico_start");
|
||||
extern const unsigned char favicon_ico_end[] asm("_binary_favicon_ico_end");
|
||||
const size_t favicon_ico_size = (favicon_ico_end - favicon_ico_start);
|
||||
httpd_resp_set_type(req, "image/x-icon");
|
||||
httpd_resp_send(req, (const char *)favicon_ico_start, favicon_ico_size);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Send HTTP response with a run-time generated html consisting of
|
||||
* a list of all files and folders under the requested path.
|
||||
* In case of SPIFFS this returns empty list when path is any
|
||||
* string other than '/', since SPIFFS doesn't support directories */
|
||||
static esp_err_t http_resp_dir_html(httpd_req_t *req, const char *dirpath)
|
||||
{
|
||||
char entrypath[FILE_PATH_MAX];
|
||||
char entrysize[16];
|
||||
const char *entrytype;
|
||||
|
||||
struct dirent *entry;
|
||||
struct stat entry_stat;
|
||||
|
||||
DIR *dir = opendir(dirpath);
|
||||
const size_t dirpath_len = strlen(dirpath);
|
||||
|
||||
/* Retrieve the base path of file storage to construct the full path */
|
||||
strlcpy(entrypath, dirpath, sizeof(entrypath));
|
||||
|
||||
if (!dir) {
|
||||
ESP_LOGE(TAG, "Failed to stat dir : %s", dirpath);
|
||||
/* Respond with 404 Not Found */
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Directory does not exist");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Send HTML file header */
|
||||
httpd_resp_sendstr_chunk(req, "<!DOCTYPE html><html><body>");
|
||||
|
||||
/* Get handle to embedded file upload script */
|
||||
extern const unsigned char upload_script_start[] asm("_binary_upload_script_html_start");
|
||||
extern const unsigned char upload_script_end[] asm("_binary_upload_script_html_end");
|
||||
const size_t upload_script_size = (upload_script_end - upload_script_start);
|
||||
|
||||
/* Add file upload form and script which on execution sends a POST request to /upload */
|
||||
httpd_resp_send_chunk(req, (const char *)upload_script_start, upload_script_size);
|
||||
|
||||
/* Send file-list table definition and column labels */
|
||||
httpd_resp_sendstr_chunk(req,
|
||||
"<table class=\"fixed\" border=\"1\">"
|
||||
"<col width=\"800px\" /><col width=\"300px\" /><col width=\"300px\" /><col width=\"100px\" />"
|
||||
"<thead><tr><th>Name</th><th>Type</th><th>Size (Bytes)</th><th>Delete</th></tr></thead>"
|
||||
"<tbody>");
|
||||
|
||||
/* Iterate over all files / folders and fetch their names and sizes */
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
entrytype = (entry->d_type == DT_DIR ? "directory" : "file");
|
||||
|
||||
strlcpy(entrypath + dirpath_len, entry->d_name, sizeof(entrypath) - dirpath_len);
|
||||
if (stat(entrypath, &entry_stat) == -1) {
|
||||
ESP_LOGE(TAG, "Failed to stat %s : %s", entrytype, entry->d_name);
|
||||
continue;
|
||||
}
|
||||
sprintf(entrysize, "%ld", entry_stat.st_size);
|
||||
ESP_LOGI(TAG, "Found %s : %s (%s bytes)", entrytype, entry->d_name, entrysize);
|
||||
|
||||
/* Send chunk of HTML file containing table entries with file name and size */
|
||||
httpd_resp_sendstr_chunk(req, "<tr><td><a href=\"");
|
||||
httpd_resp_sendstr_chunk(req, req->uri);
|
||||
httpd_resp_sendstr_chunk(req, entry->d_name);
|
||||
if (entry->d_type == DT_DIR) {
|
||||
httpd_resp_sendstr_chunk(req, "/");
|
||||
}
|
||||
httpd_resp_sendstr_chunk(req, "\">");
|
||||
httpd_resp_sendstr_chunk(req, entry->d_name);
|
||||
httpd_resp_sendstr_chunk(req, "</a></td><td>");
|
||||
httpd_resp_sendstr_chunk(req, entrytype);
|
||||
httpd_resp_sendstr_chunk(req, "</td><td>");
|
||||
httpd_resp_sendstr_chunk(req, entrysize);
|
||||
httpd_resp_sendstr_chunk(req, "</td><td>");
|
||||
httpd_resp_sendstr_chunk(req, "<form method=\"post\" action=\"/delete");
|
||||
httpd_resp_sendstr_chunk(req, req->uri);
|
||||
httpd_resp_sendstr_chunk(req, entry->d_name);
|
||||
httpd_resp_sendstr_chunk(req, "\"><button type=\"submit\">Delete</button></form>");
|
||||
httpd_resp_sendstr_chunk(req, "</td></tr>\n");
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
/* Finish the file list table */
|
||||
httpd_resp_sendstr_chunk(req, "</tbody></table>");
|
||||
|
||||
/* Send remaining chunk of HTML file to complete it */
|
||||
httpd_resp_sendstr_chunk(req, "</body></html>");
|
||||
|
||||
/* Send empty chunk to signal HTTP response completion */
|
||||
httpd_resp_sendstr_chunk(req, NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
#define IS_FILE_EXT(filename, ext) \
|
||||
(strcasecmp(&filename[strlen(filename) - sizeof(ext) + 1], ext) == 0)
|
||||
|
||||
/* Set HTTP response content type according to file extension */
|
||||
static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filename)
|
||||
{
|
||||
if (IS_FILE_EXT(filename, ".pdf")) {
|
||||
return httpd_resp_set_type(req, "application/pdf");
|
||||
} else if (IS_FILE_EXT(filename, ".html")) {
|
||||
return httpd_resp_set_type(req, "text/html");
|
||||
} else if (IS_FILE_EXT(filename, ".jpeg")) {
|
||||
return httpd_resp_set_type(req, "image/jpeg");
|
||||
} else if (IS_FILE_EXT(filename, ".ico")) {
|
||||
return httpd_resp_set_type(req, "image/x-icon");
|
||||
}
|
||||
/* This is a limited set only */
|
||||
/* For any other type always set as plain text */
|
||||
return httpd_resp_set_type(req, "text/plain");
|
||||
}
|
||||
|
||||
/* Copies the full path into destination buffer and returns
|
||||
* pointer to path (skipping the preceding base path) */
|
||||
static const char* get_path_from_uri(char *dest, const char *base_path, const char *uri, size_t destsize)
|
||||
{
|
||||
const size_t base_pathlen = strlen(base_path);
|
||||
size_t pathlen = strlen(uri);
|
||||
|
||||
const char *quest = strchr(uri, '?');
|
||||
if (quest) {
|
||||
pathlen = MIN(pathlen, quest - uri);
|
||||
}
|
||||
const char *hash = strchr(uri, '#');
|
||||
if (hash) {
|
||||
pathlen = MIN(pathlen, hash - uri);
|
||||
}
|
||||
|
||||
if (base_pathlen + pathlen + 1 > destsize) {
|
||||
/* Full path string won't fit into destination buffer */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Construct full path (base + path) */
|
||||
strcpy(dest, base_path);
|
||||
strlcpy(dest + base_pathlen, uri, pathlen + 1);
|
||||
|
||||
/* Return pointer to path, skipping the base */
|
||||
return dest + base_pathlen;
|
||||
}
|
||||
|
||||
/* Handler to download a file kept on the server */
|
||||
static esp_err_t download_get_handler(httpd_req_t *req)
|
||||
{
|
||||
char filepath[FILE_PATH_MAX];
|
||||
FILE *fd = NULL;
|
||||
struct stat file_stat;
|
||||
|
||||
const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
|
||||
req->uri, sizeof(filepath));
|
||||
if (!filename) {
|
||||
ESP_LOGE(TAG, "Filename is too long");
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* If name has trailing '/', respond with directory contents */
|
||||
if (filename[strlen(filename) - 1] == '/') {
|
||||
return http_resp_dir_html(req, filepath);
|
||||
}
|
||||
|
||||
if (stat(filepath, &file_stat) == -1) {
|
||||
/* If file not present on SPIFFS check if URI
|
||||
* corresponds to one of the hardcoded paths */
|
||||
if (strcmp(filename, "/index.html") == 0) {
|
||||
return index_html_get_handler(req);
|
||||
} else if (strcmp(filename, "/favicon.ico") == 0) {
|
||||
return favicon_get_handler(req);
|
||||
}
|
||||
ESP_LOGE(TAG, "Failed to stat file : %s", filepath);
|
||||
/* Respond with 404 Not Found */
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "File does not exist");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
fd = fopen(filepath, "r");
|
||||
if (!fd) {
|
||||
ESP_LOGE(TAG, "Failed to read existing file : %s", filepath);
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Sending file : %s (%ld bytes)...", filename, file_stat.st_size);
|
||||
set_content_type_from_file(req, filename);
|
||||
|
||||
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||
char *chunk = ((struct file_server_data *)req->user_ctx)->scratch;
|
||||
size_t chunksize;
|
||||
do {
|
||||
/* Read file in chunks into the scratch buffer */
|
||||
chunksize = fread(chunk, 1, SCRATCH_BUFSIZE, fd);
|
||||
|
||||
if (chunksize > 0) {
|
||||
/* Send the buffer contents as HTTP response chunk */
|
||||
if (httpd_resp_send_chunk(req, chunk, chunksize) != ESP_OK) {
|
||||
fclose(fd);
|
||||
ESP_LOGE(TAG, "File sending failed!");
|
||||
/* Abort sending file */
|
||||
httpd_resp_sendstr_chunk(req, NULL);
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
|
||||
/* Keep looping till the whole file is sent */
|
||||
} while (chunksize != 0);
|
||||
|
||||
/* Close file after sending complete */
|
||||
fclose(fd);
|
||||
ESP_LOGI(TAG, "File sending complete");
|
||||
|
||||
/* Respond with an empty chunk to signal HTTP response completion */
|
||||
httpd_resp_send_chunk(req, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Handler to upload a file onto the server */
|
||||
static esp_err_t upload_post_handler(httpd_req_t *req)
|
||||
{
|
||||
char filepath[FILE_PATH_MAX];
|
||||
FILE *fd = NULL;
|
||||
struct stat file_stat;
|
||||
|
||||
/* Skip leading "/upload" from URI to get filename */
|
||||
/* Note sizeof() counts NULL termination hence the -1 */
|
||||
const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
|
||||
req->uri + sizeof("/upload") - 1, sizeof(filepath));
|
||||
if (!filename) {
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Filename cannot have a trailing '/' */
|
||||
if (filename[strlen(filename) - 1] == '/') {
|
||||
ESP_LOGE(TAG, "Invalid filename : %s", filename);
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (stat(filepath, &file_stat) == 0) {
|
||||
ESP_LOGE(TAG, "File already exists : %s", filepath);
|
||||
/* Respond with 400 Bad Request */
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File already exists");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* File cannot be larger than a limit */
|
||||
if (req->content_len > MAX_FILE_SIZE) {
|
||||
ESP_LOGE(TAG, "File too large : %d bytes", req->content_len);
|
||||
/* Respond with 400 Bad Request */
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST,
|
||||
"File size must be less than "
|
||||
MAX_FILE_SIZE_STR "!");
|
||||
/* Return failure to close underlying connection else the
|
||||
* incoming file content will keep the socket busy */
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
fd = fopen(filepath, "w");
|
||||
if (!fd) {
|
||||
ESP_LOGE(TAG, "Failed to create file : %s", filepath);
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to create file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Receiving file : %s...", filename);
|
||||
|
||||
/* Retrieve the pointer to scratch buffer for temporary storage */
|
||||
char *buf = ((struct file_server_data *)req->user_ctx)->scratch;
|
||||
int received;
|
||||
|
||||
/* Content length of the request gives
|
||||
* the size of the file being uploaded */
|
||||
int remaining = req->content_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
|
||||
ESP_LOGI(TAG, "Remaining size : %d", remaining);
|
||||
/* Receive the file part by part into a buffer */
|
||||
if ((received = httpd_req_recv(req, buf, MIN(remaining, SCRATCH_BUFSIZE))) <= 0) {
|
||||
if (received == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
/* Retry if timeout occurred */
|
||||
continue;
|
||||
}
|
||||
|
||||
/* In case of unrecoverable error,
|
||||
* close and delete the unfinished file*/
|
||||
fclose(fd);
|
||||
unlink(filepath);
|
||||
|
||||
ESP_LOGE(TAG, "File reception failed!");
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to receive file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Write buffer content to file on storage */
|
||||
if (received && (received != fwrite(buf, 1, received, fd))) {
|
||||
/* Couldn't write everything to file!
|
||||
* Storage may be full? */
|
||||
fclose(fd);
|
||||
unlink(filepath);
|
||||
|
||||
ESP_LOGE(TAG, "File write failed!");
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to write file to storage");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Keep track of remaining size of
|
||||
* the file left to be uploaded */
|
||||
remaining -= received;
|
||||
}
|
||||
|
||||
/* Close file upon upload completion */
|
||||
fclose(fd);
|
||||
ESP_LOGI(TAG, "File reception complete");
|
||||
|
||||
/* Redirect onto root to see the updated file list */
|
||||
httpd_resp_set_status(req, "303 See Other");
|
||||
httpd_resp_set_hdr(req, "Location", "/");
|
||||
httpd_resp_sendstr(req, "File uploaded successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Handler to delete a file from the server */
|
||||
static esp_err_t delete_post_handler(httpd_req_t *req)
|
||||
{
|
||||
char filepath[FILE_PATH_MAX];
|
||||
struct stat file_stat;
|
||||
|
||||
/* Skip leading "/delete" from URI to get filename */
|
||||
/* Note sizeof() counts NULL termination hence the -1 */
|
||||
const char *filename = get_path_from_uri(filepath, ((struct file_server_data *)req->user_ctx)->base_path,
|
||||
req->uri + sizeof("/delete") - 1, sizeof(filepath));
|
||||
if (!filename) {
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Filename too long");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Filename cannot have a trailing '/' */
|
||||
if (filename[strlen(filename) - 1] == '/') {
|
||||
ESP_LOGE(TAG, "Invalid filename : %s", filename);
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Invalid filename");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (stat(filepath, &file_stat) == -1) {
|
||||
ESP_LOGE(TAG, "File does not exist : %s", filename);
|
||||
/* Respond with 400 Bad Request */
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "File does not exist");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Deleting file : %s", filename);
|
||||
/* Delete file */
|
||||
unlink(filepath);
|
||||
|
||||
/* Redirect onto root to see the updated file list */
|
||||
httpd_resp_set_status(req, "303 See Other");
|
||||
httpd_resp_set_hdr(req, "Location", "/");
|
||||
httpd_resp_sendstr(req, "File deleted successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Function to start the file server */
|
||||
esp_err_t start_file_server(const char *base_path)
|
||||
{
|
||||
static struct file_server_data *server_data = NULL;
|
||||
|
||||
/* Validate file storage base path */
|
||||
if (!base_path || strcmp(base_path, "/spiffs") != 0) {
|
||||
ESP_LOGE(TAG, "File server presently supports only '/spiffs' as base path");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (server_data) {
|
||||
ESP_LOGE(TAG, "File server already started");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
/* Allocate memory for server data */
|
||||
server_data = calloc(1, sizeof(struct file_server_data));
|
||||
if (!server_data) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for server data");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
strlcpy(server_data->base_path, base_path,
|
||||
sizeof(server_data->base_path));
|
||||
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
|
||||
/* Use the URI wildcard matching function in order to
|
||||
* allow the same handler to respond to multiple different
|
||||
* target URIs which match the wildcard scheme */
|
||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||
|
||||
ESP_LOGI(TAG, "Starting HTTP Server");
|
||||
if (httpd_start(&server, &config) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start file server!");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* URI handler for getting uploaded files */
|
||||
httpd_uri_t file_download = {
|
||||
.uri = "/*", // Match all URIs of type /path/to/file
|
||||
.method = HTTP_GET,
|
||||
.handler = download_get_handler,
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_download);
|
||||
|
||||
/* URI handler for uploading files to server */
|
||||
httpd_uri_t file_upload = {
|
||||
.uri = "/upload/*", // Match all URIs of type /upload/path/to/file
|
||||
.method = HTTP_POST,
|
||||
.handler = upload_post_handler,
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_upload);
|
||||
|
||||
/* URI handler for deleting files from server */
|
||||
httpd_uri_t file_delete = {
|
||||
.uri = "/delete/*", // Match all URIs of type /delete/path/to/file
|
||||
.method = HTTP_POST,
|
||||
.handler = delete_post_handler,
|
||||
.user_ctx = server_data // Pass server data as context
|
||||
};
|
||||
httpd_register_uri_handler(server, &file_delete);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/* HTTP File Server Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <sys/param.h>
|
||||
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
/* This example demonstrates how to create file server
|
||||
* using esp_http_server. This file has only startup code.
|
||||
* Look in file_server.c for the implementation */
|
||||
|
||||
static const char *TAG="example";
|
||||
|
||||
/* Function to initialize SPIFFS */
|
||||
static esp_err_t init_spiffs(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Initializing SPIFFS");
|
||||
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiffs",
|
||||
.partition_label = NULL,
|
||||
.max_files = 5, // This decides the maximum number of files that can be created on the storage
|
||||
.format_if_mount_failed = true
|
||||
};
|
||||
|
||||
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount or format filesystem");
|
||||
} else if (ret == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_spiffs_info(NULL, &total, &used);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Declare the function which starts the file server.
|
||||
* Implementation of this function is to be found in
|
||||
* file_server.c */
|
||||
esp_err_t start_file_server(const char *base_path);
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* Initialize file storage */
|
||||
ESP_ERROR_CHECK(init_spiffs());
|
||||
|
||||
/* Start the file server */
|
||||
ESP_ERROR_CHECK(start_file_server("/spiffs"));
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<table class="fixed" border="0">
|
||||
<col width="1000px" /><col width="500px" />
|
||||
<tr><td>
|
||||
<h2>ESP32 File Server</h2>
|
||||
</td><td>
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td>
|
||||
<label for="newfile">Upload a file</label>
|
||||
</td>
|
||||
<td colspan="2">
|
||||
<input id="newfile" type="file" onchange="setpath()" style="width:100%;">
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<label for="filepath">Set path on server</label>
|
||||
</td>
|
||||
<td>
|
||||
<input id="filepath" type="text" style="width:100%;">
|
||||
</td>
|
||||
<td>
|
||||
<button id="upload" type="button" onclick="upload()">Upload</button>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
<script>
|
||||
function setpath() {
|
||||
var default_path = document.getElementById("newfile").files[0].name;
|
||||
document.getElementById("filepath").value = default_path;
|
||||
}
|
||||
function upload() {
|
||||
var filePath = document.getElementById("filepath").value;
|
||||
var upload_path = "/upload/" + filePath;
|
||||
var fileInput = document.getElementById("newfile").files;
|
||||
|
||||
/* Max size of an individual file. Make sure this
|
||||
* value is same as that set in file_server.c */
|
||||
var MAX_FILE_SIZE = 200*1024;
|
||||
var MAX_FILE_SIZE_STR = "200KB";
|
||||
|
||||
if (fileInput.length == 0) {
|
||||
alert("No file selected!");
|
||||
} else if (filePath.length == 0) {
|
||||
alert("File path on server is not set!");
|
||||
} else if (filePath.indexOf(' ') >= 0) {
|
||||
alert("File path on server cannot have spaces!");
|
||||
} else if (filePath[filePath.length-1] == '/') {
|
||||
alert("File name not specified after path!");
|
||||
} else if (fileInput[0].size > 200*1024) {
|
||||
alert("File size must be less than 200KB!");
|
||||
} else {
|
||||
document.getElementById("newfile").disabled = true;
|
||||
document.getElementById("filepath").disabled = true;
|
||||
document.getElementById("upload").disabled = true;
|
||||
|
||||
var file = fileInput[0];
|
||||
var xhttp = new XMLHttpRequest();
|
||||
xhttp.onreadystatechange = function() {
|
||||
if (xhttp.readyState == 4) {
|
||||
if (xhttp.status == 200) {
|
||||
document.open();
|
||||
document.write(xhttp.responseText);
|
||||
document.close();
|
||||
} else if (xhttp.status == 0) {
|
||||
alert("Server closed the connection abruptly!");
|
||||
location.reload()
|
||||
} else {
|
||||
alert(xhttp.status + " Error!\n" + xhttp.responseText);
|
||||
location.reload()
|
||||
}
|
||||
}
|
||||
};
|
||||
xhttp.open("POST", upload_path, true);
|
||||
xhttp.send(file);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, spiffs, , 0xF0000,
|
||||
|
@@ -0,0 +1,4 @@
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
|
||||
@@ -0,0 +1,11 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(persistent_sockets)
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := persistent_sockets
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# HTTPD Server Persistent Sockets Example
|
||||
|
||||
The Example consists of HTTPD server persistent sockets demo.
|
||||
This sort of persistency enables the server to have independent sessions/contexts per client.
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
|
||||
* In order to test the HTTPD server persistent sockets demo :
|
||||
1. compile and burn the firmware `idf.py -p PORT flash`
|
||||
2. run `idf.py -p PORT monitor` and note down the IP assigned to your ESP module. The default port is 80
|
||||
3. run the test script "python scripts/adder.py \<IP\> \<port\> \<N\>"
|
||||
* the provided test script sends (POST) numbers from 1 to N to the server which has a URI POST handler for adding these numbers into an accumulator that is valid throughout the lifetime of the connection socket, hence persistent
|
||||
* the script does a GET before closing and displays the final value of the accumulator
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from builtins import str
|
||||
from builtins import range
|
||||
import re
|
||||
import os
|
||||
import random
|
||||
|
||||
from tiny_test_fw import Utility
|
||||
import ttfw_idf
|
||||
from idf_http_server_test import adder as client
|
||||
|
||||
# When running on local machine execute the following before running this script
|
||||
# > make app bootloader
|
||||
# > make print_flash_cmd | tail -n 1 > build/download.config
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_http_server_persistence(env, extra_data):
|
||||
# Acquire DUT
|
||||
dut1 = env.get_dut("http_server", "examples/protocols/http_server/persistent_sockets",
|
||||
dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
# Get binary file
|
||||
binary_file = os.path.join(dut1.app.binary_path, "persistent_sockets.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("http_server_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("http_server_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
|
||||
# Upload binary and start testing
|
||||
Utility.console_log("Starting http_server persistance test app")
|
||||
dut1.start_app()
|
||||
|
||||
# Parse IP address of STA
|
||||
Utility.console_log("Waiting to connect with AP")
|
||||
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)IPv4 address: (\d+.\d+.\d+.\d+)"), timeout=30)[0]
|
||||
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=30)[0]
|
||||
|
||||
Utility.console_log("Got IP : " + got_ip)
|
||||
Utility.console_log("Got Port : " + got_port)
|
||||
|
||||
# Expected Logs
|
||||
dut1.expect("Registering URI handlers", timeout=30)
|
||||
|
||||
# Run test script
|
||||
conn = client.start_session(got_ip, got_port)
|
||||
visitor = 0
|
||||
adder = 0
|
||||
|
||||
# Test PUT request and initialize session context
|
||||
num = random.randint(0,100)
|
||||
client.putreq(conn, "/adder", str(num))
|
||||
visitor += 1
|
||||
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||
dut1.expect("/adder PUT handler read " + str(num), timeout=30)
|
||||
dut1.expect("PUT allocating new session", timeout=30)
|
||||
|
||||
# Retest PUT request and change session context value
|
||||
num = random.randint(0,100)
|
||||
Utility.console_log("Adding: " + str(num))
|
||||
client.putreq(conn, "/adder", str(num))
|
||||
visitor += 1
|
||||
adder += num
|
||||
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||
dut1.expect("/adder PUT handler read " + str(num), timeout=30)
|
||||
try:
|
||||
# Re allocation shouldn't happen
|
||||
dut1.expect("PUT allocating new session", timeout=30)
|
||||
# Not expected
|
||||
raise RuntimeError
|
||||
except Exception:
|
||||
# As expected
|
||||
pass
|
||||
|
||||
# Test POST request and session persistence
|
||||
random_nums = [random.randint(0,100) for _ in range(100)]
|
||||
for num in random_nums:
|
||||
Utility.console_log("Adding: " + str(num))
|
||||
client.postreq(conn, "/adder", str(num))
|
||||
visitor += 1
|
||||
adder += num
|
||||
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||
dut1.expect("/adder handler read " + str(num), timeout=30)
|
||||
|
||||
# Test GET request and session persistence
|
||||
Utility.console_log("Matching final sum: " + str(adder))
|
||||
if client.getreq(conn, "/adder").decode() != str(adder):
|
||||
raise RuntimeError
|
||||
visitor += 1
|
||||
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||
dut1.expect("/adder GET handler send " + str(adder), timeout=30)
|
||||
|
||||
Utility.console_log("Ending session")
|
||||
# Close connection and check for invocation of context "Free" function
|
||||
client.end_session(conn)
|
||||
dut1.expect("/adder Free Context function called", timeout=30)
|
||||
|
||||
Utility.console_log("Validating user context data")
|
||||
# Start another session to check user context data
|
||||
client.start_session(got_ip, got_port)
|
||||
num = random.randint(0,100)
|
||||
client.putreq(conn, "/adder", str(num))
|
||||
visitor += 1
|
||||
dut1.expect("/adder visitor count = " + str(visitor), timeout=30)
|
||||
dut1.expect("/adder PUT handler read " + str(num), timeout=30)
|
||||
dut1.expect("PUT allocating new session", timeout=30)
|
||||
client.end_session(conn)
|
||||
dut1.expect("/adder Free Context function called", timeout=30)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_http_server_persistence()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
/* Persistent Sockets Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_system.h>
|
||||
#include <nvs_flash.h>
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include <esp_http_server.h>
|
||||
|
||||
/* An example to demonstrate persistent sockets, with context maintained across
|
||||
* multiple requests on that socket.
|
||||
*/
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
/* Function to free context */
|
||||
static void adder_free_func(void *ctx)
|
||||
{
|
||||
ESP_LOGI(TAG, "/adder Free Context function called");
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
/* This handler keeps accumulating data that is posted to it into a per
|
||||
* socket/session context. And returns the result.
|
||||
*/
|
||||
static esp_err_t adder_post_handler(httpd_req_t *req)
|
||||
{
|
||||
/* Log total visitors */
|
||||
unsigned *visitors = (unsigned *)req->user_ctx;
|
||||
ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
|
||||
|
||||
char buf[10];
|
||||
char outbuf[50];
|
||||
int ret;
|
||||
|
||||
/* Read data received in the request */
|
||||
ret = httpd_req_recv(req, buf, sizeof(buf));
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
buf[ret] = '\0';
|
||||
int val = atoi(buf);
|
||||
ESP_LOGI(TAG, "/adder handler read %d", val);
|
||||
|
||||
/* Create session's context if not already available */
|
||||
if (! req->sess_ctx) {
|
||||
ESP_LOGI(TAG, "/adder allocating new session");
|
||||
req->sess_ctx = malloc(sizeof(int));
|
||||
req->free_ctx = adder_free_func;
|
||||
*(int *)req->sess_ctx = 0;
|
||||
}
|
||||
|
||||
/* Add the received data to the context */
|
||||
int *adder = (int *)req->sess_ctx;
|
||||
*adder += val;
|
||||
|
||||
/* Respond with the accumulated value */
|
||||
snprintf(outbuf, sizeof(outbuf),"%d", *adder);
|
||||
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* This handler gets the present value of the accumulator */
|
||||
static esp_err_t adder_get_handler(httpd_req_t *req)
|
||||
{
|
||||
/* Log total visitors */
|
||||
unsigned *visitors = (unsigned *)req->user_ctx;
|
||||
ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
|
||||
|
||||
char outbuf[50];
|
||||
|
||||
/* Create session's context if not already available */
|
||||
if (! req->sess_ctx) {
|
||||
ESP_LOGI(TAG, "/adder GET allocating new session");
|
||||
req->sess_ctx = malloc(sizeof(int));
|
||||
req->free_ctx = adder_free_func;
|
||||
*(int *)req->sess_ctx = 0;
|
||||
}
|
||||
ESP_LOGI(TAG, "/adder GET handler send %d", *(int *)req->sess_ctx);
|
||||
|
||||
/* Respond with the accumulated value */
|
||||
snprintf(outbuf, sizeof(outbuf),"%d", *((int *)req->sess_ctx));
|
||||
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* This handler resets the value of the accumulator */
|
||||
static esp_err_t adder_put_handler(httpd_req_t *req)
|
||||
{
|
||||
/* Log total visitors */
|
||||
unsigned *visitors = (unsigned *)req->user_ctx;
|
||||
ESP_LOGI(TAG, "/adder visitor count = %d", ++(*visitors));
|
||||
|
||||
char buf[10];
|
||||
char outbuf[50];
|
||||
int ret;
|
||||
|
||||
/* Read data received in the request */
|
||||
ret = httpd_req_recv(req, buf, sizeof(buf));
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
buf[ret] = '\0';
|
||||
int val = atoi(buf);
|
||||
ESP_LOGI(TAG, "/adder PUT handler read %d", val);
|
||||
|
||||
/* Create session's context if not already available */
|
||||
if (! req->sess_ctx) {
|
||||
ESP_LOGI(TAG, "/adder PUT allocating new session");
|
||||
req->sess_ctx = malloc(sizeof(int));
|
||||
req->free_ctx = adder_free_func;
|
||||
}
|
||||
*(int *)req->sess_ctx = val;
|
||||
|
||||
/* Respond with the reset value */
|
||||
snprintf(outbuf, sizeof(outbuf),"%d", *((int *)req->sess_ctx));
|
||||
httpd_resp_send(req, outbuf, strlen(outbuf));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Maintain a variable which stores the number of times
|
||||
* the "/adder" URI has been visited */
|
||||
static unsigned visitors = 0;
|
||||
|
||||
static const httpd_uri_t adder_post = {
|
||||
.uri = "/adder",
|
||||
.method = HTTP_POST,
|
||||
.handler = adder_post_handler,
|
||||
.user_ctx = &visitors
|
||||
};
|
||||
|
||||
static const httpd_uri_t adder_get = {
|
||||
.uri = "/adder",
|
||||
.method = HTTP_GET,
|
||||
.handler = adder_get_handler,
|
||||
.user_ctx = &visitors
|
||||
};
|
||||
|
||||
static const httpd_uri_t adder_put = {
|
||||
.uri = "/adder",
|
||||
.method = HTTP_PUT,
|
||||
.handler = adder_put_handler,
|
||||
.user_ctx = &visitors
|
||||
};
|
||||
|
||||
static httpd_handle_t start_webserver(void)
|
||||
{
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
// Start the httpd server
|
||||
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
||||
httpd_handle_t server;
|
||||
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
// Set URI handlers
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &adder_get);
|
||||
httpd_register_uri_handler(server, &adder_put);
|
||||
httpd_register_uri_handler(server, &adder_post);
|
||||
return server;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Error starting server!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void stop_webserver(httpd_handle_t server)
|
||||
{
|
||||
// Stop the httpd server
|
||||
httpd_stop(server);
|
||||
}
|
||||
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server) {
|
||||
ESP_LOGI(TAG, "Stopping webserver");
|
||||
stop_webserver(*server);
|
||||
*server = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server == NULL) {
|
||||
ESP_LOGI(TAG, "Starting webserver");
|
||||
*server = start_webserver();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static httpd_handle_t server = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
|
||||
* and re-start it upon connection.
|
||||
*/
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
/* Start the server for the first time */
|
||||
server = start_webserver();
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(restful_server)
|
||||
@@ -0,0 +1,15 @@
|
||||
PROJECT_NAME := restful_server
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
ifdef CONFIG_EXAMPLE_WEB_DEPLOY_SF
|
||||
WEB_SRC_DIR = $(shell pwd)/front/web-demo
|
||||
ifneq ($(wildcard $(WEB_SRC_DIR)/dist/.*),)
|
||||
SPIFFS_IMAGE_FLASH_IN_PROJECT := 1
|
||||
$(eval $(call spiffs_create_partition_image,www,$(WEB_SRC_DIR)/dist))
|
||||
else
|
||||
$(error $(WEB_SRC_DIR)/dist doesn't exist. Please run 'npm run build' in $(WEB_SRC_DIR))
|
||||
endif
|
||||
endif
|
||||
@@ -0,0 +1,137 @@
|
||||
# HTTP Restful API Server Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
This example mainly introduces how to implement a RESTful API server and HTTP server on ESP32, with a frontend browser UI.
|
||||
|
||||
This example designs several APIs to fetch resources as follows:
|
||||
|
||||
| API | Method | Resource Example | Description | Page URL |
|
||||
| -------------------------- | ------ | ----------------------------------------------------- | ---------------------------------------------------------------------------------------- | -------- |
|
||||
| `/api/v1/system/info` | `GET` | {<br />version:"v4.0-dev",<br />cores:2<br />} | Used for clients to get system information like IDF version, ESP32 cores, etc | `/` |
|
||||
| `/api/v1/temp/raw` | `GET` | {<br />raw:22<br />} | Used for clients to get raw temperature data read from sensor | `/chart` |
|
||||
| `/api/v1/light/brightness` | `POST` | { <br />red:160,<br />green:160,<br />blue:160<br />} | Used for clients to upload control values to ESP32 in order to control LED’s brightness | `/light` |
|
||||
|
||||
**Page URL** is the URL of the webpage which will send a request to the API.
|
||||
|
||||
### About mDNS
|
||||
|
||||
The IP address of an IoT device may vary from time to time, so it’s impracticable to hard code the IP address in the webpage. In this example, we use the `mDNS` to parse the domain name `esp-home.local`, so that we can alway get access to the web server by this URL no matter what the real IP address behind it. See [here](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/protocols/mdns.html) for more information about mDNS.
|
||||
|
||||
**Notes: mDNS is installed by default on most operating systems or is available as separate package.**
|
||||
|
||||
### About deploy mode
|
||||
|
||||
In development mode, it would be awful to flash the whole webpages every time we update the html, js or css files. So it is highly recommended to deploy the webpage to host PC via `semihost` technology. Whenever the browser fetch the webpage, ESP32 can forward the required files located on host PC. By this mean, it will save a lot of time when designing new pages.
|
||||
|
||||
After developing, the pages should be deployed to one of the following destinations:
|
||||
|
||||
* SPI Flash - which is recommended when the website after built is small (e.g. less than 2MB).
|
||||
* SD Card - which would be an option when the website after built is very large that the SPI Flash have not enough space to hold (e.g. larger than 2MB).
|
||||
|
||||
### About frontend framework
|
||||
|
||||
Many famous frontend frameworks (e.g. Vue, React, Angular) can be used in this example. Here we just take [Vue](https://vuejs.org/) as example and adopt the [vuetify](https://vuetifyjs.com/) as the UI library.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you need an ESP32 dev board (e.g. ESP32-WROVER Kit, ESP32-Ethernet-Kit) or ESP32 core board (e.g. ESP32-DevKitC). An extra JTAG adapter might also needed if you choose to deploy the website by semihosting. For more information about supported JTAG adapter, please refer to [select JTAG adapter](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#jtag-debugging-selecting-jtag-adapter). Or if you choose to deploy the website to SD card, an extra SD slot board is needed.
|
||||
|
||||
#### Pin Assignment:
|
||||
|
||||
Only if you deploy the website to SD card, then the following pin connection is used in this example.
|
||||
|
||||
| ESP32 | SD Card |
|
||||
| ------ | ------- |
|
||||
| GPIO2 | D0 |
|
||||
| GPIO4 | D1 |
|
||||
| GPIO12 | D2 |
|
||||
| GPIO13 | D3 |
|
||||
| GPIO14 | CLK |
|
||||
| GPIO15 | CMD |
|
||||
|
||||
|
||||
### Configure the project
|
||||
|
||||
Open the project configuration menu (`idf.py menuconfig`).
|
||||
|
||||
In the `Example Connection Configuration` menu:
|
||||
|
||||
* Choose the network interface in `Connect using` option based on your board. Currently we support both Wi-Fi and Ethernet.
|
||||
* If you select the Wi-Fi interface, you also have to set:
|
||||
* Wi-Fi SSID and Wi-Fi password that your esp32 will connect to.
|
||||
* If you select the Ethernet interface, you also have to set:
|
||||
* PHY model in `Ethernet PHY` option, e.g. IP101.
|
||||
* PHY address in `PHY Address` option, which should be determined by your board schematic.
|
||||
* EMAC Clock mode, GPIO used by SMI.
|
||||
|
||||
In the `Example Configuration` menu:
|
||||
|
||||
* Set the domain name in `mDNS Host Name` option.
|
||||
* Choose the deploy mode in `Website deploy mode`, currently we support deploy website to host PC, SD card and SPI Nor flash.
|
||||
* If we choose to `Deploy website to host (JTAG is needed)`, then we also need to specify the full path of the website in `Host path to mount (e.g. absolute path to web dist directory)`.
|
||||
* Set the mount point of the website in `Website mount point in VFS` option, the default value is `/www`.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
After the webpage design work has been finished, you should compile them by running following commands:
|
||||
|
||||
```bash
|
||||
cd path_to_this_example/front/web-demo
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
After a while, you will see a `dist` directory which contains all the website files (e.g. html, js, css, images).
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build and flash the project..
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
### Extra steps to do for deploying website by semihost
|
||||
|
||||
We need to run the latest version of OpenOCD which should support semihost feature when we test this deploy mode:
|
||||
|
||||
```bash
|
||||
openocd-esp32/bin/openocd -s openocd-esp32/share/openocd/scripts -f board/esp32-wrover-kit-3.3v.cfg
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
### Render webpage in browser
|
||||
|
||||
In your browser, enter the URL where the website located (e.g. `http://esp-home.local`). You can also enter the IP address that ESP32 obtained if your operating system currently don't have support for mDNS service.
|
||||
|
||||
Besides that, this example also enables the NetBIOS feature with the domain name `esp-home`. If your OS supports NetBIOS and has enabled it (e.g. Windows has native support for NetBIOS), then the URL `http://esp-home` should also work.
|
||||
|
||||

|
||||
|
||||
### ESP monitor output
|
||||
|
||||
In the *Light* page, after we set up the light color and click on the check button, the browser will send a post request to ESP32, and in the console, we just print the color value.
|
||||
|
||||
```bash
|
||||
I (6115) example_connect: Connected to Ethernet
|
||||
I (6115) example_connect: IPv4 address: 192.168.2.151
|
||||
I (6325) esp-home: Partition size: total: 1920401, used: 1587575
|
||||
I (6325) esp-rest: Starting HTTP Server
|
||||
I (128305) esp-rest: File sending complete
|
||||
I (128565) esp-rest: File sending complete
|
||||
I (128855) esp-rest: File sending complete
|
||||
I (129525) esp-rest: File sending complete
|
||||
I (129855) esp-rest: File sending complete
|
||||
I (137485) esp-rest: Light control: red = 50, green = 85, blue = 28
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
1. Error occurred when building example: `...front/web-demo/dist doesn't exit. Please run 'npm run build' in ...front/web-demo`.
|
||||
* When you choose to deploy website to SPI flash, make sure the `dist` directory has been generated before you building this example.
|
||||
|
||||
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
|
||||
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not ie <= 8
|
||||
@@ -0,0 +1,5 @@
|
||||
[*.{js,jsx,ts,tsx,vue}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
@@ -0,0 +1,17 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true
|
||||
},
|
||||
'extends': [
|
||||
'plugin:vue/essential',
|
||||
'@vue/standard'
|
||||
],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
|
||||
},
|
||||
parserOptions: {
|
||||
parser: 'babel-eslint'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# APIs used in this example is simple and stable enough.
|
||||
# There shouldn't be risk of compatibility unless the major version of some library changed.
|
||||
# To compress the package size, just exclude the package-lock.json file.
|
||||
package-lock.json
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "web-demo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"core-js": "^2.6.5",
|
||||
"vue": "^2.6.10",
|
||||
"vue-router": "^3.0.3",
|
||||
"vuetify": "^1.5.14",
|
||||
"vuex": "^3.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.7.0",
|
||||
"@vue/cli-plugin-eslint": "^3.7.0",
|
||||
"@vue/cli-service": "^3.7.0",
|
||||
"@vue/eslint-config-standard": "^4.0.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"stylus": "^0.54.5",
|
||||
"stylus-loader": "^3.0.1",
|
||||
"vue-cli-plugin-vuetify": "^0.5.0",
|
||||
"vue-template-compiler": "^2.5.21",
|
||||
"vuetify-loader": "^1.0.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.3 KiB |
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title>ESP-HOME</title>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons">
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but web-demo doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<v-app id="inspire">
|
||||
<v-navigation-drawer v-model="drawer" fixed app clipped>
|
||||
<v-list dense>
|
||||
<v-list-tile to="/">
|
||||
<v-list-tile-action>
|
||||
<v-icon>home</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>Home</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
<v-list-tile to="/chart">
|
||||
<v-list-tile-action>
|
||||
<v-icon>show_chart</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>Chart</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
<v-list-tile to="/light">
|
||||
<v-list-tile-action>
|
||||
<v-icon>highlight</v-icon>
|
||||
</v-list-tile-action>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>Light</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</v-list>
|
||||
</v-navigation-drawer>
|
||||
<v-toolbar color="red accent-4" dark fixed app clipped-left>
|
||||
<v-toolbar-side-icon @click.stop="drawer = !drawer"></v-toolbar-side-icon>
|
||||
<v-toolbar-title>ESP Home</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-content>
|
||||
<v-container fluid fill-height>
|
||||
<router-view></router-view>
|
||||
</v-container>
|
||||
</v-content>
|
||||
<v-footer color="red accent-4" app fixed>
|
||||
<span class="white--text">© ESPRESSIF SYSTEMS (SHANGHAI) CO., LTD. All rights reserved.</span>
|
||||
</v-footer>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "App",
|
||||
data() {
|
||||
return {
|
||||
drawer: null
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -0,0 +1,16 @@
|
||||
import Vue from 'vue'
|
||||
import './plugins/vuetify'
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import axios from 'axios'
|
||||
import store from './store'
|
||||
|
||||
Vue.config.productionTip = false
|
||||
|
||||
Vue.prototype.$ajax = axios
|
||||
|
||||
new Vue({
|
||||
router,
|
||||
store,
|
||||
render: h => h(App)
|
||||
}).$mount('#app')
|
||||
@@ -0,0 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import Vuetify from 'vuetify/lib'
|
||||
import 'vuetify/src/stylus/app.styl'
|
||||
|
||||
Vue.use(Vuetify, {
|
||||
iconfont: 'md',
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
import Vue from 'vue'
|
||||
import Router from 'vue-router'
|
||||
import Home from './views/Home.vue'
|
||||
import Chart from './views/Chart.vue'
|
||||
import Light from './views/Light.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
mode: 'history',
|
||||
base: process.env.BASE_URL,
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: Home
|
||||
},
|
||||
{
|
||||
path: '/chart',
|
||||
name: 'chart',
|
||||
component: Chart
|
||||
},
|
||||
{
|
||||
path: '/light',
|
||||
name: 'light',
|
||||
component: Light
|
||||
}
|
||||
]
|
||||
})
|
||||
@@ -0,0 +1,28 @@
|
||||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import axios from 'axios'
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
export default new Vuex.Store({
|
||||
state: {
|
||||
chart_value: [8, 2, 5, 9, 5, 11, 3, 5, 10, 0, 1, 8, 2, 9, 0, 13, 10, 7, 16],
|
||||
},
|
||||
mutations: {
|
||||
update_chart_value(state, new_value) {
|
||||
state.chart_value.push(new_value);
|
||||
state.chart_value.shift();
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
update_chart_value({ commit }) {
|
||||
axios.get("/api/v1/temp/raw")
|
||||
.then(data => {
|
||||
commit("update_chart_value", data.data.raw);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-sparkline
|
||||
:value="get_chart_value"
|
||||
:gradient="['#f72047', '#ffd200', '#1feaea']"
|
||||
:smooth="10"
|
||||
:padding="8"
|
||||
:line-width="2"
|
||||
stroke-linecap="round"
|
||||
gradient-direction="top"
|
||||
auto-draw
|
||||
></v-sparkline>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
timer: null
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
get_chart_value() {
|
||||
return this.$store.state.chart_value;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
updateData: function() {
|
||||
this.$store.dispatch("update_chart_value");
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
clearInterval(this.timer);
|
||||
this.timer = setInterval(this.updateData, 1000);
|
||||
},
|
||||
destroyed: function() {
|
||||
clearInterval(this.timer);
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,40 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-layout text-xs-center wrap>
|
||||
<v-flex xs12 sm6 offset-sm3>
|
||||
<v-card>
|
||||
<v-img :src="require('../assets/logo.png')" contain height="200"></v-img>
|
||||
<v-card-title primary-title>
|
||||
<div class="ma-auto">
|
||||
<span class="grey--text">IDF version: {{version}}</span>
|
||||
<br>
|
||||
<span class="grey--text">ESP cores: {{cores}}</span>
|
||||
</div>
|
||||
</v-card-title>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
version: null,
|
||||
cores: null
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$ajax
|
||||
.get("/api/v1/system/info")
|
||||
.then(data => {
|
||||
this.version = data.data.version;
|
||||
this.cores = data.data.cores;
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<v-container>
|
||||
<v-layout text-xs-center wrap>
|
||||
<v-flex xs12 sm6 offset-sm3>
|
||||
<v-card>
|
||||
<v-responsive :style="{ background: `rgb(${red}, ${green}, ${blue})` }" height="300px"></v-responsive>
|
||||
<v-card-text>
|
||||
<v-container fluid grid-list-lg>
|
||||
<v-layout row wrap>
|
||||
<v-flex xs9>
|
||||
<v-slider v-model="red" :max="255" label="R"></v-slider>
|
||||
</v-flex>
|
||||
<v-flex xs3>
|
||||
<v-text-field v-model="red" class="mt-0" type="number"></v-text-field>
|
||||
</v-flex>
|
||||
<v-flex xs9>
|
||||
<v-slider v-model="green" :max="255" label="G"></v-slider>
|
||||
</v-flex>
|
||||
<v-flex xs3>
|
||||
<v-text-field v-model="green" class="mt-0" type="number"></v-text-field>
|
||||
</v-flex>
|
||||
<v-flex xs9>
|
||||
<v-slider v-model="blue" :max="255" label="B"></v-slider>
|
||||
</v-flex>
|
||||
<v-flex xs3>
|
||||
<v-text-field v-model="blue" class="mt-0" type="number"></v-text-field>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</v-card-text>
|
||||
<v-btn fab dark large color="red accent-4" @click="set_color">
|
||||
<v-icon dark>check_box</v-icon>
|
||||
</v-btn>
|
||||
</v-card>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return { red: 160, green: 160, blue: 160 };
|
||||
},
|
||||
methods: {
|
||||
set_color: function() {
|
||||
this.$ajax
|
||||
.post("/api/v1/light/brightness", {
|
||||
red: this.red,
|
||||
green: this.green,
|
||||
blue: this.blue
|
||||
})
|
||||
.then(data => {
|
||||
console.log(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.log(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
devServer: {
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://esp-home.local:80',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
idf_component_register(SRCS "esp_rest_main.c"
|
||||
"rest_server.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
if(CONFIG_EXAMPLE_WEB_DEPLOY_SF)
|
||||
set(WEB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../front/web-demo")
|
||||
if(EXISTS ${WEB_SRC_DIR}/dist)
|
||||
spiffs_create_partition_image(www ${WEB_SRC_DIR}/dist FLASH_IN_PROJECT)
|
||||
else()
|
||||
message(FATAL_ERROR "${WEB_SRC_DIR}/dist doesn't exit. Please run 'npm run build' in ${WEB_SRC_DIR}")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,51 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_MDNS_HOST_NAME
|
||||
string "mDNS Host Name"
|
||||
default "esp-home"
|
||||
help
|
||||
Specify the domain name used in the mDNS service.
|
||||
Note that webpage also take it as a part of URL where it will send GET/POST requests to.
|
||||
|
||||
choice EXAMPLE_WEB_DEPLOY_MODE
|
||||
prompt "Website deploy mode"
|
||||
default EXAMPLE_WEB_DEPLOY_SEMIHOST
|
||||
help
|
||||
Select website deploy mode.
|
||||
You can deploy website to host, and ESP32 will retrieve them in a semihost way (JTAG is needed).
|
||||
You can deploy website to SD card or SPI flash, and ESP32 will retrieve them via SDIO/SPI interface.
|
||||
Detailed operation steps are listed in the example README file.
|
||||
config EXAMPLE_WEB_DEPLOY_SEMIHOST
|
||||
bool "Deploy website to host (JTAG is needed)"
|
||||
help
|
||||
Deploy website to host.
|
||||
It is recommended to choose this mode during developing.
|
||||
config EXAMPLE_WEB_DEPLOY_SD
|
||||
depends on IDF_TARGET_ESP32
|
||||
bool "Deploy website to SD card"
|
||||
help
|
||||
Deploy website to SD card.
|
||||
Choose this production mode if the size of website is too large (bigger than 2MB).
|
||||
config EXAMPLE_WEB_DEPLOY_SF
|
||||
bool "Deploy website to SPI Nor Flash"
|
||||
help
|
||||
Deploy website to SPI Nor Flash.
|
||||
Choose this production mode if the size of website is small (less than 2MB).
|
||||
endchoice
|
||||
|
||||
if EXAMPLE_WEB_DEPLOY_SEMIHOST
|
||||
config EXAMPLE_HOST_PATH_TO_MOUNT
|
||||
string "Host path to mount (e.g. absolute path to web dist directory)"
|
||||
default "PATH-TO-WEB-DIST_DIR"
|
||||
help
|
||||
When using semihost in ESP32, you should specify the host path which will be mounted to VFS.
|
||||
Note that only absolute path is acceptable.
|
||||
endif
|
||||
|
||||
config EXAMPLE_WEB_MOUNT_POINT
|
||||
string "Website mount point in VFS"
|
||||
default "/www"
|
||||
help
|
||||
Specify the mount point in VFS.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,138 @@
|
||||
/* HTTP Restful API Server Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include "sdkconfig.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_vfs_semihost.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "esp_spiffs.h"
|
||||
#include "sdmmc_cmd.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "mdns.h"
|
||||
#include "lwip/apps/netbiosns.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#if CONFIG_EXAMPLE_WEB_DEPLOY_SD
|
||||
#include "driver/sdmmc_host.h"
|
||||
#endif
|
||||
|
||||
#define MDNS_INSTANCE "esp home web server"
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
esp_err_t start_rest_server(const char *base_path);
|
||||
|
||||
static void initialise_mdns(void)
|
||||
{
|
||||
mdns_init();
|
||||
mdns_hostname_set(CONFIG_EXAMPLE_MDNS_HOST_NAME);
|
||||
mdns_instance_name_set(MDNS_INSTANCE);
|
||||
|
||||
mdns_txt_item_t serviceTxtData[] = {
|
||||
{"board", "esp32"},
|
||||
{"path", "/"}
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, serviceTxtData,
|
||||
sizeof(serviceTxtData) / sizeof(serviceTxtData[0])));
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_WEB_DEPLOY_SEMIHOST
|
||||
esp_err_t init_fs(void)
|
||||
{
|
||||
esp_err_t ret = esp_vfs_semihost_register(CONFIG_EXAMPLE_WEB_MOUNT_POINT, CONFIG_EXAMPLE_HOST_PATH_TO_MOUNT);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to register semihost driver (%s)!", esp_err_to_name(ret));
|
||||
return ESP_FAIL;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_WEB_DEPLOY_SD
|
||||
esp_err_t init_fs(void)
|
||||
{
|
||||
sdmmc_host_t host = SDMMC_HOST_DEFAULT();
|
||||
sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
|
||||
|
||||
gpio_set_pull_mode(15, GPIO_PULLUP_ONLY); // CMD
|
||||
gpio_set_pull_mode(2, GPIO_PULLUP_ONLY); // D0
|
||||
gpio_set_pull_mode(4, GPIO_PULLUP_ONLY); // D1
|
||||
gpio_set_pull_mode(12, GPIO_PULLUP_ONLY); // D2
|
||||
gpio_set_pull_mode(13, GPIO_PULLUP_ONLY); // D3
|
||||
|
||||
esp_vfs_fat_sdmmc_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = true,
|
||||
.max_files = 4,
|
||||
.allocation_unit_size = 16 * 1024
|
||||
};
|
||||
|
||||
sdmmc_card_t *card;
|
||||
esp_err_t ret = esp_vfs_fat_sdmmc_mount(CONFIG_EXAMPLE_WEB_MOUNT_POINT, &host, &slot_config, &mount_config, &card);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount filesystem.");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize the card (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
/* print card info if mount successfully */
|
||||
sdmmc_card_print_info(stdout, card);
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if CONFIG_EXAMPLE_WEB_DEPLOY_SF
|
||||
esp_err_t init_fs(void)
|
||||
{
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = CONFIG_EXAMPLE_WEB_MOUNT_POINT,
|
||||
.partition_label = NULL,
|
||||
.max_files = 5,
|
||||
.format_if_mount_failed = false
|
||||
};
|
||||
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
if (ret == ESP_FAIL) {
|
||||
ESP_LOGE(TAG, "Failed to mount or format filesystem");
|
||||
} else if (ret == ESP_ERR_NOT_FOUND) {
|
||||
ESP_LOGE(TAG, "Failed to find SPIFFS partition");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_spiffs_info(NULL, &total, &used);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get SPIFFS partition information (%s)", esp_err_to_name(ret));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Partition size: total: %d, used: %d", total, used);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
initialise_mdns();
|
||||
netbiosns_init();
|
||||
netbiosns_set_name(CONFIG_EXAMPLE_MDNS_HOST_NAME);
|
||||
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
ESP_ERROR_CHECK(init_fs());
|
||||
ESP_ERROR_CHECK(start_rest_server(CONFIG_EXAMPLE_WEB_MOUNT_POINT));
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
/* HTTP Restful API Server
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include "esp_http_server.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_vfs.h"
|
||||
#include "cJSON.h"
|
||||
|
||||
static const char *REST_TAG = "esp-rest";
|
||||
#define REST_CHECK(a, str, goto_tag, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (!(a)) \
|
||||
{ \
|
||||
ESP_LOGE(REST_TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
goto goto_tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
|
||||
#define SCRATCH_BUFSIZE (10240)
|
||||
|
||||
typedef struct rest_server_context {
|
||||
char base_path[ESP_VFS_PATH_MAX + 1];
|
||||
char scratch[SCRATCH_BUFSIZE];
|
||||
} rest_server_context_t;
|
||||
|
||||
#define CHECK_FILE_EXTENSION(filename, ext) (strcasecmp(&filename[strlen(filename) - strlen(ext)], ext) == 0)
|
||||
|
||||
/* Set HTTP response content type according to file extension */
|
||||
static esp_err_t set_content_type_from_file(httpd_req_t *req, const char *filepath)
|
||||
{
|
||||
const char *type = "text/plain";
|
||||
if (CHECK_FILE_EXTENSION(filepath, ".html")) {
|
||||
type = "text/html";
|
||||
} else if (CHECK_FILE_EXTENSION(filepath, ".js")) {
|
||||
type = "application/javascript";
|
||||
} else if (CHECK_FILE_EXTENSION(filepath, ".css")) {
|
||||
type = "text/css";
|
||||
} else if (CHECK_FILE_EXTENSION(filepath, ".png")) {
|
||||
type = "image/png";
|
||||
} else if (CHECK_FILE_EXTENSION(filepath, ".ico")) {
|
||||
type = "image/x-icon";
|
||||
} else if (CHECK_FILE_EXTENSION(filepath, ".svg")) {
|
||||
type = "text/xml";
|
||||
}
|
||||
return httpd_resp_set_type(req, type);
|
||||
}
|
||||
|
||||
/* Send HTTP response with the contents of the requested file */
|
||||
static esp_err_t rest_common_get_handler(httpd_req_t *req)
|
||||
{
|
||||
char filepath[FILE_PATH_MAX];
|
||||
|
||||
rest_server_context_t *rest_context = (rest_server_context_t *)req->user_ctx;
|
||||
strlcpy(filepath, rest_context->base_path, sizeof(filepath));
|
||||
if (req->uri[strlen(req->uri) - 1] == '/') {
|
||||
strlcat(filepath, "/index.html", sizeof(filepath));
|
||||
} else {
|
||||
strlcat(filepath, req->uri, sizeof(filepath));
|
||||
}
|
||||
int fd = open(filepath, O_RDONLY, 0);
|
||||
if (fd == -1) {
|
||||
ESP_LOGE(REST_TAG, "Failed to open file : %s", filepath);
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to read existing file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
set_content_type_from_file(req, filepath);
|
||||
|
||||
char *chunk = rest_context->scratch;
|
||||
ssize_t read_bytes;
|
||||
do {
|
||||
/* Read file in chunks into the scratch buffer */
|
||||
read_bytes = read(fd, chunk, SCRATCH_BUFSIZE);
|
||||
if (read_bytes == -1) {
|
||||
ESP_LOGE(REST_TAG, "Failed to read file : %s", filepath);
|
||||
} else if (read_bytes > 0) {
|
||||
/* Send the buffer contents as HTTP response chunk */
|
||||
if (httpd_resp_send_chunk(req, chunk, read_bytes) != ESP_OK) {
|
||||
close(fd);
|
||||
ESP_LOGE(REST_TAG, "File sending failed!");
|
||||
/* Abort sending file */
|
||||
httpd_resp_sendstr_chunk(req, NULL);
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to send file");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
}
|
||||
} while (read_bytes > 0);
|
||||
/* Close file after sending complete */
|
||||
close(fd);
|
||||
ESP_LOGI(REST_TAG, "File sending complete");
|
||||
/* Respond with an empty chunk to signal HTTP response completion */
|
||||
httpd_resp_send_chunk(req, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Simple handler for light brightness control */
|
||||
static esp_err_t light_brightness_post_handler(httpd_req_t *req)
|
||||
{
|
||||
int total_len = req->content_len;
|
||||
int cur_len = 0;
|
||||
char *buf = ((rest_server_context_t *)(req->user_ctx))->scratch;
|
||||
int received = 0;
|
||||
if (total_len >= SCRATCH_BUFSIZE) {
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "content too long");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
while (cur_len < total_len) {
|
||||
received = httpd_req_recv(req, buf + cur_len, total_len);
|
||||
if (received <= 0) {
|
||||
/* Respond with 500 Internal Server Error */
|
||||
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "Failed to post control value");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
cur_len += received;
|
||||
}
|
||||
buf[total_len] = '\0';
|
||||
|
||||
cJSON *root = cJSON_Parse(buf);
|
||||
int red = cJSON_GetObjectItem(root, "red")->valueint;
|
||||
int green = cJSON_GetObjectItem(root, "green")->valueint;
|
||||
int blue = cJSON_GetObjectItem(root, "blue")->valueint;
|
||||
ESP_LOGI(REST_TAG, "Light control: red = %d, green = %d, blue = %d", red, green, blue);
|
||||
cJSON_Delete(root);
|
||||
httpd_resp_sendstr(req, "Post control value successfully");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Simple handler for getting system handler */
|
||||
static esp_err_t system_info_get_handler(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
esp_chip_info_t chip_info;
|
||||
esp_chip_info(&chip_info);
|
||||
cJSON_AddStringToObject(root, "version", IDF_VER);
|
||||
cJSON_AddNumberToObject(root, "cores", chip_info.cores);
|
||||
const char *sys_info = cJSON_Print(root);
|
||||
httpd_resp_sendstr(req, sys_info);
|
||||
free((void *)sys_info);
|
||||
cJSON_Delete(root);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Simple handler for getting temperature data */
|
||||
static esp_err_t temperature_data_get_handler(httpd_req_t *req)
|
||||
{
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(root, "raw", esp_random() % 20);
|
||||
const char *sys_info = cJSON_Print(root);
|
||||
httpd_resp_sendstr(req, sys_info);
|
||||
free((void *)sys_info);
|
||||
cJSON_Delete(root);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t start_rest_server(const char *base_path)
|
||||
{
|
||||
REST_CHECK(base_path, "wrong base path", err);
|
||||
rest_server_context_t *rest_context = calloc(1, sizeof(rest_server_context_t));
|
||||
REST_CHECK(rest_context, "No memory for rest context", err);
|
||||
strlcpy(rest_context->base_path, base_path, sizeof(rest_context->base_path));
|
||||
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.uri_match_fn = httpd_uri_match_wildcard;
|
||||
|
||||
ESP_LOGI(REST_TAG, "Starting HTTP Server");
|
||||
REST_CHECK(httpd_start(&server, &config) == ESP_OK, "Start server failed", err_start);
|
||||
|
||||
/* URI handler for fetching system info */
|
||||
httpd_uri_t system_info_get_uri = {
|
||||
.uri = "/api/v1/system/info",
|
||||
.method = HTTP_GET,
|
||||
.handler = system_info_get_handler,
|
||||
.user_ctx = rest_context
|
||||
};
|
||||
httpd_register_uri_handler(server, &system_info_get_uri);
|
||||
|
||||
/* URI handler for fetching temperature data */
|
||||
httpd_uri_t temperature_data_get_uri = {
|
||||
.uri = "/api/v1/temp/raw",
|
||||
.method = HTTP_GET,
|
||||
.handler = temperature_data_get_handler,
|
||||
.user_ctx = rest_context
|
||||
};
|
||||
httpd_register_uri_handler(server, &temperature_data_get_uri);
|
||||
|
||||
/* URI handler for light brightness control */
|
||||
httpd_uri_t light_brightness_post_uri = {
|
||||
.uri = "/api/v1/light/brightness",
|
||||
.method = HTTP_POST,
|
||||
.handler = light_brightness_post_handler,
|
||||
.user_ctx = rest_context
|
||||
};
|
||||
httpd_register_uri_handler(server, &light_brightness_post_uri);
|
||||
|
||||
/* URI handler for getting web server files */
|
||||
httpd_uri_t common_get_uri = {
|
||||
.uri = "/*",
|
||||
.method = HTTP_GET,
|
||||
.handler = rest_common_get_handler,
|
||||
.user_ctx = rest_context
|
||||
};
|
||||
httpd_register_uri_handler(server, &common_get_uri);
|
||||
|
||||
return ESP_OK;
|
||||
err_start:
|
||||
free(rest_context);
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
www, data, spiffs, , 2M,
|
||||
|
@@ -0,0 +1,8 @@
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
|
||||
CONFIG_SPIFFS_OBJ_NAME_LEN=64
|
||||
CONFIG_FATFS_LONG_FILENAME=y
|
||||
CONFIG_FATFS_LFN_HEAP=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
|
||||
@@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(simple)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := simple
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Simple HTTPD Server Example
|
||||
|
||||
The Example consists of HTTPD server demo with demostration of URI handling :
|
||||
1. URI \hello for GET command returns "Hello World!" message
|
||||
2. URI \echo for POST command echoes back the POSTed message
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`) to configure Wi-Fi or Ethernet. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
|
||||
* In order to test the HTTPD server persistent sockets demo :
|
||||
1. compile and burn the firmware `idf.py -p PORT flash`
|
||||
2. run `idf.py -p PORT monitor` and note down the IP assigned to your ESP module. The default port is 80
|
||||
3. test the example :
|
||||
* run the test script : "python scripts/client.py \<IP\> \<port\> \<MSG\>"
|
||||
* the provided test script first does a GET \hello and displays the response
|
||||
* the script does a POST to \echo with the user input \<MSG\> and displays the response
|
||||
* or use curl (asssuming IP is 192.168.43.130):
|
||||
1. "curl 192.168.43.130:80/hello" - tests the GET "\hello" handler
|
||||
2. "curl -X POST --data-binary @anyfile 192.168.43.130:80/echo > tmpfile"
|
||||
* "anyfile" is the file being sent as request body and "tmpfile" is where the body of the response is saved
|
||||
* since the server echoes back the request body, the two files should be same, as can be confirmed using : "cmp anyfile tmpfile"
|
||||
3. "curl -X PUT -d "0" 192.168.43.130:80/ctrl" - disable /hello and /echo handlers
|
||||
4. "curl -X PUT -d "1" 192.168.43.130:80/ctrl" - enable /hello and /echo handlers
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,111 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2018 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
from builtins import range
|
||||
import re
|
||||
import os
|
||||
import string
|
||||
import random
|
||||
|
||||
from tiny_test_fw import Utility
|
||||
import ttfw_idf
|
||||
from idf_http_server_test import client
|
||||
|
||||
|
||||
# When running on local machine execute the following before running this script
|
||||
# > make app bootloader
|
||||
# > make print_flash_cmd | tail -n 1 > build/download.config
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_http_server_simple(env, extra_data):
|
||||
# Acquire DUT
|
||||
dut1 = env.get_dut("http_server", "examples/protocols/http_server/simple", dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
# Get binary file
|
||||
binary_file = os.path.join(dut1.app.binary_path, "simple.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("http_server_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("http_server_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
|
||||
# Upload binary and start testing
|
||||
Utility.console_log("Starting http_server simple test app")
|
||||
dut1.start_app()
|
||||
|
||||
# Parse IP address of STA
|
||||
Utility.console_log("Waiting to connect with AP")
|
||||
got_ip = dut1.expect(re.compile(r"(?:[\s\S]*)IPv4 address: (\d+.\d+.\d+.\d+)"), timeout=30)[0]
|
||||
got_port = dut1.expect(re.compile(r"(?:[\s\S]*)Starting server on port: '(\d+)'"), timeout=30)[0]
|
||||
|
||||
Utility.console_log("Got IP : " + got_ip)
|
||||
Utility.console_log("Got Port : " + got_port)
|
||||
|
||||
# Expected Logs
|
||||
dut1.expect("Registering URI handlers", timeout=30)
|
||||
|
||||
# Run test script
|
||||
# If failed raise appropriate exception
|
||||
Utility.console_log("Test /hello GET handler")
|
||||
if not client.test_get_handler(got_ip, got_port):
|
||||
raise RuntimeError
|
||||
|
||||
# Acquire host IP. Need a way to check it
|
||||
dut1.expect(re.compile(r"(?:[\s\S]*)Found header => Host: (\d+.\d+.\d+.\d+)"), timeout=30)[0]
|
||||
|
||||
# Match additional headers sent in the request
|
||||
dut1.expect("Found header => Test-Header-2: Test-Value-2", timeout=30)
|
||||
dut1.expect("Found header => Test-Header-1: Test-Value-1", timeout=30)
|
||||
dut1.expect("Found URL query parameter => query1=value1", timeout=30)
|
||||
dut1.expect("Found URL query parameter => query3=value3", timeout=30)
|
||||
dut1.expect("Found URL query parameter => query2=value2", timeout=30)
|
||||
dut1.expect("Request headers lost", timeout=30)
|
||||
|
||||
Utility.console_log("Test /ctrl PUT handler and realtime handler de/registration")
|
||||
if not client.test_put_handler(got_ip, got_port):
|
||||
raise RuntimeError
|
||||
dut1.expect("Unregistering /hello and /echo URIs", timeout=30)
|
||||
dut1.expect("Registering /hello and /echo URIs", timeout=30)
|
||||
|
||||
# Generate random data of 10KB
|
||||
random_data = ''.join(string.printable[random.randint(0,len(string.printable)) - 1] for _ in range(10 * 1024))
|
||||
Utility.console_log("Test /echo POST handler with random data")
|
||||
if not client.test_post_handler(got_ip, got_port, random_data):
|
||||
raise RuntimeError
|
||||
|
||||
query = "http://foobar"
|
||||
Utility.console_log("Test /hello with custom query : " + query)
|
||||
if not client.test_custom_uri_query(got_ip, got_port, query):
|
||||
raise RuntimeError
|
||||
dut1.expect("Found URL query => " + query, timeout=30)
|
||||
|
||||
query = "abcd+1234%20xyz"
|
||||
Utility.console_log("Test /hello with custom query : " + query)
|
||||
if not client.test_custom_uri_query(got_ip, got_port, query):
|
||||
raise RuntimeError
|
||||
dut1.expect("Found URL query => " + query, timeout=30)
|
||||
|
||||
query = "abcd\nyz"
|
||||
Utility.console_log("Test /hello with invalid query")
|
||||
if client.test_custom_uri_query(got_ip, got_port, query):
|
||||
raise RuntimeError
|
||||
dut1.expect("400 Bad Request - Server unable to understand request due to invalid syntax", timeout=30)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_http_server_simple()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,298 @@
|
||||
/* Simple HTTP Server Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_system.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <sys/param.h>
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include <esp_http_server.h>
|
||||
|
||||
/* A simple example that demonstrates how to create GET and POST
|
||||
* handlers for the web server.
|
||||
*/
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
/* An HTTP GET handler */
|
||||
static esp_err_t hello_get_handler(httpd_req_t *req)
|
||||
{
|
||||
char* buf;
|
||||
size_t buf_len;
|
||||
|
||||
/* Get header value string length and allocate memory for length + 1,
|
||||
* extra byte for null termination */
|
||||
buf_len = httpd_req_get_hdr_value_len(req, "Host") + 1;
|
||||
if (buf_len > 1) {
|
||||
buf = malloc(buf_len);
|
||||
/* Copy null terminated value string into buffer */
|
||||
if (httpd_req_get_hdr_value_str(req, "Host", buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found header => Host: %s", buf);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-2") + 1;
|
||||
if (buf_len > 1) {
|
||||
buf = malloc(buf_len);
|
||||
if (httpd_req_get_hdr_value_str(req, "Test-Header-2", buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found header => Test-Header-2: %s", buf);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
buf_len = httpd_req_get_hdr_value_len(req, "Test-Header-1") + 1;
|
||||
if (buf_len > 1) {
|
||||
buf = malloc(buf_len);
|
||||
if (httpd_req_get_hdr_value_str(req, "Test-Header-1", buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found header => Test-Header-1: %s", buf);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* Read URL query string length and allocate memory for length + 1,
|
||||
* extra byte for null termination */
|
||||
buf_len = httpd_req_get_url_query_len(req) + 1;
|
||||
if (buf_len > 1) {
|
||||
buf = malloc(buf_len);
|
||||
if (httpd_req_get_url_query_str(req, buf, buf_len) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found URL query => %s", buf);
|
||||
char param[32];
|
||||
/* Get value of expected key from query string */
|
||||
if (httpd_query_key_value(buf, "query1", param, sizeof(param)) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found URL query parameter => query1=%s", param);
|
||||
}
|
||||
if (httpd_query_key_value(buf, "query3", param, sizeof(param)) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found URL query parameter => query3=%s", param);
|
||||
}
|
||||
if (httpd_query_key_value(buf, "query2", param, sizeof(param)) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Found URL query parameter => query2=%s", param);
|
||||
}
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* Set some custom headers */
|
||||
httpd_resp_set_hdr(req, "Custom-Header-1", "Custom-Value-1");
|
||||
httpd_resp_set_hdr(req, "Custom-Header-2", "Custom-Value-2");
|
||||
|
||||
/* Send response with custom headers and body set as the
|
||||
* string passed in user context*/
|
||||
const char* resp_str = (const char*) req->user_ctx;
|
||||
httpd_resp_send(req, resp_str, strlen(resp_str));
|
||||
|
||||
/* After sending the HTTP response the old HTTP request
|
||||
* headers are lost. Check if HTTP request headers can be read now. */
|
||||
if (httpd_req_get_hdr_value_len(req, "Host") == 0) {
|
||||
ESP_LOGI(TAG, "Request headers lost");
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static const httpd_uri_t hello = {
|
||||
.uri = "/hello",
|
||||
.method = HTTP_GET,
|
||||
.handler = hello_get_handler,
|
||||
/* Let's pass response string in user
|
||||
* context to demonstrate it's usage */
|
||||
.user_ctx = "Hello World!"
|
||||
};
|
||||
|
||||
/* An HTTP POST handler */
|
||||
static esp_err_t echo_post_handler(httpd_req_t *req)
|
||||
{
|
||||
char buf[100];
|
||||
int ret, remaining = req->content_len;
|
||||
|
||||
while (remaining > 0) {
|
||||
/* Read the data for the request */
|
||||
if ((ret = httpd_req_recv(req, buf,
|
||||
MIN(remaining, sizeof(buf)))) <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
/* Retry receiving if timeout occurred */
|
||||
continue;
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Send back the same data */
|
||||
httpd_resp_send_chunk(req, buf, ret);
|
||||
remaining -= ret;
|
||||
|
||||
/* Log data received */
|
||||
ESP_LOGI(TAG, "=========== RECEIVED DATA ==========");
|
||||
ESP_LOGI(TAG, "%.*s", ret, buf);
|
||||
ESP_LOGI(TAG, "====================================");
|
||||
}
|
||||
|
||||
// End response
|
||||
httpd_resp_send_chunk(req, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static const httpd_uri_t echo = {
|
||||
.uri = "/echo",
|
||||
.method = HTTP_POST,
|
||||
.handler = echo_post_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
|
||||
/* This handler allows the custom error handling functionality to be
|
||||
* tested from client side. For that, when a PUT request 0 is sent to
|
||||
* URI /ctrl, the /hello and /echo URIs are unregistered and following
|
||||
* custom error handler http_404_error_handler() is registered.
|
||||
* Afterwards, when /hello or /echo is requested, this custom error
|
||||
* handler is invoked which, after sending an error message to client,
|
||||
* either closes the underlying socket (when requested URI is /echo)
|
||||
* or keeps it open (when requested URI is /hello). This allows the
|
||||
* client to infer if the custom error handler is functioning as expected
|
||||
* by observing the socket state.
|
||||
*/
|
||||
esp_err_t http_404_error_handler(httpd_req_t *req, httpd_err_code_t err)
|
||||
{
|
||||
if (strcmp("/hello", req->uri) == 0) {
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/hello URI is not available");
|
||||
/* Return ESP_OK to keep underlying socket open */
|
||||
return ESP_OK;
|
||||
} else if (strcmp("/echo", req->uri) == 0) {
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "/echo URI is not available");
|
||||
/* Return ESP_FAIL to close underlying socket */
|
||||
return ESP_FAIL;
|
||||
}
|
||||
/* For any other URI send 404 and close socket */
|
||||
httpd_resp_send_err(req, HTTPD_404_NOT_FOUND, "Some 404 error message");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* An HTTP PUT handler. This demonstrates realtime
|
||||
* registration and deregistration of URI handlers
|
||||
*/
|
||||
static esp_err_t ctrl_put_handler(httpd_req_t *req)
|
||||
{
|
||||
char buf;
|
||||
int ret;
|
||||
|
||||
if ((ret = httpd_req_recv(req, &buf, 1)) <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (buf == '0') {
|
||||
/* URI handlers can be unregistered using the uri string */
|
||||
ESP_LOGI(TAG, "Unregistering /hello and /echo URIs");
|
||||
httpd_unregister_uri(req->handle, "/hello");
|
||||
httpd_unregister_uri(req->handle, "/echo");
|
||||
/* Register the custom error handler */
|
||||
httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, http_404_error_handler);
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(TAG, "Registering /hello and /echo URIs");
|
||||
httpd_register_uri_handler(req->handle, &hello);
|
||||
httpd_register_uri_handler(req->handle, &echo);
|
||||
/* Unregister custom error handler */
|
||||
httpd_register_err_handler(req->handle, HTTPD_404_NOT_FOUND, NULL);
|
||||
}
|
||||
|
||||
/* Respond with empty body */
|
||||
httpd_resp_send(req, NULL, 0);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static const httpd_uri_t ctrl = {
|
||||
.uri = "/ctrl",
|
||||
.method = HTTP_PUT,
|
||||
.handler = ctrl_put_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
|
||||
static httpd_handle_t start_webserver(void)
|
||||
{
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
|
||||
// Start the httpd server
|
||||
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
// Set URI handlers
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &hello);
|
||||
httpd_register_uri_handler(server, &echo);
|
||||
httpd_register_uri_handler(server, &ctrl);
|
||||
return server;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Error starting server!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void stop_webserver(httpd_handle_t server)
|
||||
{
|
||||
// Stop the httpd server
|
||||
httpd_stop(server);
|
||||
}
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server) {
|
||||
ESP_LOGI(TAG, "Stopping webserver");
|
||||
stop_webserver(*server);
|
||||
*server = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server == NULL) {
|
||||
ESP_LOGI(TAG, "Starting webserver");
|
||||
*server = start_webserver();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static httpd_handle_t server = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
|
||||
* and re-start it upon connection.
|
||||
*/
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
/* Start the server for the first time */
|
||||
server = start_webserver();
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
# (Not part of the boilerplate)
|
||||
# This example uses an extra component for common functions such as Wi-Fi and Ethernet connection.
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(ws_echo_server)
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ws_echo_server
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# Websocket echo server
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
This example demonstrates the HTTPD server using the WebSocket feature.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
The example starts a WS server on a local network, so a WS client is needed to interact with the server (an example test
|
||||
ws_server_example_test.py could be used as a simple WS client).
|
||||
|
||||
The server registers WebSocket handler which echoes back the received WebSocket frame. It also demonstrates
|
||||
use of asynchronous send, which is triggered on reception of a certain message.
|
||||
|
||||
Please note that the WebSocket HTTP server does not automatically fragment messages.
|
||||
Each outgoing frame has the FIN flag set by default.
|
||||
In case an application wants to send fragmented data, it must be done manually by setting the
|
||||
`fragmented` option and using the `final` flag as described in [RFC6455, section 5.4](https://tools.ietf.org/html/rfc6455#section-5.4).
|
||||
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example can be executed on any common development board, the only required interface is WiFi or Ethernet connection to a local network.
|
||||
|
||||
### Configure the project
|
||||
|
||||
* Open the project configuration menu (`idf.py menuconfig`)
|
||||
* Configure Wi-Fi or Ethernet under "Example Connection Configuration" menu. See "Establishing Wi-Fi or Ethernet Connection" section in [examples/protocols/README.md](../../README.md) for more details.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
```
|
||||
I (4932) example_connect: Got IPv6 event!
|
||||
I (4942) example_connect: Connected to Espressif
|
||||
I (4942) example_connect: IPv4 address: 192.168.4.2
|
||||
I (4952) example_connect: IPv6 address: fe80:xxxx
|
||||
I (4962) ws_echo_server: Starting server on port: '80'
|
||||
I (4962) ws_echo_server: Registering URI handlers
|
||||
D (4962) httpd: httpd_thread: web server started
|
||||
D (4972) httpd: httpd_server: doing select maxfd+1 = 56
|
||||
D (4982) httpd_uri: httpd_register_uri_handler: [0] installed /ws
|
||||
D (17552) httpd: httpd_server: processing listen socket 54
|
||||
D (17552) httpd: httpd_accept_conn: newfd = 57
|
||||
D (17552) httpd_sess: httpd_sess_new: fd = 57
|
||||
D (17562) httpd: httpd_accept_conn: complete
|
||||
D (17562) httpd: httpd_server: doing select maxfd+1 = 58
|
||||
D (17572) httpd: httpd_server: processing socket 57
|
||||
D (17572) httpd_sess: httpd_sess_process: httpd_req_new
|
||||
D (17582) httpd_parse: httpd_req_new: New request, has WS? No, sd->ws_handler valid? No, sd->ws_close? No
|
||||
D (17592) httpd_txrx: httpd_recv_with_opt: requested length = 128
|
||||
D (17592) httpd_txrx: httpd_recv_with_opt: received length = 128
|
||||
D (17602) httpd_parse: read_block: received HTTP request block size = 128
|
||||
D (17612) httpd_parse: cb_url: message begin
|
||||
D (17612) httpd_parse: cb_url: processing url = /ws
|
||||
D (17622) httpd_parse: verify_url: received URI = /ws
|
||||
D (17622) httpd_parse: cb_header_field: headers begin
|
||||
D (17632) httpd_txrx: httpd_unrecv: length = 110
|
||||
D (17632) httpd_parse: pause_parsing: paused
|
||||
D (17632) httpd_parse: cb_header_field: processing field = Host
|
||||
D (17642) httpd_txrx: httpd_recv_with_opt: requested length = 128
|
||||
D (17652) httpd_txrx: httpd_recv_with_opt: pending length = 110
|
||||
D (17652) httpd_parse: read_block: received HTTP request block size = 110
|
||||
D (17662) httpd_parse: continue_parsing: skip pre-parsed data of size = 5
|
||||
D (17672) httpd_parse: continue_parsing: un-paused
|
||||
D (17682) httpd_parse: cb_header_field: processing field = Upgrade
|
||||
D (17682) httpd_parse: cb_header_value: processing value = websocket
|
||||
D (17692) httpd_parse: cb_header_field: processing field = Connection
|
||||
D (17702) httpd_parse: cb_header_value: processing value = Upgrade
|
||||
D (17702) httpd_parse: cb_header_field: processing field = Sec-WebSocket-Key
|
||||
D (17712) httpd_parse: cb_header_value: processing value = gfhjgfhjfj
|
||||
D (17722) httpd_parse: cb_header_field: processing field = Sec-WebSocket-Proto
|
||||
D (17722) httpd_parse: parse_block: parsed block size = 110
|
||||
D (17732) httpd_txrx: httpd_recv_with_opt: requested length = 128
|
||||
D (17742) httpd_txrx: httpd_recv_with_opt: received length = 40
|
||||
D (17742) httpd_parse: read_block: received HTTP request block size = 40
|
||||
D (17752) httpd_parse: cb_header_field: processing field = col
|
||||
D (17752) httpd_parse: cb_header_value: processing value = echo
|
||||
D (17762) httpd_parse: cb_header_field: processing field = Sec-WebSocket-Version
|
||||
D (17772) httpd_parse: cb_header_value: processing value = 13
|
||||
D (17772) httpd_parse: cb_headers_complete: bytes read = 169
|
||||
D (17782) httpd_parse: cb_headers_complete: content length = 0
|
||||
D (17792) httpd_parse: cb_headers_complete: Got an upgrade request
|
||||
D (17792) httpd_parse: pause_parsing: paused
|
||||
D (17802) httpd_parse: cb_no_body: message complete
|
||||
D (17802) httpd_parse: httpd_parse_req: parsing complete
|
||||
D (17812) httpd_uri: httpd_uri: request for /ws with type 1
|
||||
D (17812) httpd_uri: httpd_find_uri_handler: [0] = /ws
|
||||
D (17822) httpd_uri: httpd_uri: Responding WS handshake to sock 57
|
||||
D (17822) httpd_ws: httpd_ws_respond_server_handshake: Server key before encoding: gfhjgfhjfj258EAFA5-E914-47DA-95CA-C5AB0DC85B11
|
||||
D (17842) httpd_ws: httpd_ws_respond_server_handshake: Generated server key: Jg/fQVRsgwdDzYeG8yNBHRajUxw=
|
||||
D (17852) httpd_sess: httpd_sess_process: httpd_req_delete
|
||||
D (17852) httpd_sess: httpd_sess_process: success
|
||||
D (17862) httpd: httpd_server: doing select maxfd+1 = 58
|
||||
D (17892) httpd: httpd_server: processing socket 57
|
||||
D (17892) httpd_sess: httpd_sess_process: httpd_req_new
|
||||
D (17892) httpd_parse: httpd_req_new: New request, has WS? Yes, sd->ws_handler valid? Yes, sd->ws_close? No
|
||||
D (17902) httpd_parse: httpd_req_new: New WS request from existing socket
|
||||
D (17902) httpd_txrx: httpd_recv_with_opt: requested length = 1
|
||||
D (17912) httpd_txrx: httpd_recv_with_opt: received length = 1
|
||||
D (17912) httpd_ws: httpd_ws_get_frame_type: First byte received: 0x81
|
||||
D (17922) httpd_txrx: httpd_recv_with_opt: requested length = 1
|
||||
D (17932) httpd_txrx: httpd_recv_with_opt: received length = 1
|
||||
D (17932) httpd_txrx: httpd_recv_with_opt: requested length = 4
|
||||
D (17942) httpd_txrx: httpd_recv_with_opt: received length = 4
|
||||
D (17942) httpd_txrx: httpd_recv_with_opt: requested length = 13
|
||||
D (17952) httpd_txrx: httpd_recv_with_opt: received length = 13
|
||||
I (17962) ws_echo_server: Got packet with message: Trigger async
|
||||
I (17962) ws_echo_server: Packet type: 1
|
||||
D (17972) httpd_sess: httpd_sess_process: httpd_req_delete
|
||||
D (17972) httpd_sess: httpd_sess_process: success
|
||||
D (17982) httpd: httpd_server: doing select maxfd+1 = 58
|
||||
D (17982) httpd: httpd_server: processing ctrl message
|
||||
D (17992) httpd: httpd_process_ctrl_msg: work
|
||||
D (18002) httpd: httpd_server: doing select maxfd+1 = 58
|
||||
```
|
||||
|
||||
See the README.md file in the upper level 'examples' directory for more information about examples.
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "ws_echo_server.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
/* WebSocket Echo Server Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <esp_wifi.h>
|
||||
#include <esp_event.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_system.h>
|
||||
#include <nvs_flash.h>
|
||||
#include <sys/param.h>
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "protocol_examples_common.h"
|
||||
|
||||
#include <esp_http_server.h>
|
||||
|
||||
/* A simple example that demonstrates using websocket echo server
|
||||
*/
|
||||
static const char *TAG = "ws_echo_server";
|
||||
|
||||
/*
|
||||
* Structure holding server handle
|
||||
* and internal socket fd in order
|
||||
* to use out of request send
|
||||
*/
|
||||
struct async_resp_arg {
|
||||
httpd_handle_t hd;
|
||||
int fd;
|
||||
};
|
||||
|
||||
/*
|
||||
* async send function, which we put into the httpd work queue
|
||||
*/
|
||||
static void ws_async_send(void *arg)
|
||||
{
|
||||
static const char * data = "Async data";
|
||||
struct async_resp_arg *resp_arg = arg;
|
||||
httpd_handle_t hd = resp_arg->hd;
|
||||
int fd = resp_arg->fd;
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = (uint8_t*)data;
|
||||
ws_pkt.len = strlen(data);
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
|
||||
httpd_ws_send_frame_async(hd, fd, &ws_pkt);
|
||||
free(resp_arg);
|
||||
}
|
||||
|
||||
static esp_err_t trigger_async_send(httpd_handle_t handle, httpd_req_t *req)
|
||||
{
|
||||
struct async_resp_arg *resp_arg = malloc(sizeof(struct async_resp_arg));
|
||||
resp_arg->hd = req->handle;
|
||||
resp_arg->fd = httpd_req_to_sockfd(req);
|
||||
return httpd_queue_work(handle, ws_async_send, resp_arg);
|
||||
}
|
||||
|
||||
/*
|
||||
* This handler echos back the received ws data
|
||||
* and triggers an async send if certain message received
|
||||
*/
|
||||
static esp_err_t echo_handler(httpd_req_t *req)
|
||||
{
|
||||
uint8_t buf[128] = { 0 };
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
ws_pkt.payload = buf;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 128);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
|
||||
return ret;
|
||||
}
|
||||
ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
|
||||
ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
|
||||
if (ws_pkt.type == HTTPD_WS_TYPE_TEXT &&
|
||||
strcmp((char*)ws_pkt.payload,"Trigger async") == 0) {
|
||||
return trigger_async_send(req->handle, req);
|
||||
}
|
||||
|
||||
ret = httpd_ws_send_frame(req, &ws_pkt);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "httpd_ws_send_frame failed with %d", ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const httpd_uri_t ws = {
|
||||
.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = echo_handler,
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = true
|
||||
};
|
||||
|
||||
|
||||
static httpd_handle_t start_webserver(void)
|
||||
{
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
|
||||
// Start the httpd server
|
||||
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
|
||||
if (httpd_start(&server, &config) == ESP_OK) {
|
||||
// Registering the ws handler
|
||||
ESP_LOGI(TAG, "Registering URI handlers");
|
||||
httpd_register_uri_handler(server, &ws);
|
||||
return server;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Error starting server!");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void stop_webserver(httpd_handle_t server)
|
||||
{
|
||||
// Stop the httpd server
|
||||
httpd_stop(server);
|
||||
}
|
||||
|
||||
static void disconnect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server) {
|
||||
ESP_LOGI(TAG, "Stopping webserver");
|
||||
stop_webserver(*server);
|
||||
*server = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static void connect_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
httpd_handle_t* server = (httpd_handle_t*) arg;
|
||||
if (*server == NULL) {
|
||||
ESP_LOGI(TAG, "Starting webserver");
|
||||
*server = start_webserver();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
static httpd_handle_t server = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
/* This helper function configures Wi-Fi or Ethernet, as selected in menuconfig.
|
||||
* Read "Establishing Wi-Fi or Ethernet Connection" section in
|
||||
* examples/protocols/README.md for more information about this function.
|
||||
*/
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
/* Register event handlers to stop the server when Wi-Fi or Ethernet is disconnected,
|
||||
* and re-start it upon connection.
|
||||
*/
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_WIFI
|
||||
#ifdef CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &connect_handler, &server));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ETHERNET_EVENT_DISCONNECTED, &disconnect_handler, &server));
|
||||
#endif // CONFIG_EXAMPLE_CONNECT_ETHERNET
|
||||
|
||||
/* Start the server for the first time */
|
||||
server = start_webserver();
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_LOG_DEFAULT_LEVEL_DEBUG=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
@@ -0,0 +1,147 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright 2020 Espressif Systems (Shanghai) PTE LTD
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
from tiny_test_fw import Utility
|
||||
import ttfw_idf
|
||||
import os
|
||||
import six
|
||||
import socket
|
||||
import hashlib
|
||||
import base64
|
||||
import struct
|
||||
|
||||
|
||||
OPCODE_TEXT = 0x1
|
||||
OPCODE_BIN = 0x2
|
||||
OPCODE_PING = 0x9
|
||||
OPCODE_PONG = 0xa
|
||||
|
||||
|
||||
class WsClient:
|
||||
def __init__(self, ip, port):
|
||||
self.port = port
|
||||
self.ip = ip
|
||||
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.client_key = "abcdefghjk"
|
||||
self.socket.settimeout(10.0)
|
||||
|
||||
def __enter__(self):
|
||||
self.socket.connect((self.ip, self.port))
|
||||
self._handshake()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.socket.close()
|
||||
|
||||
def _handshake(self):
|
||||
MAGIC_STRING = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
||||
client_key = self.client_key + MAGIC_STRING
|
||||
expected_accept = base64.standard_b64encode(hashlib.sha1(client_key.encode()).digest())
|
||||
request = ('GET /ws HTTP/1.1\r\nHost: localhost\r\nUpgrade: websocket\r\nConnection: '
|
||||
'Upgrade\r\nSec-WebSocket-Key: {}\r\n'
|
||||
'Sec-WebSocket-Version: 13\r\n\r\n'.format(self.client_key))
|
||||
self.socket.send(request.encode('utf-8'))
|
||||
response = self.socket.recv(1024)
|
||||
ws_accept = re.search(b'Sec-WebSocket-Accept: (.*)\r\n', response, re.IGNORECASE)
|
||||
if ws_accept and ws_accept.group(1) is not None and ws_accept.group(1) == expected_accept:
|
||||
pass
|
||||
else:
|
||||
raise("Unexpected Sec-WebSocket-Accept, handshake response: {}".format(response))
|
||||
|
||||
def _masked(self, data):
|
||||
mask = struct.unpack('B' * 4, os.urandom(4))
|
||||
out = list(mask)
|
||||
for i, d in enumerate(struct.unpack('B' * len(data), data)):
|
||||
out.append(d ^ mask[i % 4])
|
||||
return struct.pack('B' * len(out), *out)
|
||||
|
||||
def _ws_encode(self, data="", opcode=OPCODE_TEXT, mask=1):
|
||||
data = data.encode('utf-8')
|
||||
length = len(data)
|
||||
if length >= 126:
|
||||
raise("Packet length of {} not supported!".format(length))
|
||||
frame_header = chr(1 << 7 | opcode)
|
||||
frame_header += chr(mask << 7 | length)
|
||||
frame_header = six.b(frame_header)
|
||||
if not mask:
|
||||
return frame_header + data
|
||||
return frame_header + self._masked(data)
|
||||
|
||||
def read(self):
|
||||
header = self.socket.recv(2)
|
||||
if not six.PY3:
|
||||
header = [ord(character) for character in header]
|
||||
opcode = header[0] & 15
|
||||
length = header[1] & 127
|
||||
payload = self.socket.recv(length)
|
||||
return opcode, payload.decode('utf-8')
|
||||
|
||||
def write(self, data="", opcode=OPCODE_TEXT, mask=1):
|
||||
return self.socket.sendall(self._ws_encode(data=data, opcode=opcode, mask=mask))
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="Example_WIFI")
|
||||
def test_examples_protocol_http_ws_echo_server(env, extra_data):
|
||||
# Acquire DUT
|
||||
dut1 = env.get_dut("http_server", "examples/protocols/http_server/ws_echo_server", dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
# Get binary file
|
||||
binary_file = os.path.join(dut1.app.binary_path, "ws_echo_server.bin")
|
||||
bin_size = os.path.getsize(binary_file)
|
||||
ttfw_idf.log_performance("http_ws_server_bin_size", "{}KB".format(bin_size // 1024))
|
||||
ttfw_idf.check_performance("http_ws_server_bin_size", bin_size // 1024, dut1.TARGET)
|
||||
|
||||
# Upload binary and start testing
|
||||
Utility.console_log("Starting ws-echo-server test app based on http_server")
|
||||
dut1.start_app()
|
||||
|
||||
# Parse IP address of STA
|
||||
Utility.console_log("Waiting to connect with AP")
|
||||
got_ip = dut1.expect(re.compile(r"IPv4 address: (\d+.\d+.\d+.\d+)"), timeout=60)[0]
|
||||
got_port = dut1.expect(re.compile(r"Starting server on port: '(\d+)'"), timeout=60)[0]
|
||||
|
||||
Utility.console_log("Got IP : " + got_ip)
|
||||
Utility.console_log("Got Port : " + got_port)
|
||||
|
||||
# Start ws server test
|
||||
with WsClient(got_ip, int(got_port)) as ws:
|
||||
DATA = 'Espressif'
|
||||
for expected_opcode in [OPCODE_TEXT, OPCODE_BIN, OPCODE_PING]:
|
||||
ws.write(data=DATA, opcode=expected_opcode)
|
||||
opcode, data = ws.read()
|
||||
Utility.console_log("Testing opcode {}: Received opcode:{}, data:{}".format(expected_opcode, opcode, data))
|
||||
if expected_opcode == OPCODE_PING:
|
||||
dut1.expect("Got a WS PING frame, Replying PONG")
|
||||
if opcode != OPCODE_PONG or data != DATA:
|
||||
raise RuntimeError("Failed to receive correct opcode:{} or data:{}".format(opcode, data))
|
||||
continue
|
||||
dut_data = dut1.expect(re.compile(r"Got packet with message: ([A-Za-z0-9_]*)"))[0]
|
||||
dut_opcode = int(dut1.expect(re.compile(r"Packet type: ([0-9]*)"))[0])
|
||||
if opcode != expected_opcode or data != DATA or opcode != dut_opcode or data != dut_data:
|
||||
raise RuntimeError("Failed to receive correct opcode:{} or data:{}".format(opcode, data))
|
||||
ws.write(data="Trigger async", opcode=OPCODE_TEXT)
|
||||
opcode, data = ws.read()
|
||||
Utility.console_log("Testing async send: Received opcode:{}, data:{}".format(opcode, data))
|
||||
if opcode != OPCODE_TEXT or data != "Async data":
|
||||
raise RuntimeError("Failed to receive correct opcode:{} or data:{}".format(opcode, data))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_protocol_http_ws_echo_server()
|
||||
Reference in New Issue
Block a user