Merge remote-tracking branch 'origin/httpd' into master-cmake

Conflicts:
	.cproject
	.gitmodules
	.project
	.pydevproject
	.settings/language.settings.xml
	.settings/org.eclipse.cdt.core.prefs
	components/cmd_i2c/CMakeLists.txt
	components/cmd_i2c/cmd_i2ctools.c
	components/cmd_i2c/component.mk
	components/cmd_nvs/cmd_nvs.c
	components/cmd_nvs/component.mk
	components/cmd_system/cmd_system.c
	components/cmd_system/component.mk
	components/config/config.c
	components/config/config.h
	components/config/nvs_utilities.c
	components/display/CMakeLists.txt
	components/driver_bt/CMakeLists.txt
	components/driver_bt/component.mk
	components/raop/raop.c
	components/services/CMakeLists.txt
	components/squeezelite-ota/cmd_ota.c
	components/squeezelite-ota/squeezelite-ota.c
	components/squeezelite-ota/squeezelite-ota.h
	components/squeezelite/component.mk
	components/telnet/CMakeLists.txt
	components/wifi-manager/CMakeLists.txt
	components/wifi-manager/dns_server.c
	components/wifi-manager/http_server.c
	components/wifi-manager/http_server.h
	components/wifi-manager/wifi_manager.c
	components/wifi-manager/wifi_manager.h
	main/CMakeLists.txt
	main/console.c
	main/esp_app_main.c
	main/platform_esp32.h
This commit is contained in:
Sebastien
2020-03-10 13:55:22 -04:00
53 changed files with 4302 additions and 1431 deletions

View File

@@ -0,0 +1,93 @@
// 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.
#ifndef __ESP_HTTP_SERVER_H_
#define __ESP_HTTP_SERVER_H_
#include <stdio.h>
#include <string.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <http_parser.h>
#include <sdkconfig.h>
#include <esp_err.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Starts the web server
*
* Create an instance of HTTP server and allocate memory/resources for it
* depending upon the specified configuration.
*
* Example usage:
* @code{c}
*
* //Function for starting the webserver
* httpd_handle_t start_webserver(void)
* {
* // Generate default configuration
* httpd_config_t config = HTTPD_DEFAULT_CONFIG();
*
* // Empty handle to http_server
* httpd_handle_t server = NULL;
*
* // Start the httpd server
* if (httpd_start(&server, &config) == ESP_OK) {
* // Register URI handlers
* httpd_register_uri_handler(server, &uri_get);
* httpd_register_uri_handler(server, &uri_post);
* }
* // If server failed to start, handle will be NULL
* return server;
* }
*
* @endcode
*
* @param[in] config Configuration for new instance of the server
* @param[out] handle Handle to newly created instance of the server. NULL on error
* @return
* - ESP_OK : Instance created successfully
* - ESP_ERR_INVALID_ARG : Null argument(s)
* - ESP_ERR_HTTPD_ALLOC_MEM : Failed to allocate memory for instance
* - ESP_ERR_HTTPD_TASK : Failed to launch server task
*/
esp_err_t __httpd_start(httpd_handle_t *handle, const httpd_config_t *config);
static inline int __httpd_os_thread_create_static(TaskHandle_t *thread,
const char *name, uint16_t stacksize, int prio,
void (*thread_routine)(void *arg), void *arg,
BaseType_t core_id)
{
StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), (MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT));
StackType_t *xStack = heap_caps_malloc(stacksize,(MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT));
//
*thread = xTaskCreateStaticPinnedToCore(thread_routine, name, stacksize, arg, prio, xStack,xTaskBuffer,core_id);
if (*thread) {
return ESP_OK;
}
return ESP_FAIL;
}
#ifdef __cplusplus
}
#endif
#endif /* ! _ESP_HTTP_SERVER_H_ */

View File

@@ -0,0 +1,373 @@
// 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.
#include <string.h>
#include <sys/socket.h>
#include <sys/param.h>
#include <errno.h>
#include <esp_log.h>
#include <esp_err.h>
#include <assert.h>
#include <esp_http_server.h>
#include <_esp_http_server.h>
#include "esp_httpd_priv.h"
#include "ctrl_sock.h"
static const char *TAG = "_httpd";
struct httpd_ctrl_data {
enum httpd_ctrl_msg {
HTTPD_CTRL_SHUTDOWN,
HTTPD_CTRL_WORK,
} hc_msg;
httpd_work_fn_t hc_work;
void *hc_work_arg;
};
static esp_err_t _httpd_server_init(struct httpd_data *hd)
{
int fd = socket(PF_INET6, SOCK_STREAM, 0);
if (fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in socket (%d)"), errno);
return ESP_FAIL;
}
struct in6_addr inaddr_any = IN6ADDR_ANY_INIT;
struct sockaddr_in6 serv_addr = {
.sin6_family = PF_INET6,
.sin6_addr = inaddr_any,
.sin6_port = htons(hd->config.server_port)
};
/* Enable SO_REUSEADDR to allow binding to the same
* address and port when restarting the server */
int enable = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) {
/* This will fail if CONFIG_LWIP_SO_REUSE is not enabled. But
* it does not affect the normal working of the HTTP Server */
ESP_LOGW(TAG, LOG_FMT("error enabling SO_REUSEADDR (%d)"), errno);
}
int ret = bind(fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
if (ret < 0) {
ESP_LOGE(TAG, LOG_FMT("error in bind (%d)"), errno);
close(fd);
return ESP_FAIL;
}
ret = listen(fd, hd->config.backlog_conn);
if (ret < 0) {
ESP_LOGE(TAG, LOG_FMT("error in listen (%d)"), errno);
close(fd);
return ESP_FAIL;
}
int ctrl_fd = cs_create_ctrl_sock(hd->config.ctrl_port);
if (ctrl_fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in creating ctrl socket (%d)"), errno);
close(fd);
return ESP_FAIL;
}
int msg_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (msg_fd < 0) {
ESP_LOGE(TAG, LOG_FMT("error in creating msg socket (%d)"), errno);
close(fd);
close(ctrl_fd);
return ESP_FAIL;
}
hd->listen_fd = fd;
hd->ctrl_fd = ctrl_fd;
hd->msg_fd = msg_fd;
return ESP_OK;
}
static void _httpd_process_ctrl_msg(struct httpd_data *hd)
{
struct httpd_ctrl_data msg;
int ret = recv(hd->ctrl_fd, &msg, sizeof(msg), 0);
if (ret <= 0) {
ESP_LOGW(TAG, LOG_FMT("error in recv (%d)"), errno);
return;
}
if (ret != sizeof(msg)) {
ESP_LOGW(TAG, LOG_FMT("incomplete msg"));
return;
}
switch (msg.hc_msg) {
case HTTPD_CTRL_WORK:
if (msg.hc_work) {
ESP_LOGD(TAG, LOG_FMT("work"));
(*msg.hc_work)(msg.hc_work_arg);
}
break;
case HTTPD_CTRL_SHUTDOWN:
ESP_LOGD(TAG, LOG_FMT("shutdown"));
hd->hd_td.status = THREAD_STOPPING;
break;
default:
break;
}
}
static esp_err_t _httpd_accept_conn(struct httpd_data *hd, int listen_fd)
{
/* If no space is available for new session, close the least recently used one */
if (hd->config.lru_purge_enable == true) {
if (!httpd_is_sess_available(hd)) {
/* Queue asynchronous closure of the least recently used session */
return httpd_sess_close_lru(hd);
/* Returning from this allowes the main server thread to process
* the queued asynchronous control message for closing LRU session.
* Since connection request hasn't been addressed yet using accept()
* therefore _httpd_accept_conn() will be called again, but this time
* with space available for one session
*/
}
}
struct sockaddr_in addr_from;
socklen_t addr_from_len = sizeof(addr_from);
int new_fd = accept(listen_fd, (struct sockaddr *)&addr_from, &addr_from_len);
if (new_fd < 0) {
ESP_LOGW(TAG, LOG_FMT("error in accept (%d)"), errno);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("newfd = %d"), new_fd);
struct timeval tv;
/* Set recv timeout of this fd as per config */
tv.tv_sec = hd->config.recv_wait_timeout;
tv.tv_usec = 0;
setsockopt(new_fd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
/* Set send timeout of this fd as per config */
tv.tv_sec = hd->config.send_wait_timeout;
tv.tv_usec = 0;
setsockopt(new_fd, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
if (ESP_OK != httpd_sess_new(hd, new_fd)) {
ESP_LOGW(TAG, LOG_FMT("session creation failed"));
close(new_fd);
return ESP_FAIL;
}
ESP_LOGD(TAG, LOG_FMT("complete"));
return ESP_OK;
}
/* Manage in-coming connection or data requests */
static esp_err_t _httpd_server(struct httpd_data *hd)
{
fd_set read_set;
FD_ZERO(&read_set);
if (hd->config.lru_purge_enable || httpd_is_sess_available(hd)) {
/* Only listen for new connections if server has capacity to
* handle more (or when LRU purge is enabled, in which case
* older connections will be closed) */
FD_SET(hd->listen_fd, &read_set);
}
FD_SET(hd->ctrl_fd, &read_set);
int tmp_max_fd;
httpd_sess_set_descriptors(hd, &read_set, &tmp_max_fd);
int maxfd = MAX(hd->listen_fd, tmp_max_fd);
tmp_max_fd = maxfd;
maxfd = MAX(hd->ctrl_fd, tmp_max_fd);
ESP_LOGD(TAG, LOG_FMT("doing select maxfd+1 = %d"), maxfd + 1);
int active_cnt = select(maxfd + 1, &read_set, NULL, NULL, NULL);
if (active_cnt < 0) {
ESP_LOGE(TAG, LOG_FMT("error in select (%d)"), errno);
httpd_sess_delete_invalid(hd);
return ESP_OK;
}
/* Case0: Do we have a control message? */
if (FD_ISSET(hd->ctrl_fd, &read_set)) {
ESP_LOGD(TAG, LOG_FMT("processing ctrl message"));
_httpd_process_ctrl_msg(hd);
if (hd->hd_td.status == THREAD_STOPPING) {
ESP_LOGD(TAG, LOG_FMT("stopping thread"));
return ESP_FAIL;
}
}
/* Case1: Do we have any activity on the current data
* sessions? */
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
if (FD_ISSET(fd, &read_set) || (httpd_sess_pending(hd, fd))) {
ESP_LOGD(TAG, LOG_FMT("processing socket %d"), fd);
if (httpd_sess_process(hd, fd) != ESP_OK) {
ESP_LOGD(TAG, LOG_FMT("closing socket %d"), fd);
close(fd);
/* Delete session and update fd to that
* preceding the one being deleted */
fd = httpd_sess_delete(hd, fd);
}
}
}
/* Case2: Do we have any incoming connection requests to
* process? */
if (FD_ISSET(hd->listen_fd, &read_set)) {
ESP_LOGD(TAG, LOG_FMT("processing listen socket %d"), hd->listen_fd);
if (_httpd_accept_conn(hd, hd->listen_fd) != ESP_OK) {
ESP_LOGW(TAG, LOG_FMT("error accepting new connection"));
}
}
return ESP_OK;
}
static void _httpd_close_all_sessions(struct httpd_data *hd)
{
int fd = -1;
while ((fd = httpd_sess_iterate(hd, fd)) != -1) {
ESP_LOGD(TAG, LOG_FMT("cleaning up socket %d"), fd);
httpd_sess_delete(hd, fd);
close(fd);
}
}
/* The main HTTPD thread */
static void _httpd_thread(void *arg)
{
int ret;
struct httpd_data *hd = (struct httpd_data *) arg;
hd->hd_td.status = THREAD_RUNNING;
ESP_LOGD(TAG, LOG_FMT("web server started"));
while (1) {
ret = _httpd_server(hd);
if (ret != ESP_OK) {
break;
}
}
ESP_LOGD(TAG, LOG_FMT("web server exiting"));
close(hd->msg_fd);
cs_free_ctrl_sock(hd->ctrl_fd);
_httpd_close_all_sessions(hd);
close(hd->listen_fd);
hd->hd_td.status = THREAD_STOPPED;
httpd_os_thread_delete();
}
static struct httpd_data *__httpd_create(const httpd_config_t *config)
{
/* Allocate memory for httpd instance data */
struct httpd_data *hd = calloc(1, sizeof(struct httpd_data));
if (!hd) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP server instance"));
return NULL;
}
hd->hd_calls = calloc(config->max_uri_handlers, sizeof(httpd_uri_t *));
if (!hd->hd_calls) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP URI handlers"));
free(hd);
return NULL;
}
hd->hd_sd = calloc(config->max_open_sockets, sizeof(struct sock_db));
if (!hd->hd_sd) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP session data"));
free(hd->hd_calls);
free(hd);
return NULL;
}
struct httpd_req_aux *ra = &hd->hd_req_aux;
ra->resp_hdrs = calloc(config->max_resp_headers, sizeof(struct resp_hdr));
if (!ra->resp_hdrs) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP response headers"));
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
hd->err_handler_fns = calloc(HTTPD_ERR_CODE_MAX, sizeof(httpd_err_handler_func_t));
if (!hd->err_handler_fns) {
ESP_LOGE(TAG, LOG_FMT("Failed to allocate memory for HTTP error handlers"));
free(ra->resp_hdrs);
free(hd->hd_sd);
free(hd->hd_calls);
free(hd);
return NULL;
}
/* Save the configuration for this instance */
hd->config = *config;
return hd;
}
static void _httpd_delete(struct httpd_data *hd)
{
struct httpd_req_aux *ra = &hd->hd_req_aux;
/* Free memory of httpd instance data */
free(hd->err_handler_fns);
free(ra->resp_hdrs);
free(hd->hd_sd);
/* Free registered URI handlers */
httpd_unregister_all_uri_handlers(hd);
free(hd->hd_calls);
free(hd);
}
esp_err_t __httpd_start(httpd_handle_t *handle, const httpd_config_t *config)
{
if (handle == NULL || config == NULL) {
return ESP_ERR_INVALID_ARG;
}
/* Sanity check about whether LWIP is configured for providing the
* maximum number of open sockets sufficient for the server. Though,
* this check doesn't guarantee that many sockets will actually be
* available at runtime as other processes may use up some sockets.
* Note that server also uses 3 sockets for its internal use :
* 1) listening for new TCP connections
* 2) for sending control messages over UDP
* 3) for receiving control messages over UDP
* So the total number of required sockets is max_open_sockets + 3
*/
if (CONFIG_LWIP_MAX_SOCKETS < config->max_open_sockets + 3) {
ESP_LOGE(TAG, "Configuration option max_open_sockets is too large (max allowed %d)\n\t"
"Either decrease this or configure LWIP_MAX_SOCKETS to a larger value",
CONFIG_LWIP_MAX_SOCKETS - 3);
return ESP_ERR_INVALID_ARG;
}
struct httpd_data *hd = __httpd_create(config);
if (hd == NULL) {
/* Failed to allocate memory */
return ESP_ERR_HTTPD_ALLOC_MEM;
}
if (_httpd_server_init(hd) != ESP_OK) {
_httpd_delete(hd);
return ESP_FAIL;
}
httpd_sess_init(hd);
if (__httpd_os_thread_create_static(&hd->hd_td.handle, "httpd",
hd->config.stack_size,
hd->config.task_priority,
_httpd_thread, hd,
hd->config.core_id) != ESP_OK) {
/* Failed to launch task */
_httpd_delete(hd);
return ESP_ERR_HTTPD_TASK;
}
*handle = (httpd_handle_t *)hd;
return ESP_OK;
}

View File

@@ -10,7 +10,28 @@ if (!String.prototype.format) {
});
};
}
var nvs_type_t = {
NVS_TYPE_U8 : 0x01, /*!< Type uint8_t */
NVS_TYPE_I8 : 0x11, /*!< Type int8_t */
NVS_TYPE_U16 : 0x02, /*!< Type uint16_t */
NVS_TYPE_I16 : 0x12, /*!< Type int16_t */
NVS_TYPE_U32 : 0x04, /*!< Type uint32_t */
NVS_TYPE_I32 : 0x14, /*!< Type int32_t */
NVS_TYPE_U64 : 0x08, /*!< Type uint64_t */
NVS_TYPE_I64 : 0x18, /*!< Type int64_t */
NVS_TYPE_STR : 0x21, /*!< Type string */
NVS_TYPE_BLOB : 0x42, /*!< Type blob */
NVS_TYPE_ANY : 0xff /*!< Must be last */
} ;
var task_state_t = {
0 : "eRunning", /*!< A task is querying the state of itself, so must be running. */
1 : "eReady", /*!< The task being queried is in a read or pending ready list. */
2 : "eBlocked", /*!< The task being queried is in the Blocked state. */
3 : "eSuspended", /*!< The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
4 : "eDeleted"
}
var releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';
var recovery = false;
var enableAPTimer = true;
@@ -19,7 +40,6 @@ var commandHeader = 'squeezelite -b 500:2000 -d all=info ';
var pname, ver, otapct, otadsc;
var blockAjax = false;
var blockFlashButton = false;
var lastMsg = '';
var apList = null;
var selectedSSID = "";
@@ -189,20 +209,29 @@ $(document).ready(function(){
$("input#autoexec-cb").on("click", function() {
var data = { 'timestamp': Date.now() };
autoexec = (this.checked)?1:0;
data['autoexec'] = autoexec;
showMessage('please wait for the ESP32 to reboot', 'WARNING');
data['config'] = {};
data['config'] = {
autoexec : {
value : autoexec,
type : 33
}
}
showMessage('please wait for the ESP32 to reboot', 'MESSAGING_WARNING');
$.ajax({
url: '/config.json',
dataType: 'text',
method: 'POST',
cache: false,
headers: { "X-Custom-autoexec": autoexec },
// headers: { "X-Custom-autoexec": autoexec },
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
data: JSON.stringify(data),
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
},
complete: function(response) {
//var returnedResponse = JSON.parse(response.responseText);
@@ -219,7 +248,7 @@ $(document).ready(function(){
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
},
complete: function(response) {
console.log('reboot call completed');
@@ -232,20 +261,26 @@ $(document).ready(function(){
$("input#save-autoexec1").on("click", function() {
var data = { 'timestamp': Date.now() };
autoexec1 = $("#autoexec1").val();
data['autoexec1'] = autoexec1;
data['config'] = {};
data['config'] = {
autoexec1 : {
value : autoexec1,
type : 33
}
}
$.ajax({
url: '/config.json',
dataType: 'text',
method: 'POST',
cache: false,
headers: { "X-Custom-autoexec1": autoexec1 },
// headers: { "X-Custom-autoexec1": autoexec1 },
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
}
});
console.log('sent config JSON with headers:', autoexec1);
@@ -254,15 +289,19 @@ $(document).ready(function(){
$("input#save-gpio").on("click", function() {
var data = { 'timestamp': Date.now() };
var config = {};
var headers = {};
$("input.gpio").each(function() {
var id = $(this)[0].id;
var pin = $(this).val();
if (pin != '') {
headers["X-Custom-"+id] = pin;
data[id] = pin;
config[id] = {};
config[id].value = pin;
config[id].type = nvs_type_t.NVS_TYPE_STR;
}
});
data['config'] = config;
$.ajax({
url: '/config.json',
dataType: 'text',
@@ -274,7 +313,7 @@ $(document).ready(function(){
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
}
});
console.log('sent config JSON with headers:', JSON.stringify(headers));
@@ -284,23 +323,40 @@ $(document).ready(function(){
$("#save-nvs").on("click", function() {
var headers = {};
var data = { 'timestamp': Date.now() };
var config = {};
$("input.nvs").each(function() {
var key = $(this)[0].id;
var val = $(this).val();
var nvs_type = parseInt($(this)[0].attributes.nvs_type.nodeValue,10);
if (key != '') {
headers["X-Custom-"+key] = val;
data[key] = {};
data[key].value = val;
data[key].type = 33;
config[key] = {};
if(nvs_type == nvs_type_t.NVS_TYPE_U8
|| nvs_type == nvs_type_t.NVS_TYPE_I8
|| nvs_type == nvs_type_t.NVS_TYPE_U16
|| nvs_type == nvs_type_t.NVS_TYPE_I16
|| nvs_type == nvs_type_t.NVS_TYPE_U32
|| nvs_type == nvs_type_t.NVS_TYPE_I32
|| nvs_type == nvs_type_t.NVS_TYPE_U64
|| nvs_type == nvs_type_t.NVS_TYPE_I64) {
config[key].value = parseInt(val);
}
else {
config[key].value = val;
}
config[key].type = nvs_type;
}
});
var key = $("#nvs-new-key").val();
var val = $("#nvs-new-value").val();
if (key != '') {
headers["X-Custom-"+key] = val;
data[key] = {};
data[key].value = val;
// headers["X-Custom-" +key] = val;
config[key] = {};
config[key].value = val;
config[key].type = 33;
}
data['config'] = config;
$.ajax({
url: '/config.json',
dataType: 'text',
@@ -308,35 +364,64 @@ $(document).ready(function(){
cache: false,
headers: headers,
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
data : JSON.stringify(data),
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
}
});
console.log('sent config JSON with headers:', JSON.stringify(headers));
console.log('sent config JSON with data:', JSON.stringify(data));
});
$("#fwUpload").on("click", function() {
var upload_path = "/flash.json";
var fileInput = document.getElementById("flashfilename").files;
if (fileInput.length == 0) {
alert("No file selected!");
} else {
var file = fileInput[0];
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (xhttp.readyState == 4) {
if (xhttp.status == 200) {
showMessage(xhttp.responseText, 'MESSAGING_INFO')
} else if (xhttp.status == 0) {
showMessage("Upload connection was closed abruptly!", 'MESSAGING_ERROR');
} else {
showMessage(xhttp.status + " Error!\n" + xhttp.responseText, 'MESSAGING_ERROR');
}
}
};
xhttp.open("POST", upload_path, true);
xhttp.send(file);
}
enableStatusTimer = true;
});
$("#flash").on("click", function() {
var data = { 'timestamp': Date.now() };
if (blockFlashButton) return;
blockFlashButton = true;
var url = $("#fwurl").val();
data['fwurl'] = url;
data['config'] = {
fwurl : {
value : url,
type : 33
}
};
$.ajax({
url: '/config.json',
dataType: 'text',
method: 'POST',
cache: false,
headers: { "X-Custom-fwurl": url },
contentType: 'application/json; charset=utf-8',
data: JSON.stringify(data),
data: JSON.stringify(data),
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
}
});
enableStatusTimer = true;
@@ -509,13 +594,17 @@ function performConnect(conntype){
dataType: 'text',
method: 'POST',
cache: false,
headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname },
// headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname },
contentType: 'application/json; charset=utf-8',
data: { 'timestamp': Date.now()},
data: JSON.stringify({ 'timestamp': Date.now(),
'ssid' : selectedSSID,
'pwd' : pwd,
'host_name' : dhcpname
}),
error: function (xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
}
});
@@ -564,11 +653,76 @@ function refreshAPHTML(data){
$( "#wifi-list" ).html(h)
}
function getMessages() {
$.getJSON("/messages.json?1", function(data) {
data.forEach(function(msg) {
var msg_age = msg["current_time"] - msg["sent_time"];
var msg_time = new Date();
msg_time.setTime( msg_time.getTime() - msg_age );
switch (msg["class"]) {
case "MESSAGING_CLASS_OTA":
//message: "{"ota_dsc":"Erasing flash complete","ota_pct":0}"
var ota_data = JSON.parse(msg["message"]);
if (ota_data.hasOwnProperty('ota_pct') && ota_data['ota_pct'] != 0){
otapct = ota_data['ota_pct'];
$('.progress-bar').css('width', otapct+'%').attr('aria-valuenow', otapct);
$('.progress-bar').html(otapct+'%');
}
if (ota_data.hasOwnProperty('ota_dsc') && ota_data['ota_dsc'] != ''){
otadsc = ota_data['ota_dsc'];
$("span#flash-status").html(otadsc);
if (otadsc.match(/Error:/) || otapct > 95) {
blockFlashButton = false;
enableStatusTimer = true;
}
}
break;
case "MESSAGING_CLASS_STATS":
// for task states, check structure : task_state_t
var stats_data = JSON.parse(msg["message"]);
console.log(msg_time.toLocaleString() + " - Number of tasks on the ESP32: " + stats_data["ntasks"]);
var stats_tasks = stats_data["tasks"];
console.log(msg_time.toLocaleString() + '\tname' + '\tcpu' + '\tstate'+ '\tminstk'+ '\tbprio'+ '\tcprio'+ '\tnum' );
stats_tasks.forEach(function(task) {
console.log(msg_time.toLocaleString() + '\t' + task["nme"] + '\t'+ task["cpu"] + '\t'+ task_state_t[task["st"]]+ '\t'+ task["minstk"]+ '\t'+ task["bprio"]+ '\t'+ task["cprio"]+ '\t'+ task["num"]);
});
break;
case "MESSAGING_CLASS_SYSTEM":
showMessage(msg["message"], msg["type"],msg_age);
$("#syslogTable").append(
"<tr class='"+msg["type"]+"'>"+
"<td>"+msg_time.toLocaleString()+"</td>"+
"<td>"+msg["message"]+"</td>"+
"</tr>"
);
break;
default:
break;
}
});
})
.fail(function(xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
});
/*
Minstk is minimum stack space left
Bprio is base priority
cprio is current priority
nme is name
st is task state. I provided a "typedef" that you can use to convert to text
cpu is cpu percent used
*/
}
function checkStatus(){
RepeatCheckStatusInterval();
if (!enableStatusTimer) return;
if (blockAjax) return;
blockAjax = true;
getMessages();
$.getJSON( "/status.json", function( data ) {
if (data.hasOwnProperty('ssid') && data['ssid'] != ""){
if (data["ssid"] === selectedSSID){
@@ -659,20 +813,24 @@ function checkStatus(){
$("#otadiv").show();
$('a[href^="#tab-audio"]').hide();
$('a[href^="#tab-gpio"]').show();
$('#uploaddiv').show();
$("footer.footer").removeClass('sl');
$("footer.footer").addClass('recovery');
$("#boot-button").html('Reboot');
$("#boot-form").attr('action', '/reboot_ota.json');
enableStatusTimer = true;
} else {
recovery = false;
$("#otadiv").hide();
$('a[href^="#tab-audio"]').show();
$('a[href^="#tab-gpio"]').hide();
$('#uploaddiv').hide();
$("footer.footer").removeClass('recovery');
$("footer.footer").addClass('sl');
$("#boot-button").html('Recovery');
$("#boot-form").attr('action', '/recovery.json');
enableStatusTimer = false;
}
}
@@ -683,29 +841,10 @@ function checkStatus(){
ver = data['version'];
$("span#foot-fw").html("fw: <strong>"+ver+"</strong>, mode: <strong>"+pname+"</strong>");
}
if (data.hasOwnProperty('ota_pct') && data['ota_pct'] != 0){
otapct = data['ota_pct'];
$('.progress-bar').css('width', otapct+'%').attr('aria-valuenow', otapct);
$('.progress-bar').html(otapct+'%');
}
if (data.hasOwnProperty('ota_dsc') && data['ota_dsc'] != ''){
otadsc = data['ota_dsc'];
$("span#flash-status").html(otadsc);
if (otadsc.match(/Error:/) || otapct > 95) {
blockFlashButton = false;
enableStatusTimer = true;
}
} else {
else {
$("span#flash-status").html('');
}
if (data.hasOwnProperty('message') && data['message'] != ''){
var msg = data['message'].text;
var severity = data['message'].severity;
if (msg != lastMsg) {
showMessage(msg, severity);
lastMsg = msg;
}
}
if (data.hasOwnProperty('Voltage')) {
var voltage = data['Voltage'];
var layer;
@@ -735,7 +874,7 @@ function checkStatus(){
.fail(function(xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
blockAjax = false;
});
}
@@ -770,7 +909,7 @@ function getConfig() {
"<tr>"+
"<td>"+key+"</td>"+
"<td class='value'>"+
"<input type='text' class='form-control nvs' id='"+key+"'>"+
"<input type='text' class='form-control nvs' id='"+key+"' nvs_type="+data[key].type+" >"+
"</td>"+
"</tr>"
);
@@ -783,7 +922,7 @@ function getConfig() {
"<input type='text' class='form-control' id='nvs-new-key' placeholder='new key'>"+
"</td>"+
"<td>"+
"<input type='text' class='form-control' id='nvs-new-value' placeholder='new value'>"+
"<input type='text' class='form-control' id='nvs-new-value' placeholder='new value' nvs_type=33 >"+ // todo: provide a way to choose field type
"</td>"+
"</tr>"
);
@@ -791,19 +930,23 @@ function getConfig() {
.fail(function(xhr, ajaxOptions, thrownError) {
console.log(xhr.status);
console.log(thrownError);
if (thrownError != '') showMessage(thrownError, 'ERROR');
if (thrownError != '') showMessage(thrownError, 'MESSAGING_ERROR');
blockAjax = false;
});
}
function showMessage(message, severity) {
if (severity == 'INFO') {
function showMessage(message, severity, age=0) {
if (severity == 'MESSAGING_INFO') {
$('#message').css('background', '#6af');
} else if (severity == 'WARNING') {
} else if (severity == 'MESSAGING_WARNING') {
$('#message').css('background', '#ff0');
} else if (severity == 'MESSAGING_ERROR' ) {
$('#message').css('background', '#f00');
} else {
$('#message').css('background', '#f00');
}
$('#message').html(message);
$("#content").fadeTo("slow", 0.3, function() {
$("#message").show(500).delay(5000).hide(500, function() {
@@ -815,3 +958,6 @@ function showMessage(message, severity) {
function inRange(x, min, max) {
return ((x-min)*(x-max) <= 0);
}

View File

@@ -7,14 +7,8 @@
# please read the SDK documents if you need to do this.
#
COMPONENT_EMBED_FILES := style.css code.js index.html bootstrap.min.css.gz jquery.min.js.gz popper.min.js.gz bootstrap.min.js.gz
#CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG
CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO \
-I$(COMPONENT_PATH)/../tools
COMPONENT_ADD_INCLUDEDIRS := .
COMPONENT_ADD_INCLUDEDIRS += $(COMPONENT_PATH)/../tools
COMPONENT_ADD_INCLUDEDIRS += $(COMPONENT_PATH)/../squeezelite-ota
COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/
COMPONENT_ADD_INCLUDEDIRS := .
COMPONENT_EXTRA_INCLUDES += $(IDF_PATH)/components/esp_http_server/src $(IDF_PATH)/components/esp_http_server/src/port/esp32 $(IDF_PATH)/components/esp_http_server/src/util $(IDF_PATH)/components/esp_http_server/src/
CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_INFO

View File

@@ -73,11 +73,7 @@ void dns_server_stop(){
}
void dns_server(void *pvParameters) {
struct sockaddr_in sa, ra;
/* Set redirection DNS hijack to the access point IP */

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
/*
Copyright (c) 2017-2019 Tony Pottier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@file http_server.h
@author Tony Pottier
@brief Defines all functions necessary for the HTTP server to run.
Contains the freeRTOS task for the HTTP listener and all necessary support
function to process requests, decode URLs, serve files, etc. etc.
@note http_server task cannot run without the wifi_manager task!
@see https://idyl.io
@see https://github.com/tonyp7/esp32-wifi-manager
*/
#ifndef HTTP_SERVER_H_INCLUDED
#define HTTP_SERVER_H_INCLUDED
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include "esp_http_server.h"
#include "wifi_manager.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_wifi.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include "esp_log.h"
#include "driver/gpio.h"
#include "mdns.h"
#include "lwip/api.h"
#include "lwip/err.h"
#include "lwip/netdb.h"
#include "lwip/opt.h"
#include "lwip/memp.h"
#include "lwip/ip.h"
#include "lwip/raw.h"
#include "lwip/udp.h"
#include "lwip/priv/api_msg.h"
#include "lwip/priv/tcp_priv.h"
#include "lwip/priv/tcpip_priv.h"
#include "esp_vfs.h"
#ifdef __cplusplus
extern "C" {
#endif
#define ESP_LOGE_LOC(t,str, ...) ESP_LOGE(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define ESP_LOGI_LOC(t,str, ...) ESP_LOGI(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define ESP_LOGD_LOC(t,str, ...) ESP_LOGD(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define ESP_LOGW_LOC(t,str, ...) ESP_LOGW(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define ESP_LOGV_LOC(t,str, ...) ESP_LOGV(t, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__)
esp_err_t root_get_handler(httpd_req_t *req);
esp_err_t resource_filehandler(httpd_req_t *req);
esp_err_t resource_filehandler(httpd_req_t *req);
esp_err_t resource_filehandler(httpd_req_t *req);
esp_err_t resource_filehandler(httpd_req_t *req);
esp_err_t resource_filehandler(httpd_req_t *req);
esp_err_t resource_filehandler(httpd_req_t *req);
esp_err_t ap_get_handler(httpd_req_t *req);
esp_err_t config_get_handler(httpd_req_t *req);
esp_err_t config_post_handler(httpd_req_t *req);
esp_err_t connect_post_handler(httpd_req_t *req);
esp_err_t connect_delete_handler(httpd_req_t *req);
esp_err_t reboot_ota_post_handler(httpd_req_t *req);
esp_err_t reboot_post_handler(httpd_req_t *req);
esp_err_t recovery_post_handler(httpd_req_t *req);
#if RECOVERY_APPLICATION
esp_err_t flash_post_handler(httpd_req_t *req);
#endif
esp_err_t status_get_handler(httpd_req_t *req);
esp_err_t messages_get_handler(httpd_req_t *req);
esp_err_t ap_scan_handler(httpd_req_t *req);
esp_err_t redirect_ev_handler(httpd_req_t *req);
esp_err_t redirect_200_ev_handler(httpd_req_t *req);
esp_err_t err_handler(httpd_req_t *req, httpd_err_code_t error);
#define SCRATCH_BUFSIZE (10240)
#define FILE_PATH_MAX (ESP_VFS_PATH_MAX + 128)
typedef struct rest_server_context {
char base_path[ESP_VFS_PATH_MAX + 1];
char scratch[SCRATCH_BUFSIZE];
} rest_server_context_t;
/**
* @brief RTOS task for the HTTP server. Do not start manually.
* @see void http_server_start()
*/
void CODE_RAM_LOCATION http_server(void *pvParameters);
/* @brief helper function that processes one HTTP request at a time */
void CODE_RAM_LOCATION http_server_netconn_serve(struct netconn *conn);
/* @brief create the task for the http server */
esp_err_t CODE_RAM_LOCATION http_server_start();
/**
* @brief gets a char* pointer to the first occurence of header_name withing the complete http request request.
*
* For optimization purposes, no local copy is made. memcpy can then be used in coordination with len to extract the
* data.
*
* @param request the full HTTP raw request.
* @param header_name the header that is being searched.
* @param len the size of the header value if found.
* @return pointer to the beginning of the header value.
*/
char* CODE_RAM_LOCATION http_server_get_header(char *request, char *header_name, int *len);
void CODE_RAM_LOCATION strreplace(char *src, char *str, char *rep);
/* @brief lock the json config object */
bool http_server_lock_json_object(TickType_t xTicksToWait);
/* @brief unlock the json config object */
void http_server_unlock_json_object();
#define PROTECTED_JSON_CALL(a) if(http_server_lock_json_object( portMAX_DELAY )){ \ a; http_server_unlocklock_json_object(); } else{ ESP_LOGE(TAG, "could not get access to json mutex in wifi_scan"); }
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -4,19 +4,12 @@
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes" />
<link rel="stylesheet" href="/bootstrap.css">
<link rel="stylesheet" href="res/test/bootstrap.min.css"> <!-- TODO delete -->
<link rel="stylesheet" href="/style.css">
<script src="/jquery.js"></script>
<script src="/popper.js"></script>
<script src="/bootstrap.js"></script>
<script src="res/test/jquery.min.js"></script> <!-- TODO delete -->
<script src="res/test/popper.min.js"></script> <!-- TODO delete -->
<script src="res/test/bootstrap.min.js"></script> <!-- TODO delete -->
<script src="/code.js"></script>
<link rel="stylesheet" href="/res/bootstrap.css">
<link rel="stylesheet" href="/res/style.css">
<script src="/res/jquery.js"></script>
<script src="/res/popper.js"></script>
<script src="/res/bootstrap.js"></script>
<script src="/res/code.js"></script>
<title>esp32-wifi-manager</title>
</head>
@@ -74,7 +67,7 @@
<a class="nav-link" data-toggle="tab" href="#tab-firmware">Firmware</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#tab-gpio">GPIO</a>
<a class="nav-link" data-toggle="tab" href="#tab-syslog">Syslog</a>
</li>
<li class="nav-item">
<a class="nav-link" data-toggle="tab" href="#tab-nvs">NVS editor</a>
@@ -197,7 +190,7 @@
</div>
</div>
</div>
</div>
</div> <!-- wifi -->
<div class="tab-pane fade" id="tab-audio">
<div id="audioout">
@@ -247,72 +240,7 @@
<label class="custom-control-label" for="autoexec-cb"></label>
</div>
<br />
</div>
<div class="tab-pane fade" id="tab-gpio">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Signal</th>
<th scope="col">I2S pin</th>
<th scope="col">SPDIF pin</th>
</tr>
</thead>
<tbody id="gpioTable">
<tr>
<td>Bit clock</td>
<td>
<input type="text" class="form-control gpio" id="gpio-i2s-bc" maxlength="2" size="2">
</td>
<td>
<input type="text" class="form-control gpio" id="gpio-spdif-bc" maxlength="2" size="2">
</td>
</tr>
<tr>
<td>Word select</td>
<td>
<input type="text" class="form-control gpio" id="gpio-i2s-ws" maxlength="2" size="2">
</td>
<td>
<input type="text" class="form-control gpio" id="gpio-spdif-ws" maxlength="2" size="2">
</td>
</tr>
<tr>
<td>Data</td>
<td>
<input type="text" class="form-control gpio" id="gpio-i2s-data" maxlength="2" size="2">
</td>
<td>
<input type="text" class="form-control gpio" id="gpio-spdif-data" maxlength="2" size="2">
</td>
</tr>
</tbody>
</table>
<div class="buttons">
<input id="save-gpio" type="button" class="btn btn-success" value="Save" />
</div>
</div>
<div class="tab-pane fade" id="tab-nvs">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody id="nvsTable">
</tbody>
</table>
<div class="buttons">
<div id="boot-div">
<form id="reboot-form" action="/reboot.json" method="post" target="dummyframe">
<button id="reboot-button" type="submit" class="btn btn-primary">Reboot</button>
</form>
</div>
<input id="save-nvs" type="button" class="btn btn-success" value="Save" />
</div>
</div>
</div> <!-- audio -->
<div class="tab-pane fade" id="tab-firmware">
<div id="boot-div">
@@ -343,21 +271,17 @@
</table>
<h2>Firmware URL:</h2>
<textarea id="fwurl" maxlength="350"></textarea>
<!--
<br />OR<br />
<div class="input-group mb-3" id="upload">
<div class="custom-file">
<input type="file" class="custom-file-input" id="inputGroupFile01">
<label class="custom-file-label" for="inputGroupFile01"></label>
</div>
<div class="input-group-append">
<span class="input-group-text" id="fwUpload">Upload</span>
</div>
</div>
-->
<div class="buttons">
<input type="button" id="flash" class="btn btn-danger" value="Flash!" /><span id="flash-status"></span>
<input type="button" id="flash" class="btn btn-danger" value="Flash!" /><span id="flash-status"></span>
</div>
<p>OR</p>
<div class="form-group">
<input type="file" class="form-control-file" id="flashfilename" aria-describedby="fileHelp">
<div class="buttons">
<button type="button" class="btn btn-danger" id="fwUpload">Upload!</button>
</div>
</div>
<div id="otadiv">
<div class="progress" id="progress">
<div class="progress-bar" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width:0%">
@@ -365,11 +289,48 @@
</div>
</div>
</div>
</div>
</div> <!-- firmware -->
<div class="tab-pane fade" id="tab-syslog">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Timestamp</th>
<th scope="col">Message</th>
</tr>
</thead>
<tbody id="syslogTable">
</tbody>
</table>
<div class="buttons">
<input id="clear-syslog" type="button" class="btn btn-danger btn-sm" value="Clear" />
</div>
</div> <!-- syslog -->
<div class="tab-pane fade" id="tab-nvs">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Key</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody id="nvsTable">
</tbody>
</table>
<div class="buttons">
<div id="boot-div">
<form id="reboot-form" action="/reboot.json" method="post" target="dummyframe">
<button id="reboot-button" type="submit" class="btn btn-primary">Reboot</button>
</form>
</div>
<input id="save-nvs" type="button" class="btn btn-success" value="Save" />
</div>
</div> <!-- nvs -->
<div class="tab-pane fade" id="tab-credits">
<div class="jumbotron">
<p><strong><a href="https://github.com/sle118/squeezelite-esp32">squeezelite-esp32</a></strong>, &copy; 2019, philippe44, sle118, daduke<br />Licensed under the GPL</p>
<p><strong><a href="https://github.com/sle118/squeezelite-esp32">squeezelite-esp32</a></strong>, &copy; 2020, philippe44, sle118, daduke<br />Licensed under the GPL</p>
<p>
This app would not be possible without the following libraries:
</p>
@@ -388,7 +349,7 @@
<input type="checkbox" class="custom-control-input" id="show-nvs" checked="checked">
<label class="custom-control-label" for="show-nvs"></label>
</div>
</div>
</div> <!-- credits -->
</div>
<footer class="footer"><span id="foot-fw"></span><span id="foot-wifi"></span></footer>
<iframe width="0" height="0" border="0" name="dummyframe" id="dummyframe"></iframe>

View File

@@ -212,12 +212,6 @@ input[type='text'], input[type='password'], textarea {
padding: 4px;
}
input.gpio {
width: 2em;
color: #000;
height: 1.8em;
}
.custom-switch {
margin-left: 8px;
}
@@ -273,6 +267,18 @@ textarea#autoexec1, textarea#fwurl, div#upload {
width: 80%;
}
table tr.MESSAGING_INFO {
background: #123;
}
table tr.MESSAGING_WARNING {
background: #330;
}
table tr.MESSAGING_ERROR {
background: #300;
}
input, textarea {
border-radius: 3px;
border: 1px solid transparent;

View File

@@ -37,7 +37,6 @@ Contains the freeRTOS task and all necessary support
#include <stdbool.h>
#include "dns_server.h"
#include "http_server.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
@@ -61,6 +60,9 @@ Contains the freeRTOS task and all necessary support
#include "platform_config.h"
#include "trace.h"
#include "cmd_system.h"
#include "messaging.h"
#include "http_server_handlers.h"
#include "monitor.h"
#include "globdefs.h"
@@ -69,7 +71,7 @@ Contains the freeRTOS task and all necessary support
#endif
#define STR_OR_BLANK(p) p==NULL?"":p
#define FREE_AND_NULL(p) if(p!=NULL){ free(p); p=NULL;}
/* objects used to manipulate the main queue of events */
QueueHandle_t wifi_manager_queue;
SemaphoreHandle_t wifi_manager_json_mutex = NULL;
@@ -83,7 +85,7 @@ char *ip_info_json = NULL;
char * release_url=NULL;
cJSON * ip_info_cjson=NULL;
wifi_config_t* wifi_manager_config_sta = NULL;
static update_reason_code_t last_update_reason_code=0;
static int32_t total_connected_time=0;
static int64_t last_connected=0;
@@ -202,9 +204,6 @@ bool isGroupBitSet(uint8_t bit){
EventBits_t uxBits= xEventGroupGetBits(wifi_manager_event_group);
return (uxBits & bit);
}
void wifi_manager_refresh_ota_json(){
wifi_manager_send_message(EVENT_REFRESH_OTA, NULL);
}
void wifi_manager_scan_async(){
wifi_manager_send_message(ORDER_START_WIFI_SCAN, NULL);
@@ -357,6 +356,7 @@ esp_err_t wifi_manager_save_sta_config(){
esp_err = nvs_commit(handle);
if (esp_err != ESP_OK) {
ESP_LOGE(TAG, "Unable to commit changes. Error %s", esp_err_to_name(esp_err));
messaging_post_message(MESSAGING_ERROR,MESSAGING_CLASS_SYSTEM,"Unable to save wifi credentials. %s",esp_err_to_name(esp_err));
return esp_err;
}
nvs_close(handle);
@@ -449,8 +449,6 @@ cJSON * wifi_manager_get_basic_info(cJSON **old){
cJSON_AddItemToObject(root, "version", cJSON_CreateString(desc->version));
if(release_url !=NULL) cJSON_AddItemToObject(root, "release_url", cJSON_CreateString(release_url));
cJSON_AddNumberToObject(root,"recovery", is_recovery_running?1:0);
cJSON_AddItemToObject(root, "ota_dsc", cJSON_CreateString(ota_get_status()));
cJSON_AddNumberToObject(root,"ota_pct", ota_get_pct_complete() );
cJSON_AddItemToObject(root, "Jack", cJSON_CreateString(jack_inserted_svc() ? "1" : "0"));
cJSON_AddNumberToObject(root,"Voltage", battery_value_svc());
cJSON_AddNumberToObject(root,"disconnect_count", num_disconnect );
@@ -479,12 +477,6 @@ void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code)
wifi_config_t *config = wifi_manager_get_wifi_sta_config();
ip_info_cjson = wifi_manager_get_basic_info(&ip_info_cjson);
if(update_reason_code == UPDATE_OTA) {
update_reason_code = last_update_reason_code;
}
else {
last_update_reason_code = update_reason_code;
}
cJSON_AddNumberToObject(ip_info_cjson, "urc", update_reason_code);
if(config){
cJSON_AddItemToObject(ip_info_cjson, "ssid", cJSON_CreateString((char *)config->sta.ssid));
@@ -505,7 +497,7 @@ char * get_mac_string(uint8_t mac[6]){
char * macStr=malloc(LOCAL_MAC_SIZE);
memset(macStr, 0x00, LOCAL_MAC_SIZE);
snprintf(macStr, LOCAL_MAC_SIZE-1,MACSTR, MAC2STR(mac));
snprintf(macStr, LOCAL_MAC_SIZE,MACSTR, MAC2STR(mac));
return macStr;
}
@@ -855,20 +847,6 @@ void wifi_manager_connect_async(){
wifi_manager_send_message(ORDER_CONNECT_STA, (void*)CONNECTION_REQUEST_USER);
}
void set_status_message(message_severity_t severity, const char * message){
if(ip_info_cjson==NULL){
ip_info_cjson = wifi_manager_get_new_json(&ip_info_cjson);
}
if(ip_info_cjson==NULL){
ESP_LOGE(TAG, "Error setting status message. Unable to allocate cJSON.");
return;
}
cJSON * item=cJSON_GetObjectItem(ip_info_cjson, "message");
item = wifi_manager_get_new_json(&item);
cJSON_AddItemToObject(item, "severity", cJSON_CreateString(severity==INFO?"INFO":severity==WARNING?"WARNING":severity==ERROR?"ERROR":"" ));
cJSON_AddItemToObject(item, "text", cJSON_CreateString(message));
}
char* wifi_manager_alloc_get_ip_info_json(){
return cJSON_PrintUnformatted(ip_info_cjson);
@@ -1138,12 +1116,6 @@ void wifi_manager( void * pvParameters ){
ESP_LOGD(TAG, "Done Invoking SCAN DONE callback");
}
break;
case EVENT_REFRESH_OTA:
if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
wifi_manager_generate_ip_info_json( UPDATE_OTA );
wifi_manager_unlock_json_buffer();
}
break;
case ORDER_START_WIFI_SCAN:
ESP_LOGD(TAG, "MESSAGE: ORDER_START_WIFI_SCAN");
@@ -1153,6 +1125,7 @@ void wifi_manager( void * pvParameters ){
if(esp_wifi_scan_start(&scan_config, false)!=ESP_OK){
ESP_LOGW(TAG, "Unable to start scan; wifi is trying to connect");
// set_status_message(WARNING, "Wifi Connecting. Cannot start scan.");
messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"Wifi connecting. Cannot start scan.");
}
else {
xEventGroupSetBits(wifi_manager_event_group, WIFI_MANAGER_SCAN_BIT);
@@ -1358,6 +1331,8 @@ void wifi_manager( void * pvParameters ){
else{
/* lost connection ? */
ESP_LOGE(TAG, "WiFi Connection lost.");
messaging_post_message(MESSAGING_WARNING,MESSAGING_CLASS_SYSTEM,"WiFi Connection lost");
if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
wifi_manager_generate_ip_info_json( UPDATE_LOST_CONNECTION );
wifi_manager_unlock_json_buffer();
@@ -1439,7 +1414,7 @@ void wifi_manager( void * pvParameters ){
if(cb_ptr_arr[msg.code]) (*cb_ptr_arr[msg.code])(NULL);
break;
case UPDATE_CONNECTION_OK:
/* refresh JSON with the new ota data */
/* refresh JSON */
if(wifi_manager_lock_json_buffer( portMAX_DELAY )){
/* generate the connection info with success */
wifi_manager_generate_ip_info_json( UPDATE_CONNECTION_OK );

View File

@@ -42,6 +42,17 @@ extern "C" {
#include "squeezelite-ota.h"
#include "cJSON.h"
#ifndef RECOVERY_APPLICATION
#error "RECOVERY_APPLICATION not defined. Defaulting to squeezelite"
#endif
#if RECOVERY_APPLICATION==1
#elif RECOVERY_APPLICATION==0
#else
#error "unknown configuration"
#endif
/**
* @brief Defines the maximum size of a SSID name. 32 is IEEE standard.
@@ -187,12 +198,11 @@ typedef enum message_code_t {
EVENT_STA_DISCONNECTED = 12,
EVENT_SCAN_DONE = 13,
EVENT_STA_GOT_IP = 14,
EVENT_REFRESH_OTA = 15,
ORDER_RESTART_OTA = 16,
ORDER_RESTART_RECOVERY = 17,
ORDER_RESTART_OTA_URL = 18,
ORDER_RESTART = 19,
MESSAGE_CODE_COUNT = 20 /* important for the callback array */
ORDER_RESTART_OTA = 15,
ORDER_RESTART_RECOVERY = 16,
ORDER_RESTART_OTA_URL = 17,
ORDER_RESTART = 18,
MESSAGE_CODE_COUNT = 19 /* important for the callback array */
}message_code_t;
@@ -215,8 +225,7 @@ typedef enum update_reason_code_t {
UPDATE_CONNECTION_OK = 0,
UPDATE_FAILED_ATTEMPT = 1,
UPDATE_USER_DISCONNECT = 2,
UPDATE_LOST_CONNECTION = 3,
UPDATE_OTA=4
UPDATE_LOST_CONNECTION = 3
}update_reason_code_t;
typedef enum connection_request_made_by_code_t{

View File

@@ -0,0 +1,165 @@
/*
* Squeezelite for esp32
*
* (c) Sebastien 2019
* Philippe G. 2019, philippe_44@outlook.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "http_server_handlers.h"
#include "esp_log.h"
#include "esp_http_server.h"
#include "_esp_http_server.h"
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "config.h"
#include "messaging.h"
static const char TAG[] = "http_server";
static httpd_handle_t _server = NULL;
rest_server_context_t *rest_context = NULL;
RingbufHandle_t messaging=NULL;
void register_common_handlers(httpd_handle_t server){
httpd_uri_t res_get = { .uri = "/res/*", .method = HTTP_GET, .handler = resource_filehandler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &res_get);
}
void register_regular_handlers(httpd_handle_t server){
httpd_uri_t root_get = { .uri = "/", .method = HTTP_GET, .handler = root_get_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &root_get);
httpd_uri_t ap_get = { .uri = "/ap.json", .method = HTTP_GET, .handler = ap_get_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &ap_get);
httpd_uri_t scan_get = { .uri = "/scan.json", .method = HTTP_GET, .handler = ap_scan_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &scan_get);
httpd_uri_t config_get = { .uri = "/config.json", .method = HTTP_GET, .handler = config_get_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &config_get);
httpd_uri_t status_get = { .uri = "/status.json", .method = HTTP_GET, .handler = status_get_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &status_get);
httpd_uri_t messages_get = { .uri = "/messages.json", .method = HTTP_GET, .handler = messages_get_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &messages_get);
httpd_uri_t config_post = { .uri = "/config.json", .method = HTTP_POST, .handler = config_post_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &config_post);
httpd_uri_t connect_post = { .uri = "/connect.json", .method = HTTP_POST, .handler = connect_post_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &connect_post);
httpd_uri_t reboot_ota_post = { .uri = "/reboot_ota.json", .method = HTTP_POST, .handler = reboot_ota_post_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &reboot_ota_post);
httpd_uri_t reboot_post = { .uri = "/reboot.json", .method = HTTP_POST, .handler = reboot_post_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &reboot_post);
httpd_uri_t recovery_post = { .uri = "/recovery.json", .method = HTTP_POST, .handler = recovery_post_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &recovery_post);
httpd_uri_t connect_delete = { .uri = "/connect.json", .method = HTTP_DELETE, .handler = connect_delete_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &connect_delete);
#if RECOVERY_APPLICATION
httpd_uri_t flash_post = { .uri = "/flash.json", .method = HTTP_POST, .handler = flash_post_handler, .user_ctx = rest_context };
httpd_register_uri_handler(server, &flash_post);
#endif
// from https://github.com/tripflex/wifi-captive-portal/blob/master/src/mgos_wifi_captive_portal.c
// https://unix.stackexchange.com/questions/432190/why-isnt-androids-captive-portal-detection-triggering-a-browser-window
// Known HTTP GET requests to check for Captive Portal
///kindle-wifi/wifiredirect.html Kindle when requested with com.android.captiveportallogin
///kindle-wifi/wifistub.html Kindle before requesting with captive portal login window (maybe for detection?)
httpd_uri_t connect_redirect_1 = { .uri = "/mobile/status.php", .method = HTTP_GET, .handler = redirect_200_ev_handler, .user_ctx = rest_context };// Android 8.0 (Samsung s9+)
httpd_register_uri_handler(server, &connect_redirect_1);
httpd_uri_t connect_redirect_2 = { .uri = "/generate_204", .method = HTTP_GET, .handler = redirect_200_ev_handler, .user_ctx = rest_context };// Android
httpd_register_uri_handler(server, &connect_redirect_2);
httpd_uri_t connect_redirect_3 = { .uri = "/gen_204", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context };// Android 9.0
httpd_register_uri_handler(server, &connect_redirect_3);
// httpd_uri_t connect_redirect_4 = { .uri = "/ncsi.txt", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context };// Windows
// httpd_register_uri_handler(server, &connect_redirect_4);
httpd_uri_t connect_redirect_5 = { .uri = "/hotspot-detect.html", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context }; // iOS 8/9
httpd_register_uri_handler(server, &connect_redirect_5);
httpd_uri_t connect_redirect_6 = { .uri = "/library/test/success.html", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context };// iOS 8/9
httpd_register_uri_handler(server, &connect_redirect_6);
httpd_uri_t connect_redirect_7 = { .uri = "/hotspotdetect.html", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context }; // iOS
httpd_register_uri_handler(server, &connect_redirect_7);
httpd_uri_t connect_redirect_8 = { .uri = "/success.txt", .method = HTTP_GET, .handler = redirect_ev_handler, .user_ctx = rest_context }; // OSX
httpd_register_uri_handler(server, &connect_redirect_8);
ESP_LOGD(TAG,"Registering default error handler for 404");
httpd_register_err_handler(server, HTTPD_404_NOT_FOUND,&err_handler);
}
esp_err_t http_server_start()
{
ESP_LOGI(TAG, "Initializing HTTP Server");
messaging = messaging_register_subscriber(10, "http_server");
rest_context = calloc(1, sizeof(rest_server_context_t));
if(rest_context==NULL){
ESP_LOGE(TAG,"No memory for http context");
return ESP_FAIL;
}
strlcpy(rest_context->base_path, "/res/", sizeof(rest_context->base_path));
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = 25;
config.max_open_sockets = 5;
config.uri_match_fn = httpd_uri_match_wildcard;
//todo: use the endpoint below to configure session token?
// config.open_fn
ESP_LOGI(TAG, "Starting HTTP Server");
esp_err_t err= __httpd_start(&_server, &config);
if(err != ESP_OK){
ESP_LOGE_LOC(TAG,"Start server failed");
}
else {
register_common_handlers(_server);
register_regular_handlers(_server);
}
return err;
}
/* Function to free context */
void adder_free_func(void *ctx)
{
ESP_LOGI(TAG, "/adder Free Context function called");
free(ctx);
}
void stop_webserver(httpd_handle_t server)
{
// Stop the httpd server
httpd_stop(server);
}