initial work on a wifi/http configuration module
10
.cproject
@@ -14,13 +14,13 @@
|
|||||||
</extensions>
|
</extensions>
|
||||||
</storageModule>
|
</storageModule>
|
||||||
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
<storageModule moduleId="cdtBuildSystem" version="4.0.0">
|
||||||
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.1476804786" name="Default" optionalBuildProperties="org.eclipse.cdt.docker.launcher.containerbuild.property.volumes=,org.eclipse.cdt.docker.launcher.containerbuild.property.selectedvolumes=" parent="org.eclipse.cdt.build.core.emptycfg">
|
<configuration artifactName="${ProjName}" buildProperties="" description="" id="cdt.managedbuild.toolchain.gnu.cross.base.1476804786" name="Default" optionalBuildProperties="org.eclipse.cdt.docker.launcher.containerbuild.property.selectedvolumes=,org.eclipse.cdt.docker.launcher.containerbuild.property.volumes=" parent="org.eclipse.cdt.build.core.emptycfg">
|
||||||
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.1476804786.1800826258" name="/" resourcePath="">
|
<folderInfo id="cdt.managedbuild.toolchain.gnu.cross.base.1476804786.1800826258" name="/" resourcePath="">
|
||||||
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.811827721" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
|
<toolChain id="cdt.managedbuild.toolchain.gnu.cross.base.811827721" name="Cross GCC" superClass="cdt.managedbuild.toolchain.gnu.cross.base">
|
||||||
<option id="cdt.managedbuild.option.gnu.cross.prefix.1666584715" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
|
<option id="cdt.managedbuild.option.gnu.cross.prefix.1666584715" name="Prefix" superClass="cdt.managedbuild.option.gnu.cross.prefix"/>
|
||||||
<option id="cdt.managedbuild.option.gnu.cross.path.144124148" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
|
<option id="cdt.managedbuild.option.gnu.cross.path.144124148" name="Path" superClass="cdt.managedbuild.option.gnu.cross.path"/>
|
||||||
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1562292378" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
|
<targetPlatform archList="all" binaryParser="org.eclipse.cdt.core.ELF" id="cdt.managedbuild.targetPlatform.gnu.cross.1562292378" isAbstract="false" osList="all" superClass="cdt.managedbuild.targetPlatform.gnu.cross"/>
|
||||||
<builder id="cdt.managedbuild.builder.gnu.cross.1011968237" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" superClass="cdt.managedbuild.builder.gnu.cross"/>
|
<builder id="cdt.managedbuild.builder.gnu.cross.1011968237" keepEnvironmentInBuildfile="false" managedBuildOn="false" name="Gnu Make Builder" parallelBuildOn="true" parallelizationNumber="optimal" superClass="cdt.managedbuild.builder.gnu.cross"/>
|
||||||
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.1502936757" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
|
<tool id="cdt.managedbuild.tool.gnu.cross.c.compiler.1502936757" name="Cross GCC Compiler" superClass="cdt.managedbuild.tool.gnu.cross.c.compiler">
|
||||||
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1614739014" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
<inputType id="cdt.managedbuild.tool.gnu.c.compiler.input.1614739014" superClass="cdt.managedbuild.tool.gnu.c.compiler.input"/>
|
||||||
</tool>
|
</tool>
|
||||||
@@ -64,7 +64,11 @@
|
|||||||
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
<autodiscovery enabled="true" problemReportingEnabled="true" selectedProfileId=""/>
|
||||||
</scannerConfigBuildInfo>
|
</scannerConfigBuildInfo>
|
||||||
</storageModule>
|
</storageModule>
|
||||||
<storageModule moduleId="refreshScope"/>
|
<storageModule moduleId="refreshScope" versionNumber="2">
|
||||||
|
<configuration configurationName="Default">
|
||||||
|
<resource resourceType="PROJECT" workspacePath="/squeezelite-esp32"/>
|
||||||
|
</configuration>
|
||||||
|
</storageModule>
|
||||||
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
|
<storageModule moduleId="org.eclipse.cdt.make.core.buildtargets">
|
||||||
<buildTargets>
|
<buildTargets>
|
||||||
<target name="all" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
|
<target name="all" path="" targetID="org.eclipse.cdt.build.MakeTargetBuilder">
|
||||||
|
|||||||
1
.gitignore
vendored
@@ -66,4 +66,3 @@ libs/
|
|||||||
/cdump.cmd
|
/cdump.cmd
|
||||||
/_*
|
/_*
|
||||||
sdkconfig
|
sdkconfig
|
||||||
*_history/
|
|
||||||
|
|||||||
9
.gitmodules
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[submodule "components/libwebsockets"]
|
||||||
|
path = components/libwebsockets
|
||||||
|
url = https://github.com/warmcat/libwebsockets.git
|
||||||
|
[submodule "components/mbedtls"]
|
||||||
|
path = components/mbedtls
|
||||||
|
url = https://github.com/lws-team/mbedtls.git
|
||||||
|
[submodule "components/lws-esp32"]
|
||||||
|
path = components/lws-esp32
|
||||||
|
url = https://github.com/huming2207/lws-esp32.git
|
||||||
2
Makefile
@@ -4,6 +4,4 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
PROJECT_NAME := squeezelite
|
PROJECT_NAME := squeezelite
|
||||||
|
|
||||||
include $(IDF_PATH)/make/project.mk
|
include $(IDF_PATH)/make/project.mk
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
TODO
|
|
||||||
- when IP changes, best is to reboot at this point
|
|
||||||
|
|
||||||
MOST IMPORTANT: create the right default config file
|
MOST IMPORTANT: create the right default config file
|
||||||
- make defconfig
|
- make defconfig
|
||||||
Then adapt the config file to your wifi/BT/I2C device (can alos be done on the command line)
|
Then adapt the config file to your wifi/BT/I2C device (can alos be done on the command line)
|
||||||
@@ -23,11 +20,6 @@ nvs_set autoexec2 str -v "squeezelite -o I2S -b 500:2000 -d all=info -m ESP32"
|
|||||||
|
|
||||||
nvs_set autoexec u8 -v 1
|
nvs_set autoexec u8 -v 1
|
||||||
|
|
||||||
4/ set bluetooth & airplaysink name (if not set in menuconfig)
|
|
||||||
|
|
||||||
nvs_set bt_sink_name str -v "<name>"
|
|
||||||
nvs_set airplay_sink_name str -v "<name>"
|
|
||||||
|
|
||||||
The "join" and "squeezelite" commands can also be typed at the prompt to start manually. Use "help" to see the list.
|
The "join" and "squeezelite" commands can also be typed at the prompt to start manually. Use "help" to see the list.
|
||||||
|
|
||||||
The squeezelite options are very similar to the regular Linux ones. Differences are :
|
The squeezelite options are very similar to the regular Linux ones. Differences are :
|
||||||
@@ -43,7 +35,6 @@ To add options that require quotes ("), escape them with \". For example, so use
|
|||||||
nvs_set autoexec2 str -v "squeezelite -o \"BT -n 'MySpeaker'\" -b 500:2000 -R -u m -Z 192000 -r \"44100-44100\""
|
nvs_set autoexec2 str -v "squeezelite -o \"BT -n 'MySpeaker'\" -b 500:2000 -R -u m -Z 192000 -r \"44100-44100\""
|
||||||
|
|
||||||
# Additional misc notes to do you build
|
# Additional misc notes to do you build
|
||||||
- as of this writing, ESP-IDF has a bug int he way the PLL values are calculated for i2s, so you *must* use the i2s.c file in the patch directory
|
|
||||||
- for all libraries, add -mlongcalls.
|
- for all libraries, add -mlongcalls.
|
||||||
- audio libraries are complicated to rebuild, open an issue if you really want to
|
- audio libraries are complicated to rebuild, open an issue if you really want to
|
||||||
- libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work
|
- libmad, libflac (no esp's version), libvorbis (tremor - not esp's version), alac work
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ static const type_str_pair_t type_str_pair[] = {
|
|||||||
|
|
||||||
static const size_t TYPE_STR_PAIR_SIZE = sizeof(type_str_pair) / sizeof(type_str_pair[0]);
|
static const size_t TYPE_STR_PAIR_SIZE = sizeof(type_str_pair) / sizeof(type_str_pair[0]);
|
||||||
static const char *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob";
|
static const char *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob";
|
||||||
char current_namespace[16] = "storage";
|
char current_namespace[16] = "espwifimgr";
|
||||||
static const char * TAG = "platform_esp32";
|
static const char * TAG = "platform_esp32";
|
||||||
|
|
||||||
static struct {
|
static struct {
|
||||||
|
|||||||
@@ -28,6 +28,12 @@
|
|||||||
#ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
|
#ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
|
||||||
#define WITH_TASKS_INFO 1
|
#define WITH_TASKS_INFO 1
|
||||||
#endif
|
#endif
|
||||||
|
#define LWS_MAGIC_REBOOT_TYPE_ADS 0x50001ffc
|
||||||
|
#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY 0xb00bcafe
|
||||||
|
#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY 0xfaceb00b
|
||||||
|
#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON 0xf0cedfac
|
||||||
|
#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY_ERASE_OTA 0xfac0eeee
|
||||||
|
|
||||||
|
|
||||||
static const char * TAG = "platform_esp32";
|
static const char * TAG = "platform_esp32";
|
||||||
|
|
||||||
@@ -37,6 +43,7 @@ static void register_version();
|
|||||||
static void register_restart();
|
static void register_restart();
|
||||||
static void register_deep_sleep();
|
static void register_deep_sleep();
|
||||||
static void register_light_sleep();
|
static void register_light_sleep();
|
||||||
|
static void register_factory_boot();
|
||||||
#if WITH_TASKS_INFO
|
#if WITH_TASKS_INFO
|
||||||
static void register_tasks();
|
static void register_tasks();
|
||||||
#endif
|
#endif
|
||||||
@@ -49,6 +56,7 @@ void register_system()
|
|||||||
register_restart();
|
register_restart();
|
||||||
register_deep_sleep();
|
register_deep_sleep();
|
||||||
register_light_sleep();
|
register_light_sleep();
|
||||||
|
register_factory_boot();
|
||||||
#if WITH_TASKS_INFO
|
#if WITH_TASKS_INFO
|
||||||
register_tasks();
|
register_tasks();
|
||||||
#endif
|
#endif
|
||||||
@@ -91,7 +99,20 @@ static int restart(int argc, char **argv)
|
|||||||
ESP_LOGI(TAG, "Restarting");
|
ESP_LOGI(TAG, "Restarting");
|
||||||
esp_restart();
|
esp_restart();
|
||||||
}
|
}
|
||||||
|
void guided_factory()
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "Rebooting to factory.");
|
||||||
|
uint32_t *p_force_factory_magic = (uint32_t *)LWS_MAGIC_REBOOT_TYPE_ADS;
|
||||||
|
*p_force_factory_magic = LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY;
|
||||||
|
|
||||||
|
esp_restart();
|
||||||
|
|
||||||
|
}
|
||||||
|
static int restart_factory(int argc, char **argv)
|
||||||
|
{
|
||||||
|
guided_factory();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
static void register_restart()
|
static void register_restart()
|
||||||
{
|
{
|
||||||
const esp_console_cmd_t cmd = {
|
const esp_console_cmd_t cmd = {
|
||||||
@@ -103,6 +124,16 @@ static void register_restart()
|
|||||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void register_factory_boot()
|
||||||
|
{
|
||||||
|
const esp_console_cmd_t cmd = {
|
||||||
|
.command = "factory",
|
||||||
|
.help = "Resets and boot to factory (if available)",
|
||||||
|
.hint = NULL,
|
||||||
|
.func = &restart_factory,
|
||||||
|
};
|
||||||
|
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||||
|
}
|
||||||
/** 'free' command prints available heap memory */
|
/** 'free' command prints available heap memory */
|
||||||
|
|
||||||
static int free_mem(int argc, char **argv)
|
static int free_mem(int argc, char **argv)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ extern "C" {
|
|||||||
|
|
||||||
// Register system functions
|
// Register system functions
|
||||||
void register_system();
|
void register_system();
|
||||||
|
void guided_factory();
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
#include "esp_gap_bt_api.h"
|
#include "esp_gap_bt_api.h"
|
||||||
#include "esp_a2dp_api.h"
|
#include "esp_a2dp_api.h"
|
||||||
#include "esp_avrc_api.h"
|
#include "esp_avrc_api.h"
|
||||||
#include "nvs.h"
|
|
||||||
|
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
#include "freertos/task.h"
|
#include "freertos/task.h"
|
||||||
@@ -40,11 +39,9 @@
|
|||||||
#define BT_RC_CT_TAG "RCCT"
|
#define BT_RC_CT_TAG "RCCT"
|
||||||
|
|
||||||
#ifndef CONFIG_BT_SINK_NAME
|
#ifndef CONFIG_BT_SINK_NAME
|
||||||
#define CONFIG_BT_SINK_NAME "default"
|
#define CONFIG_BT_SINK_NAME "unavailable"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern char current_namespace[];
|
|
||||||
|
|
||||||
/* event for handler "bt_av_hdl_stack_up */
|
/* event for handler "bt_av_hdl_stack_up */
|
||||||
enum {
|
enum {
|
||||||
BT_APP_EVT_STACK_UP = 0,
|
BT_APP_EVT_STACK_UP = 0,
|
||||||
@@ -355,7 +352,7 @@ static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb)
|
void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len))
|
||||||
{
|
{
|
||||||
esp_err_t err;
|
esp_err_t err;
|
||||||
|
|
||||||
@@ -412,21 +409,6 @@ void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void bt_sink_deinit(void)
|
|
||||||
{
|
|
||||||
/* this still does not work, can't figure out how to stop properly this BT stack */
|
|
||||||
bt_app_task_shut_down();
|
|
||||||
ESP_LOGI(BT_AV_TAG, "bt_app_task shutdown successfully");
|
|
||||||
if (esp_bluedroid_disable() != ESP_OK) return;
|
|
||||||
ESP_LOGI(BT_AV_TAG, "esp_bluedroid_disable called successfully");
|
|
||||||
if (esp_bluedroid_deinit() != ESP_OK) return;
|
|
||||||
ESP_LOGI(BT_AV_TAG, "esp_bluedroid_deinit called successfully");
|
|
||||||
if (esp_bt_controller_disable() != ESP_OK) return;
|
|
||||||
ESP_LOGI(BT_AV_TAG, "esp_bt_controller_disable called successfully");
|
|
||||||
if (esp_bt_controller_deinit() != ESP_OK) return;
|
|
||||||
ESP_LOGI(BT_AV_TAG, "bt stopped successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||||
{
|
{
|
||||||
switch (event) {
|
switch (event) {
|
||||||
@@ -467,15 +449,7 @@ static void bt_av_hdl_stack_evt(uint16_t event, void *p_param)
|
|||||||
switch (event) {
|
switch (event) {
|
||||||
case BT_APP_EVT_STACK_UP: {
|
case BT_APP_EVT_STACK_UP: {
|
||||||
/* set up device name */
|
/* set up device name */
|
||||||
nvs_handle nvs;
|
char *dev_name = CONFIG_BT_SINK_NAME;
|
||||||
char dev_name[32] = CONFIG_BT_SINK_NAME;
|
|
||||||
|
|
||||||
if (nvs_open(current_namespace, NVS_READONLY, &nvs) == ESP_OK) {
|
|
||||||
size_t len = 31;
|
|
||||||
nvs_get_str(nvs, "bt_sink_name", dev_name, &len);
|
|
||||||
nvs_close(nvs);
|
|
||||||
}
|
|
||||||
|
|
||||||
esp_bt_dev_set_device_name(dev_name);
|
esp_bt_dev_set_device_name(dev_name);
|
||||||
|
|
||||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||||
|
|||||||
@@ -14,22 +14,9 @@
|
|||||||
typedef enum { BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE,
|
typedef enum { BT_SINK_CONNECTED, BT_SINK_DISCONNECTED, BT_SINK_PLAY, BT_SINK_STOP, BT_SINK_PAUSE,
|
||||||
BT_SINK_RATE, BT_SINK_VOLUME, } bt_sink_cmd_t;
|
BT_SINK_RATE, BT_SINK_VOLUME, } bt_sink_cmd_t;
|
||||||
|
|
||||||
typedef void (*bt_cmd_cb_t)(bt_sink_cmd_t cmd, ...);
|
|
||||||
typedef void (*bt_data_cb_t)(const uint8_t *data, uint32_t len);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief init sink mode (need to be provided)
|
* @brief init sink mode (need to be provided)
|
||||||
*/
|
*/
|
||||||
void bt_sink_init(bt_cmd_cb_t cmd_cb, bt_data_cb_t data_cb);
|
void bt_sink_init(void (*cmd_cb)(bt_sink_cmd_t cmd, ...), void (*data_cb)(const uint8_t *data, uint32_t len));
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief deinit sink mode (need to be provided)
|
|
||||||
*/
|
|
||||||
void bt_sink_deinit(void);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief local command mode (stop, play, volume ...)
|
|
||||||
*/
|
|
||||||
void bt_sink_cmd(bt_sink_cmd_t event, ...);
|
|
||||||
|
|
||||||
#endif /* __BT_APP_SINK_H__*/
|
#endif /* __BT_APP_SINK_H__*/
|
||||||
|
|||||||
@@ -8,5 +8,4 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
CFLAGS += -I$(COMPONENT_PATH)/../tools
|
CFLAGS += -I$(COMPONENT_PATH)/../tools
|
||||||
|
|
||||||
#CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG
|
#CFLAGS += -DLOG_LOCAL_LEVEL=ESP_LOG_DEBUG
|
||||||
|
|||||||
@@ -109,7 +109,6 @@ bool led_unconfig(int idx) {
|
|||||||
if (idx >= MAX_LED) return false;
|
if (idx >= MAX_LED) return false;
|
||||||
|
|
||||||
if (leds[idx].timer) xTimerDelete(leds[idx].timer, BLOCKTIME);
|
if (leds[idx].timer) xTimerDelete(leds[idx].timer, BLOCKTIME);
|
||||||
leds[idx].timer = NULL;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef LED_H
|
#ifndef LED_H
|
||||||
|
#define LED_H
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
enum { LED_GREEN = 0, LED_RED };
|
enum { LED_GREEN = 0, LED_RED };
|
||||||
|
|||||||
@@ -64,11 +64,6 @@ void buf_flush(struct buffer *buf) {
|
|||||||
mutex_unlock(buf->mutex);
|
mutex_unlock(buf->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _buf_flush(struct buffer *buf) {
|
|
||||||
buf->readp = buf->buf;
|
|
||||||
buf->writep = buf->buf;
|
|
||||||
}
|
|
||||||
|
|
||||||
// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
|
// adjust buffer to multiple of mod bytes so reading in multiple always wraps on frame boundary
|
||||||
void buf_adjust(struct buffer *buf, size_t mod) {
|
void buf_adjust(struct buffer *buf, size_t mod) {
|
||||||
size_t size;
|
size_t size;
|
||||||
@@ -83,7 +78,6 @@ void buf_adjust(struct buffer *buf, size_t mod) {
|
|||||||
|
|
||||||
// called with mutex locked to resize, does not retain contents, reverts to original size if fails
|
// called with mutex locked to resize, does not retain contents, reverts to original size if fails
|
||||||
void _buf_resize(struct buffer *buf, size_t size) {
|
void _buf_resize(struct buffer *buf, size_t size) {
|
||||||
if (size == buf->size) return;
|
|
||||||
free(buf->buf);
|
free(buf->buf);
|
||||||
buf->buf = malloc(size);
|
buf->buf = malloc(size);
|
||||||
if (!buf->buf) {
|
if (!buf->buf) {
|
||||||
|
|||||||
@@ -13,8 +13,7 @@ CFLAGS += -O3 -DLINKALL -DLOOPBACK -DNO_FAAD -DRESAMPLE16 -DEMBEDDED -DTREMOR_ON
|
|||||||
-I$(COMPONENT_PATH)/../tools \
|
-I$(COMPONENT_PATH)/../tools \
|
||||||
-I$(COMPONENT_PATH)/../codecs/inc/opus \
|
-I$(COMPONENT_PATH)/../codecs/inc/opus \
|
||||||
-I$(COMPONENT_PATH)/../codecs/inc/opusfile \
|
-I$(COMPONENT_PATH)/../codecs/inc/opusfile \
|
||||||
-I$(COMPONENT_PATH)/../driver_bt \
|
-I$(COMPONENT_PATH)/../driver_bt
|
||||||
-I$(COMPONENT_PATH)/../raop
|
|
||||||
|
|
||||||
# -I$(COMPONENT_PATH)/../codecs/inc/faad2
|
# -I$(COMPONENT_PATH)/../codecs/inc/faad2
|
||||||
|
|
||||||
|
|||||||
@@ -117,10 +117,6 @@ static void *decode_thread() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if EMBEDDED
|
|
||||||
deregister_external();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,7 +200,7 @@ void decode_init(log_level level, const char *include_codecs, const char *exclud
|
|||||||
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg());
|
sort_codecs((include_codecs ? order_codecs - include_codecs : i), register_mpg());
|
||||||
|
|
||||||
#if EMBEDDED
|
#if EMBEDDED
|
||||||
register_external();
|
register_other();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs);
|
LOG_DEBUG("include codecs: %s exclude codecs: %s", include_codecs ? include_codecs : "", exclude_codecs);
|
||||||
|
|||||||
141
components/squeezelite/decode_bt.c
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* 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 "squeezelite.h"
|
||||||
|
#include "bt_app_sink.h"
|
||||||
|
|
||||||
|
#define LOCK_O mutex_lock(outputbuf->mutex)
|
||||||
|
#define UNLOCK_O mutex_unlock(outputbuf->mutex)
|
||||||
|
#define LOCK_D mutex_lock(decode.mutex);
|
||||||
|
#define UNLOCK_D mutex_unlock(decode.mutex);
|
||||||
|
|
||||||
|
extern struct outputstate output;
|
||||||
|
extern struct decodestate decode;
|
||||||
|
extern struct buffer *outputbuf;
|
||||||
|
// this is the only system-wide loglevel variable
|
||||||
|
extern log_level loglevel;
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* BT sink data handler
|
||||||
|
*/
|
||||||
|
static void bt_sink_data_handler(const uint8_t *data, uint32_t len)
|
||||||
|
{
|
||||||
|
size_t bytes;
|
||||||
|
|
||||||
|
// would be better to lock decoder, but really, it does not matter
|
||||||
|
if (decode.state != DECODE_STOPPED) {
|
||||||
|
LOG_WARN("Cannot use BT sink while LMS is controlling player");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// there will always be room at some point
|
||||||
|
while (len) {
|
||||||
|
LOCK_O;
|
||||||
|
|
||||||
|
bytes = min(len, _buf_cont_write(outputbuf));
|
||||||
|
#if BYTES_PER_FRAME == 4
|
||||||
|
memcpy(outputbuf->writep, data, bytes);
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
s16_t *iptr = (s16_t*) data;
|
||||||
|
ISAMPLE_T *optr = (ISAMPLE_T*) outputbuf->writep;
|
||||||
|
size_t n = bytes / BYTES_PER_FRAME * 2;
|
||||||
|
while (n--) *optr++ = *iptr++ << 16;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
_buf_inc_writep(outputbuf, bytes);
|
||||||
|
len -= bytes;
|
||||||
|
data += bytes;
|
||||||
|
|
||||||
|
UNLOCK_O;
|
||||||
|
|
||||||
|
// allow i2s to empty the buffer if needed
|
||||||
|
if (len) usleep(50000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* BT sink command handler
|
||||||
|
*/
|
||||||
|
static void bt_sink_cmd_handler(bt_sink_cmd_t cmd, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
|
||||||
|
LOCK_D;
|
||||||
|
if (decode.state != DECODE_STOPPED) {
|
||||||
|
LOG_WARN("Cannot use BT sink while LMS is controlling player");
|
||||||
|
UNLOCK_D;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
va_start(args, cmd);
|
||||||
|
|
||||||
|
if (cmd != BT_SINK_VOLUME) LOCK_O;
|
||||||
|
|
||||||
|
switch(cmd) {
|
||||||
|
case BT_SINK_CONNECTED:
|
||||||
|
output.state = OUTPUT_STOPPED;
|
||||||
|
LOG_INFO("BT sink started");
|
||||||
|
break;
|
||||||
|
case BT_SINK_DISCONNECTED:
|
||||||
|
output.state = OUTPUT_OFF;
|
||||||
|
LOG_INFO("BT sink stopped");
|
||||||
|
break;
|
||||||
|
case BT_SINK_PLAY:
|
||||||
|
output.state = OUTPUT_EXTERNAL;
|
||||||
|
LOG_INFO("BT sink playing");
|
||||||
|
break;
|
||||||
|
case BT_SINK_PAUSE:
|
||||||
|
case BT_SINK_STOP:
|
||||||
|
output.state = OUTPUT_STOPPED;
|
||||||
|
LOG_INFO("BT sink stopped");
|
||||||
|
break;
|
||||||
|
case BT_SINK_RATE:
|
||||||
|
output.current_sample_rate = va_arg(args, u32_t);
|
||||||
|
LOG_INFO("Setting BT sample rate %u", output.current_sample_rate);
|
||||||
|
break;
|
||||||
|
case BT_SINK_VOLUME: {
|
||||||
|
u16_t volume = (u16_t) va_arg(args, u32_t);
|
||||||
|
volume *= 65536 / 128;
|
||||||
|
set_volume(volume, volume);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd != BT_SINK_VOLUME) UNLOCK_O;
|
||||||
|
UNLOCK_D;
|
||||||
|
|
||||||
|
va_end(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/****************************************************************************************
|
||||||
|
* We provide the generic codec register option
|
||||||
|
*/
|
||||||
|
void register_other(void) {
|
||||||
|
#ifdef CONFIG_BT_SINK
|
||||||
|
if (!strcasestr(output.device, "BT ")) {
|
||||||
|
bt_sink_init(bt_sink_cmd_handler, bt_sink_data_handler);
|
||||||
|
LOG_INFO("Initializing BT sink");
|
||||||
|
} else {
|
||||||
|
LOG_WARN("Cannot be a BT sink and source");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -19,10 +19,10 @@
|
|||||||
#define PTHREAD_STACK_MIN 256
|
#define PTHREAD_STACK_MIN 256
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define STREAM_THREAD_STACK_SIZE 6 * 1024
|
#define STREAM_THREAD_STACK_SIZE 8 * 1024
|
||||||
#define DECODE_THREAD_STACK_SIZE 16 * 1024
|
#define DECODE_THREAD_STACK_SIZE 20 * 1024
|
||||||
#define OUTPUT_THREAD_STACK_SIZE 6 * 1024
|
#define OUTPUT_THREAD_STACK_SIZE 8 * 1024
|
||||||
#define IR_THREAD_STACK_SIZE 6 * 1024
|
#define IR_THREAD_STACK_SIZE 8 * 1024
|
||||||
|
|
||||||
//#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=0,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
|
//#define BASE_CAP "Model=squeezelite,AccuratePlayPoints=0,HasDigitalOut=1,HasPolarityInversion=1,Firmware=" VERSION
|
||||||
|
|
||||||
@@ -37,12 +37,8 @@ typedef unsigned long long u64_t;
|
|||||||
#define mutex_create_p(m) mutex_create(m)
|
#define mutex_create_p(m) mutex_create(m)
|
||||||
|
|
||||||
uint32_t _gettime_ms_(void);
|
uint32_t _gettime_ms_(void);
|
||||||
|
|
||||||
int pthread_create_name(pthread_t *thread, _CONST pthread_attr_t *attr,
|
int pthread_create_name(pthread_t *thread, _CONST pthread_attr_t *attr,
|
||||||
void *(*start_routine)( void * ), void *arg, char *name);
|
void *(*start_routine)( void * ), void *arg, char *name);
|
||||||
|
void register_other(void);
|
||||||
// these are here as they can be #define to nothing
|
|
||||||
void register_external(void);
|
|
||||||
void deregister_external(void);
|
|
||||||
|
|
||||||
#endif // EMBEDDED_H
|
#endif // EMBEDDED_H
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ static decode_state opus_decompress(void) {
|
|||||||
info = OP(u, head, u->of, -1);
|
info = OP(u, head, u->of, -1);
|
||||||
|
|
||||||
LOCK_O;
|
LOCK_O;
|
||||||
output.next_sample_rate = decode_newstream(48000, output.supported_rates);
|
output.next_sample_rate = 48000;
|
||||||
IF_DSD( output.next_fmt = PCM; )
|
IF_DSD( output.next_fmt = PCM; )
|
||||||
output.track_start = outputbuf->writep;
|
output.track_start = outputbuf->writep;
|
||||||
if (output.fade_mode) _checkfade(true);
|
if (output.fade_mode) _checkfade(true);
|
||||||
|
|||||||
@@ -347,7 +347,6 @@ void output_init_common(log_level level, const char *device, unsigned output_buf
|
|||||||
loglevel = level;
|
loglevel = level;
|
||||||
|
|
||||||
output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
|
output_buf_size = output_buf_size - (output_buf_size % BYTES_PER_FRAME);
|
||||||
output.init_size = output_buf_size;
|
|
||||||
LOG_DEBUG("outputbuf size: %u", output_buf_size);
|
LOG_DEBUG("outputbuf size: %u", output_buf_size);
|
||||||
|
|
||||||
buf_init(outputbuf, output_buf_size);
|
buf_init(outputbuf, output_buf_size);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "driver/gpio.h"
|
|
||||||
#include "squeezelite.h"
|
#include "squeezelite.h"
|
||||||
#include "perf_trace.h"
|
#include "perf_trace.h"
|
||||||
|
|
||||||
@@ -39,12 +38,10 @@ extern u8_t *silencebuf;
|
|||||||
|
|
||||||
extern void hal_bluetooth_init(const char * options);
|
extern void hal_bluetooth_init(const char * options);
|
||||||
extern void hal_bluetooth_stop(void);
|
extern void hal_bluetooth_stop(void);
|
||||||
extern u8_t config_spdif_gpio;
|
|
||||||
|
|
||||||
static log_level loglevel;
|
static log_level loglevel;
|
||||||
static bool running = false;
|
static bool running = false;
|
||||||
static uint8_t *btout;
|
uint8_t * btout;
|
||||||
static frames_t oframes;
|
|
||||||
|
|
||||||
static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
|
static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t gainR,
|
||||||
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
|
s32_t cross_gain_in, s32_t cross_gain_out, ISAMPLE_T **cross_ptr);
|
||||||
@@ -68,11 +65,6 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
|
|||||||
DECLARE_ALL_MIN_MAX;
|
DECLARE_ALL_MIN_MAX;
|
||||||
|
|
||||||
void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
|
void output_init_bt(log_level level, char *device, unsigned output_buf_size, char *params, unsigned rates[], unsigned rate_delay, unsigned idle) {
|
||||||
#ifdef CONFIG_SQUEEZEAMP
|
|
||||||
gpio_pad_select_gpio(config_spdif_gpio);
|
|
||||||
gpio_set_direction(config_spdif_gpio, GPIO_MODE_OUTPUT);
|
|
||||||
gpio_set_level(config_spdif_gpio, 0);
|
|
||||||
#endif
|
|
||||||
loglevel = level;
|
loglevel = level;
|
||||||
running = true;
|
running = true;
|
||||||
output.write_cb = &_write_frames;
|
output.write_cb = &_write_frames;
|
||||||
@@ -102,12 +94,12 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if BYTES_PER_FRAME == 4
|
#if BYTES_PER_FRAME == 4
|
||||||
memcpy(btout + oframes * BYTES_PER_FRAME, outputbuf->readp, out_frames * BYTES_PER_FRAME);
|
memcpy(btout, outputbuf->readp, out_frames * BYTES_PER_FRAME);
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
frames_t count = out_frames;
|
frames_t count = out_frames;
|
||||||
s32_t *_iptr = (s32_t*) outputbuf->readp;
|
s32_t *_iptr = (s32_t*) outputbuf->readp;
|
||||||
s16_t *_optr = (s16_t*) (btout + oframes * BYTES_PER_FRAME);
|
s16_t *_optr = (s16_t*) bt_optr;
|
||||||
while (count--) {
|
while (count--) {
|
||||||
*_optr++ = *_iptr++ >> 16;
|
*_optr++ = *_iptr++ >> 16;
|
||||||
*_optr++ = *_iptr++ >> 16;
|
*_optr++ = *_iptr++ >> 16;
|
||||||
@@ -118,7 +110,7 @@ static int _write_frames(frames_t out_frames, bool silence, s32_t gainL, s32_t g
|
|||||||
} else {
|
} else {
|
||||||
|
|
||||||
u8_t *buf = silencebuf;
|
u8_t *buf = silencebuf;
|
||||||
memcpy(btout + oframes * BYTES_PER_FRAME, buf, out_frames * BYTES_PER_FRAME);
|
memcpy(btout, buf, out_frames * BYTES_PER_FRAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (int)out_frames;
|
return (int)out_frames;
|
||||||
@@ -132,7 +124,6 @@ int32_t output_bt_data(uint8_t *data, int32_t len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
btout = data;
|
btout = data;
|
||||||
oframes = 0;
|
|
||||||
|
|
||||||
// This is how the BTC layer calculates the number of bytes to
|
// This is how the BTC layer calculates the number of bytes to
|
||||||
// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes
|
// for us to send. (BTC_SBC_DEC_PCM_DATA_LEN * sizeof(OI_INT16) - availPcmBytes
|
||||||
@@ -152,7 +143,6 @@ int32_t output_bt_data(uint8_t *data, int32_t len) {
|
|||||||
if (wanted_len > 0) {
|
if (wanted_len > 0) {
|
||||||
SET_MIN_MAX(wanted_len, under);
|
SET_MIN_MAX(wanted_len, under);
|
||||||
}
|
}
|
||||||
output.frames_in_process = len-wanted_len;
|
|
||||||
|
|
||||||
UNLOCK;
|
UNLOCK;
|
||||||
SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);
|
SET_MIN_MAX(TIME_MEASUREMENT_GET(start_timer),lock_out_time);
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ sure that using rate_delay would fix that
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "squeezelite.h"
|
#include "squeezelite.h"
|
||||||
#include "esp_pthread.h"
|
|
||||||
#include "driver/i2s.h"
|
#include "driver/i2s.h"
|
||||||
#include "driver/i2c.h"
|
#include "driver/i2c.h"
|
||||||
#include "driver/gpio.h"
|
#include "driver/gpio.h"
|
||||||
@@ -85,9 +84,9 @@ sure that using rate_delay would fix that
|
|||||||
|
|
||||||
typedef enum { DAC_ON = 0, DAC_OFF, DAC_POWERDOWN, DAC_VOLUME } dac_cmd_e;
|
typedef enum { DAC_ON = 0, DAC_OFF, DAC_POWERDOWN, DAC_VOLUME } dac_cmd_e;
|
||||||
|
|
||||||
// must have an integer ratio with FRAME_BLOCK (see spdif comment)
|
// must have an integer ratio with FRAME_BLOCK
|
||||||
#define DMA_BUF_LEN 512
|
#define DMA_BUF_LEN 512
|
||||||
#define DMA_BUF_COUNT 12
|
#define DMA_BUF_COUNT 16
|
||||||
|
|
||||||
#define DECLARE_ALL_MIN_MAX \
|
#define DECLARE_ALL_MIN_MAX \
|
||||||
DECLARE_MIN_MAX(o); \
|
DECLARE_MIN_MAX(o); \
|
||||||
@@ -116,9 +115,7 @@ static i2s_config_t i2s_config;
|
|||||||
static int bytes_per_frame;
|
static int bytes_per_frame;
|
||||||
static thread_type thread, stats_thread;
|
static thread_type thread, stats_thread;
|
||||||
static u8_t *obuf;
|
static u8_t *obuf;
|
||||||
static frames_t oframes;
|
|
||||||
static bool spdif;
|
static bool spdif;
|
||||||
static size_t dma_buf_frames;
|
|
||||||
|
|
||||||
DECLARE_ALL_MIN_MAX;
|
DECLARE_ALL_MIN_MAX;
|
||||||
|
|
||||||
@@ -154,15 +151,13 @@ static void spdif_convert(ISAMPLE_T *src, size_t frames, u32_t *dst, size_t *cou
|
|||||||
#define I2C_PORT 0
|
#define I2C_PORT 0
|
||||||
#define I2C_ADDR 0x4c
|
#define I2C_ADDR 0x4c
|
||||||
#define VOLUME_GPIO 33
|
#define VOLUME_GPIO 33
|
||||||
#define JACK_GPIO 34
|
#define JACK_GPIO 39
|
||||||
|
|
||||||
struct tas575x_cmd_s {
|
struct tas575x_cmd_s {
|
||||||
u8_t reg;
|
u8_t reg;
|
||||||
u8_t value;
|
u8_t value;
|
||||||
};
|
};
|
||||||
|
|
||||||
u8_t config_spdif_gpio = CONFIG_SPDIF_DO_IO;
|
|
||||||
|
|
||||||
static const struct tas575x_cmd_s tas575x_init_sequence[] = {
|
static const struct tas575x_cmd_s tas575x_init_sequence[] = {
|
||||||
{ 0x00, 0x00 }, // select page 0
|
{ 0x00, 0x00 }, // select page 0
|
||||||
{ 0x02, 0x10 }, // standby
|
{ 0x02, 0x10 }, // standby
|
||||||
@@ -199,7 +194,7 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
|||||||
gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
|
gpio_set_direction(JACK_GPIO, GPIO_MODE_INPUT);
|
||||||
|
|
||||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||||
adc1_config_channel_atten(ADC1_CHANNEL_7, ADC_ATTEN_DB_0);
|
adc1_config_channel_atten(ADC1_CHANNEL_0,ADC_ATTEN_DB_0);
|
||||||
|
|
||||||
// init volume & mute
|
// init volume & mute
|
||||||
gpio_pad_select_gpio(VOLUME_GPIO);
|
gpio_pad_select_gpio(VOLUME_GPIO);
|
||||||
@@ -271,25 +266,12 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
|||||||
};
|
};
|
||||||
i2s_config.sample_rate = output.current_sample_rate * 2;
|
i2s_config.sample_rate = output.current_sample_rate * 2;
|
||||||
i2s_config.bits_per_sample = 32;
|
i2s_config.bits_per_sample = 32;
|
||||||
// Normally counted in frames, but 16 sample are transformed into 32 bits in spdif
|
|
||||||
i2s_config.dma_buf_len = DMA_BUF_LEN / 2;
|
|
||||||
i2s_config.dma_buf_count = DMA_BUF_COUNT * 2;
|
|
||||||
/*
|
|
||||||
In DMA, we have room for (LEN * COUNT) frames of 32 bits samples that
|
|
||||||
we push at sample_rate * 2. Each of these peuso-frames is a single true
|
|
||||||
audio frame. So the real depth is true frames is (LEN * COUNT / 2)
|
|
||||||
*/
|
|
||||||
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN / 2;
|
|
||||||
} else {
|
} else {
|
||||||
pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO,
|
pin_config = (i2s_pin_config_t) { .bck_io_num = CONFIG_I2S_BCK_IO, .ws_io_num = CONFIG_I2S_WS_IO,
|
||||||
.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = -1 //Not used
|
.data_out_num = CONFIG_I2S_DO_IO, .data_in_num = -1 //Not used
|
||||||
};
|
};
|
||||||
i2s_config.sample_rate = output.current_sample_rate;
|
i2s_config.sample_rate = output.current_sample_rate;
|
||||||
i2s_config.bits_per_sample = bytes_per_frame * 8 / 2;
|
i2s_config.bits_per_sample = bytes_per_frame * 8 / 2;
|
||||||
// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
|
|
||||||
i2s_config.dma_buf_len = DMA_BUF_LEN;
|
|
||||||
i2s_config.dma_buf_count = DMA_BUF_COUNT;
|
|
||||||
dma_buf_frames = DMA_BUF_COUNT * DMA_BUF_LEN;
|
|
||||||
#ifdef TAS575x
|
#ifdef TAS575x
|
||||||
gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO);
|
gpio_pad_select_gpio(CONFIG_SPDIF_DO_IO);
|
||||||
gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT);
|
gpio_set_direction(CONFIG_SPDIF_DO_IO, GPIO_MODE_OUTPUT);
|
||||||
@@ -302,6 +284,9 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
|||||||
i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
|
i2s_config.communication_format = I2S_COMM_FORMAT_I2S| I2S_COMM_FORMAT_I2S_MSB;
|
||||||
// in case of overflow, do not replay old buffer
|
// in case of overflow, do not replay old buffer
|
||||||
i2s_config.tx_desc_auto_clear = true;
|
i2s_config.tx_desc_auto_clear = true;
|
||||||
|
i2s_config.dma_buf_count = DMA_BUF_COUNT;
|
||||||
|
// Counted in frames (but i2s allocates a buffer <= 4092 bytes)
|
||||||
|
i2s_config.dma_buf_len = DMA_BUF_LEN;
|
||||||
i2s_config.use_apll = true;
|
i2s_config.use_apll = true;
|
||||||
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
|
i2s_config.intr_alloc_flags = ESP_INTR_FLAG_LEVEL1; //Interrupt level 1
|
||||||
|
|
||||||
@@ -317,20 +302,14 @@ void output_init_i2s(log_level level, char *device, unsigned output_buf_size, ch
|
|||||||
|
|
||||||
dac_cmd(DAC_OFF);
|
dac_cmd(DAC_OFF);
|
||||||
|
|
||||||
esp_pthread_cfg_t cfg = esp_pthread_get_default_config();
|
pthread_attr_t attr;
|
||||||
|
pthread_attr_init(&attr);
|
||||||
|
pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE);
|
||||||
|
pthread_create_name(&thread, &attr, output_thread_i2s, NULL, "output_i2s");
|
||||||
|
pthread_attr_destroy(&attr);
|
||||||
|
|
||||||
cfg.thread_name= "output_i2s";
|
// leave stack size to default
|
||||||
cfg.inherit_cfg = false;
|
pthread_create_name(&stats_thread, NULL, output_thread_i2s_stats, NULL, "output_i2s_sts");
|
||||||
cfg.prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT + 1;
|
|
||||||
cfg.stack_size = PTHREAD_STACK_MIN + OUTPUT_THREAD_STACK_SIZE;
|
|
||||||
esp_pthread_set_cfg(&cfg);
|
|
||||||
pthread_create(&thread, NULL, output_thread_i2s, NULL);
|
|
||||||
|
|
||||||
cfg.thread_name= "output_i2s_sts";
|
|
||||||
cfg.prio = CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT - 1;
|
|
||||||
cfg.stack_size = 2048;
|
|
||||||
esp_pthread_set_cfg(&cfg);
|
|
||||||
pthread_create(&stats_thread, NULL, output_thread_i2s_stats, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -382,13 +361,13 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
|
|||||||
_apply_gain(outputbuf, out_frames, gainL, gainR);
|
_apply_gain(outputbuf, out_frames, gainL, gainR);
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(obuf + oframes * bytes_per_frame, outputbuf->readp, out_frames * bytes_per_frame);
|
memcpy(obuf, outputbuf->readp, out_frames * bytes_per_frame);
|
||||||
#else
|
#else
|
||||||
optr = (s32_t*) outputbuf->readp;
|
optr = (s32_t*) outputbuf->readp;
|
||||||
#endif
|
#endif
|
||||||
} else {
|
} else {
|
||||||
#if BYTES_PER_FRAME == 4
|
#if BYTES_PER_FRAME == 4
|
||||||
memcpy(obuf + oframes * bytes_per_frame, silencebuf, out_frames * bytes_per_frame);
|
memcpy(obuf, silencebuf, out_frames * bytes_per_frame);
|
||||||
#else
|
#else
|
||||||
optr = (s32_t*) silencebuf;
|
optr = (s32_t*) silencebuf;
|
||||||
#endif
|
#endif
|
||||||
@@ -402,20 +381,19 @@ static int _i2s_write_frames(frames_t out_frames, bool silence, s32_t gainL, s32
|
|||||||
dsd_invert((u32_t *) optr, out_frames);
|
dsd_invert((u32_t *) optr, out_frames);
|
||||||
)
|
)
|
||||||
|
|
||||||
_scale_and_pack_frames(obuf + oframes * bytes_per_frame, optr, out_frames, gainL, gainR, output.format);
|
_scale_and_pack_frames(obuf, optr, out_frames, gainL, gainR, output.format);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
oframes += out_frames;
|
|
||||||
|
|
||||||
return out_frames;
|
return out_frames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/****************************************************************************************
|
/****************************************************************************************
|
||||||
* Main output thread
|
* Main output thread
|
||||||
*/
|
*/
|
||||||
static void *output_thread_i2s() {
|
static void *output_thread_i2s() {
|
||||||
size_t count = 0, bytes;
|
size_t count = 0, bytes;
|
||||||
frames_t iframes = FRAME_BLOCK;
|
frames_t iframes = FRAME_BLOCK, oframes;
|
||||||
uint32_t timer_start = 0;
|
uint32_t timer_start = 0;
|
||||||
int discard = 0;
|
int discard = 0;
|
||||||
uint32_t fullness = gettime_ms();
|
uint32_t fullness = gettime_ms();
|
||||||
@@ -457,14 +435,11 @@ static void *output_thread_i2s() {
|
|||||||
synced = false;
|
synced = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
oframes = 0;
|
|
||||||
output.updated = gettime_ms();
|
output.updated = gettime_ms();
|
||||||
output.frames_played_dmp = output.frames_played;
|
output.frames_played_dmp = output.frames_played;
|
||||||
// try to estimate how much we have consumed from the DMA buffer (calculation is incorrect at the very beginning ...)
|
// try to estimate how much we have consumed from the DMA buffer
|
||||||
output.device_frames = dma_buf_frames - ((output.updated - fullness) * output.current_sample_rate) / 1000;
|
output.device_frames = DMA_BUF_COUNT * DMA_BUF_LEN - ((output.updated - fullness) * output.current_sample_rate) / 1000;
|
||||||
_output_frames( iframes );
|
oframes = _output_frames( iframes );
|
||||||
// oframes must be a global updated by the write callback
|
|
||||||
output.frames_in_process = oframes;
|
|
||||||
|
|
||||||
SET_MIN_MAX_SIZED(oframes,rec,iframes);
|
SET_MIN_MAX_SIZED(oframes,rec,iframes);
|
||||||
SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
|
SET_MIN_MAX_SIZED(_buf_used(outputbuf),o,outputbuf->size);
|
||||||
@@ -542,7 +517,7 @@ static void *output_thread_i2s() {
|
|||||||
static void *output_thread_i2s_stats() {
|
static void *output_thread_i2s_stats() {
|
||||||
while (running) {
|
while (running) {
|
||||||
#ifdef TAS575x
|
#ifdef TAS575x
|
||||||
LOG_ERROR("Jack %d Voltage %.2fV", !gpio_get_level(JACK_GPIO), adc1_get_raw(ADC1_CHANNEL_7) / 4095. * (10+174)/10. * 1.1);
|
LOG_ERROR("Jack %d Voltage %.2fV", !gpio_get_level(JACK_GPIO), adc1_get_raw(ADC1_CHANNEL_0) / 4095. * (10+169)/10. * 1.1);
|
||||||
#endif
|
#endif
|
||||||
LOCK;
|
LOCK;
|
||||||
output_state state = output.state;
|
output_state state = output.state;
|
||||||
@@ -567,11 +542,6 @@ static void *output_thread_i2s_stats() {
|
|||||||
LOG_INFO(" ----------+----------+-----------+-----------+");
|
LOG_INFO(" ----------+----------+-----------+-----------+");
|
||||||
RESET_ALL_MIN_MAX;
|
RESET_ALL_MIN_MAX;
|
||||||
}
|
}
|
||||||
LOG_INFO("Heap internal:%zu (min:%zu) external:%zu (min:%zu)",
|
|
||||||
heap_caps_get_free_size(MALLOC_CAP_INTERNAL),
|
|
||||||
heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL),
|
|
||||||
heap_caps_get_free_size(MALLOC_CAP_SPIRAM),
|
|
||||||
heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM));
|
|
||||||
usleep(STATS_PERIOD_MS *1000);
|
usleep(STATS_PERIOD_MS *1000);
|
||||||
}
|
}
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|||||||
@@ -371,8 +371,6 @@ static void process_strm(u8_t *pkt, int len) {
|
|||||||
sendSTAT("STMc", 0);
|
sendSTAT("STMc", 0);
|
||||||
sentSTMu = sentSTMo = sentSTMl = false;
|
sentSTMu = sentSTMo = sentSTMl = false;
|
||||||
LOCK_O;
|
LOCK_O;
|
||||||
output.external = false;
|
|
||||||
_buf_resize(outputbuf, output.init_size);
|
|
||||||
output.threshold = strm->output_threshold;
|
output.threshold = strm->output_threshold;
|
||||||
output.next_replay_gain = unpackN(&strm->replay_gain);
|
output.next_replay_gain = unpackN(&strm->replay_gain);
|
||||||
output.fade_mode = strm->transition_type - '0';
|
output.fade_mode = strm->transition_type - '0';
|
||||||
@@ -631,6 +629,7 @@ static void slimproto_run() {
|
|||||||
#endif
|
#endif
|
||||||
last = now;
|
last = now;
|
||||||
|
|
||||||
|
|
||||||
LOCK_S;
|
LOCK_S;
|
||||||
status.stream_full = _buf_used(streambuf);
|
status.stream_full = _buf_used(streambuf);
|
||||||
status.stream_size = streambuf->size;
|
status.stream_size = streambuf->size;
|
||||||
@@ -704,7 +703,7 @@ static void slimproto_run() {
|
|||||||
if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
|
if (_start_output && (output.state == OUTPUT_STOPPED || output.state == OUTPUT_OFF)) {
|
||||||
output.state = OUTPUT_BUFFER;
|
output.state = OUTPUT_BUFFER;
|
||||||
}
|
}
|
||||||
if (!output.external && output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
|
if (output.state == OUTPUT_RUNNING && !sentSTMu && status.output_full == 0 && status.stream_state <= DISCONNECT &&
|
||||||
_decode_state == DECODE_STOPPED) {
|
_decode_state == DECODE_STOPPED) {
|
||||||
|
|
||||||
_sendSTMu = true;
|
_sendSTMu = true;
|
||||||
@@ -722,7 +721,7 @@ static void slimproto_run() {
|
|||||||
output.state = OUTPUT_OFF;
|
output.state = OUTPUT_OFF;
|
||||||
LOG_DEBUG("output timeout");
|
LOG_DEBUG("output timeout");
|
||||||
}
|
}
|
||||||
if (!output.external && output.state == OUTPUT_RUNNING && now - status.last > 1000) {
|
if (output.state == OUTPUT_RUNNING && now - status.last > 1000) {
|
||||||
_sendSTMt = true;
|
_sendSTMt = true;
|
||||||
status.last = now;
|
status.last = now;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -539,7 +539,6 @@ unsigned _buf_cont_write(struct buffer *buf);
|
|||||||
void _buf_inc_readp(struct buffer *buf, unsigned by);
|
void _buf_inc_readp(struct buffer *buf, unsigned by);
|
||||||
void _buf_inc_writep(struct buffer *buf, unsigned by);
|
void _buf_inc_writep(struct buffer *buf, unsigned by);
|
||||||
void buf_flush(struct buffer *buf);
|
void buf_flush(struct buffer *buf);
|
||||||
void _buf_flush(struct buffer *buf);
|
|
||||||
void buf_adjust(struct buffer *buf, size_t mod);
|
void buf_adjust(struct buffer *buf, size_t mod);
|
||||||
void _buf_resize(struct buffer *buf, size_t size);
|
void _buf_resize(struct buffer *buf, size_t size);
|
||||||
void buf_init(struct buffer *buf, size_t size);
|
void buf_init(struct buffer *buf, size_t size);
|
||||||
@@ -635,7 +634,7 @@ bool resample_init(char *opt);
|
|||||||
|
|
||||||
// output.c output_alsa.c output_pa.c output_pack.c
|
// output.c output_alsa.c output_pa.c output_pack.c
|
||||||
typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING,
|
typedef enum { OUTPUT_OFF = -1, OUTPUT_STOPPED = 0, OUTPUT_BUFFER, OUTPUT_RUNNING,
|
||||||
OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT } output_state;
|
OUTPUT_PAUSE_FRAMES, OUTPUT_SKIP_FRAMES, OUTPUT_START_AT, OUTPUT_EXTERNAL } output_state;
|
||||||
|
|
||||||
#if DSD
|
#if DSD
|
||||||
typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format;
|
typedef enum { PCM, DOP, DSD_U8, DSD_U16_LE, DSD_U32_LE, DSD_U16_BE, DSD_U32_BE, DOP_S24_LE, DOP_S24_3LE } dsd_format;
|
||||||
@@ -655,8 +654,6 @@ struct outputstate {
|
|||||||
output_state state;
|
output_state state;
|
||||||
output_format format;
|
output_format format;
|
||||||
const char *device;
|
const char *device;
|
||||||
bool external;
|
|
||||||
u32_t init_size;
|
|
||||||
#if ALSA
|
#if ALSA
|
||||||
unsigned buffer;
|
unsigned buffer;
|
||||||
unsigned period;
|
unsigned period;
|
||||||
@@ -676,7 +673,6 @@ struct outputstate {
|
|||||||
unsigned default_sample_rate;
|
unsigned default_sample_rate;
|
||||||
bool error_opening;
|
bool error_opening;
|
||||||
unsigned device_frames;
|
unsigned device_frames;
|
||||||
unsigned frames_in_process;
|
|
||||||
u32_t updated;
|
u32_t updated;
|
||||||
u32_t track_start_time;
|
u32_t track_start_time;
|
||||||
u32_t current_replay_gain;
|
u32_t current_replay_gain;
|
||||||
|
|||||||
11
components/wifi-manager/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
set(COMPONENT_ADD_INCLUDEDIRS .)
|
||||||
|
|
||||||
|
set(COMPONENT_SRCS "dns_server.c" "http_server.c" "json.c" "wifi_manager.c")
|
||||||
|
set(REQUIRES esp_common)
|
||||||
|
set(COMPONENT_EMBED_FILES "style.css jquery.gz code.js index.html")
|
||||||
|
|
||||||
|
set(REQUIRES_COMPONENTS freertos )
|
||||||
|
|
||||||
|
register_component()
|
||||||
|
|
||||||
|
|
||||||
67
components/wifi-manager/Kconfig.projbuild
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
menu "Wifi Manager Configuration"
|
||||||
|
|
||||||
|
config WIFI_MANAGER_TASK_PRIORITY
|
||||||
|
int "RTOS Task Priority for the wifi_manager"
|
||||||
|
default 5
|
||||||
|
help
|
||||||
|
Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1. For this particular reason, minimum recommended task priority is 2.
|
||||||
|
|
||||||
|
config WIFI_MANAGER_MAX_RETRY
|
||||||
|
int "Max Retry on failed connection"
|
||||||
|
default 2
|
||||||
|
help
|
||||||
|
Defines when a connection is lost/attempt to connect is made, how many retries should be made before giving up.
|
||||||
|
|
||||||
|
config DEFAULT_AP_SSID
|
||||||
|
string "Access Point SSID"
|
||||||
|
default "esp32"
|
||||||
|
help
|
||||||
|
SSID (network name) the the esp32 will broadcast.
|
||||||
|
|
||||||
|
config DEFAULT_AP_PASSWORD
|
||||||
|
string "Access Point Password"
|
||||||
|
default "esp32pwd"
|
||||||
|
help
|
||||||
|
Password used for the Access Point. Leave empty and set AUTH MODE to WIFI_AUTH_OPEN for no password.
|
||||||
|
|
||||||
|
config DEFAULT_AP_CHANNEL
|
||||||
|
int "Access Point WiFi Channel"
|
||||||
|
default 1
|
||||||
|
help
|
||||||
|
Be careful you might not see the access point if you use a channel not allowed in your country.
|
||||||
|
|
||||||
|
config DEFAULT_AP_IP
|
||||||
|
string "Access Point IP Address"
|
||||||
|
default "10.10.0.1"
|
||||||
|
help
|
||||||
|
This is used for the redirection to the captive portal. It is recommended to leave unchanged.
|
||||||
|
|
||||||
|
config DEFAULT_AP_GATEWAY
|
||||||
|
string "Access Point IP Gateway"
|
||||||
|
default "10.10.0.1"
|
||||||
|
help
|
||||||
|
This is used for the redirection to the captive portal. It is recommended to leave unchanged.
|
||||||
|
|
||||||
|
config DEFAULT_AP_NETMASK
|
||||||
|
string "Access Point Netmask"
|
||||||
|
default "255.255.255.0"
|
||||||
|
help
|
||||||
|
This is used for the redirection to the captive portal. It is recommended to leave unchanged.
|
||||||
|
|
||||||
|
config DEFAULT_AP_MAX_CONNECTIONS
|
||||||
|
int "Access Point Max Connections"
|
||||||
|
default 4
|
||||||
|
help
|
||||||
|
Max is 4.
|
||||||
|
|
||||||
|
config DEFAULT_AP_BEACON_INTERVAL
|
||||||
|
int "Access Point Beacon Interval (ms)"
|
||||||
|
default 100
|
||||||
|
help
|
||||||
|
100ms is the recommended default.
|
||||||
|
config DEFAULT_COMMAND_LINE
|
||||||
|
string "Default command line to execute"
|
||||||
|
default "squeezelite -o I2S -b 500:2000 -d all=info"
|
||||||
|
help
|
||||||
|
This is the command to run when starting the device
|
||||||
|
endmenu
|
||||||
19
components/wifi-manager/LICENSE.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
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.
|
||||||
41
components/wifi-manager/README.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# What is esp32-wifi-manager?
|
||||||
|
*esp32-wifi-manager* is an esp32 program that enables easy management of wifi networks through a web application.
|
||||||
|
|
||||||
|
*esp32-wifi-manager* is **lightweight** (8KB of task stack in total) and barely uses any CPU power through a completely event driven architecture. It's an all in one wifi scanner, http server & dns daemon living in the least amount of RAM possible.
|
||||||
|
|
||||||
|
For real time constrained applications, *esp32-wifi-manager* can live entirely on PRO CPU, leaving the entire APP CPU untouched for your own needs.
|
||||||
|
|
||||||
|
*esp32-wifi-manager* will automatically attempt to re-connect to a previously saved network on boot, and it will start its own wifi access point through which you can manage wifi networks if a saved network cannot be found and/or if the connection is lost.
|
||||||
|
|
||||||
|
*esp32-wifi-manager* is an esp-idf project that compiles successfully with the esp-idf 3.2 release. You can simply copy the project and start adding your own code to it.
|
||||||
|
|
||||||
|
# Demo
|
||||||
|
[](http://www.youtube.com/watch?v=hxlZi15bym4)
|
||||||
|
|
||||||
|
# Look and Feel
|
||||||
|
 
|
||||||
|
|
||||||
|
# Adding esp32-wifi-manager to your code
|
||||||
|
Ther are effectively three different ways you can embed esp32-wifi-manager with your code:
|
||||||
|
* Just forget about it and poll in your code for wifi connectivity status
|
||||||
|
* Use event callbacks
|
||||||
|
* Modify esp32-wifi-manager code directly to fit your needs
|
||||||
|
|
||||||
|
**Event callbacks** are the cleanest way to use the wifi manager and that's the recommended way to do it. A typical use-case would be to get notified when wifi manager finally gets a connection an access point. In order to do this you can simply define a callback function:
|
||||||
|
|
||||||
|
```c
|
||||||
|
void cb_connection_ok(void *pvParameter){
|
||||||
|
ESP_LOGI(TAG, "I have a connection!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Then just register it by calling:
|
||||||
|
|
||||||
|
```c
|
||||||
|
wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_ok);
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it! Now everytime the event is triggered it will call this function.
|
||||||
|
|
||||||
|
# License
|
||||||
|
*esp32-wifi-manager* is MIT licensed. As such, it can be included in any project, commercial or not, as long as you retain original copyright. Please make sure to read the license file.
|
||||||
12
components/wifi-manager/ap.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{"ssid":"Pantum-AP-A6D49F","chan":11,"rssi":-55,"auth":4},
|
||||||
|
{"ssid":"a0308","chan":1,"rssi":-56,"auth":3},
|
||||||
|
{"ssid":"dlink-D9D8","chan":11,"rssi":-82,"auth":4},
|
||||||
|
{"ssid":"Linksys06730","chan":7,"rssi":-85,"auth":3},
|
||||||
|
{"ssid":"SINGTEL-5171","chan":9,"rssi":-88,"auth":4},
|
||||||
|
{"ssid":"1126-1","chan":11,"rssi":-89,"auth":4},
|
||||||
|
{"ssid":"The Shah 5GHz-2","chan":1,"rssi":-90,"auth":3},
|
||||||
|
{"ssid":"SINGTEL-1D28 (2G)","chan":11,"rssi":-91,"auth":3},
|
||||||
|
{"ssid":"dlink-F864","chan":1,"rssi":-92,"auth":4},
|
||||||
|
{"ssid":"dlink-74F0","chan":1,"rssi":-93,"auth":4}
|
||||||
|
]
|
||||||
454
components/wifi-manager/code.js
Normal file
@@ -0,0 +1,454 @@
|
|||||||
|
// First, checks if it isn't implemented yet.
|
||||||
|
if (!String.prototype.format) {
|
||||||
|
String.prototype.format = function() {
|
||||||
|
var args = arguments;
|
||||||
|
return this.replace(/{(\d+)}/g, function(match, number) {
|
||||||
|
return typeof args[number] != 'undefined'
|
||||||
|
? args[number]
|
||||||
|
: match
|
||||||
|
;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var apList = null;
|
||||||
|
var selectedSSID = "";
|
||||||
|
var refreshAPInterval = null;
|
||||||
|
var checkStatusInterval = null;
|
||||||
|
var checkConfigInterval = null;
|
||||||
|
|
||||||
|
var StatusIntervalActive = false;
|
||||||
|
var ConfigIntervalActive = false;
|
||||||
|
var RefreshAPIIntervalActive = false;
|
||||||
|
|
||||||
|
|
||||||
|
function stopCheckStatusInterval(){
|
||||||
|
if(checkStatusInterval != null){
|
||||||
|
clearTimeout(checkStatusInterval);
|
||||||
|
checkStatusInterval = null;
|
||||||
|
}
|
||||||
|
StatusIntervalActive = false;
|
||||||
|
}
|
||||||
|
function stopCheckConfigInterval(){
|
||||||
|
if(checkConfigInterval != null){
|
||||||
|
clearTimeout(checkConfigInterval);
|
||||||
|
checkConfigInterval = null;
|
||||||
|
}
|
||||||
|
ConfigIntervalActive=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopRefreshAPInterval(){
|
||||||
|
|
||||||
|
if(refreshAPInterval != null){
|
||||||
|
clearTimeout(refreshAPInterval);
|
||||||
|
refreshAPInterval = null;
|
||||||
|
}
|
||||||
|
RefreshAPIIntervalActive = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function startCheckStatusInterval(){
|
||||||
|
StatusIntervalActive = true;
|
||||||
|
checkStatusInterval = setTimeout(checkStatus, 950);
|
||||||
|
}
|
||||||
|
function startCheckConfigInterval(){
|
||||||
|
ConfigIntervalActive = true;
|
||||||
|
checkConfigInterval = setTimeout(checkConfig, 950);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRefreshAPInterval(){
|
||||||
|
RefreshAPIIntervalActive = true;
|
||||||
|
refreshAPInterval = setTimeout(refreshAP, 2800);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function RepeatCheckStatusInterval(){
|
||||||
|
if(StatusIntervalActive)
|
||||||
|
startCheckStatusInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
function RepeatCheckConfigInterval(){
|
||||||
|
if(ConfigIntervalActive)
|
||||||
|
startCheckConfigInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
function RepeatRefreshAPInterval(){
|
||||||
|
if(RefreshAPIIntervalActive)
|
||||||
|
startRefreshAPInterval()
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function(){
|
||||||
|
|
||||||
|
|
||||||
|
$("#wifi-status").on("click", ".ape", function() {
|
||||||
|
$( "#wifi" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect-details" ).slideDown( "fast", function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#manual_add").on("click", ".ape", function() {
|
||||||
|
selectedSSID = $(this).text();
|
||||||
|
$( "#ssid-pwd" ).text(selectedSSID);
|
||||||
|
$( "#wifi" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect_manual" ).slideDown( "fast", function() {});
|
||||||
|
$( "#connect" ).slideUp( "fast", function() {});
|
||||||
|
|
||||||
|
//update wait screen
|
||||||
|
$( "#loading" ).show();
|
||||||
|
$( "#connect-success" ).hide();
|
||||||
|
$( "#connect-fail" ).hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#wifi-list").on("click", ".ape", function() {
|
||||||
|
selectedSSID = $(this).text();
|
||||||
|
$( "#ssid-pwd" ).text(selectedSSID);
|
||||||
|
$( "#wifi" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect_manual" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect" ).slideDown( "fast", function() {});
|
||||||
|
|
||||||
|
//update wait screen
|
||||||
|
$( "#loading" ).show();
|
||||||
|
$( "#connect-success" ).hide();
|
||||||
|
$( "#connect-fail" ).hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#cancel").on("click", function() {
|
||||||
|
selectedSSID = "";
|
||||||
|
$( "#connect" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect_manual" ).slideUp( "fast", function() {});
|
||||||
|
$( "#wifi" ).slideDown( "fast", function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#manual_cancel").on("click", function() {
|
||||||
|
selectedSSID = "";
|
||||||
|
$( "#connect" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect_manual" ).slideUp( "fast", function() {});
|
||||||
|
$( "#wifi" ).slideDown( "fast", function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#join").on("click", function() {
|
||||||
|
performConnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#manual_join").on("click", function() {
|
||||||
|
performConnect($(this).data('connect'));
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#ok-details").on("click", function() {
|
||||||
|
$( "#connect-details" ).slideUp( "fast", function() {});
|
||||||
|
$( "#wifi" ).slideDown( "fast", function() {});
|
||||||
|
|
||||||
|
});
|
||||||
|
$("#update").on("click", function() {
|
||||||
|
|
||||||
|
performUpdate();
|
||||||
|
});
|
||||||
|
$("#factory").on("click", function() {
|
||||||
|
|
||||||
|
performFactory();
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#ok-credits").on("click", function() {
|
||||||
|
$( "#credits" ).slideUp( "fast", function() {});
|
||||||
|
$( "#app" ).slideDown( "fast", function() {});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#acredits").on("click", function(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
$( "#app" ).slideUp( "fast", function() {});
|
||||||
|
$( "#credits" ).slideDown( "fast", function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#ok-connect").on("click", function() {
|
||||||
|
$( "#connect-wait" ).slideUp( "fast", function() {});
|
||||||
|
$( "#wifi" ).slideDown( "fast", function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#disconnect").on("click", function() {
|
||||||
|
$( "#connect-details-wrap" ).addClass('blur');
|
||||||
|
$( "#diag-disconnect" ).slideDown( "fast", function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#no-disconnect").on("click", function() {
|
||||||
|
$( "#diag-disconnect" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect-details-wrap" ).removeClass('blur');
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#yes-disconnect").on("click", function() {
|
||||||
|
|
||||||
|
stopCheckStatusInterval();
|
||||||
|
selectedSSID = "";
|
||||||
|
|
||||||
|
$( "#diag-disconnect" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect-details-wrap" ).removeClass('blur');
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/connect.json',
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'DELETE',
|
||||||
|
cache: false,
|
||||||
|
data: { 'timestamp': Date.now()}
|
||||||
|
});
|
||||||
|
|
||||||
|
startCheckStatusInterval();
|
||||||
|
|
||||||
|
$( "#connect-details" ).slideUp( "fast", function() {});
|
||||||
|
$( "#wifi" ).slideDown( "fast", function() {})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//first time the page loads: attempt get the connection status and start the wifi scan
|
||||||
|
refreshAP();
|
||||||
|
startCheckStatusInterval();
|
||||||
|
startRefreshAPInterval();
|
||||||
|
startCheckConfigInterval();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function performUpdate(){
|
||||||
|
autoexec1 = $("#autoexec1").val();
|
||||||
|
//reset connection
|
||||||
|
//
|
||||||
|
// $( "#ok-connect" ).prop("disabled",true);
|
||||||
|
// $( "#ssid-wait" ).text(selectedSSID);
|
||||||
|
// $( "#connect" ).slideUp( "fast", function() {});
|
||||||
|
// $( "#connect_manual" ).slideUp( "fast", function() {});
|
||||||
|
// $( "#connect-wait" ).slideDown( "fast", function() {});
|
||||||
|
// // todo: should we update the UI here?
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/config.json',
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'POST',
|
||||||
|
cache: false,
|
||||||
|
headers: { 'X-Custom-autoexec1': autoexec1 },
|
||||||
|
data: { 'timestamp': Date.now()}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function performFactory(){
|
||||||
|
|
||||||
|
// $( "#ok-connect" ).prop("disabled",true);
|
||||||
|
// $( "#ssid-wait" ).text(selectedSSID);
|
||||||
|
// $( "#connect" ).slideUp( "fast", function() {});
|
||||||
|
// $( "#connect_manual" ).slideUp( "fast", function() {});
|
||||||
|
// $( "#connect-wait" ).slideDown( "fast", function() {});
|
||||||
|
// // todo: should we update the UI here?
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/factory.json',
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'POST',
|
||||||
|
cache: false,
|
||||||
|
data: { 'timestamp': Date.now()}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function performConnect(conntype){
|
||||||
|
|
||||||
|
//stop the status refresh. This prevents a race condition where a status
|
||||||
|
//request would be refreshed with wrong ip info from a previous connection
|
||||||
|
//and the request would automatically shows as succesful.
|
||||||
|
stopCheckStatusInterval();
|
||||||
|
|
||||||
|
//stop refreshing wifi list
|
||||||
|
stopRefreshAPInterval();
|
||||||
|
|
||||||
|
var pwd;
|
||||||
|
if (conntype == 'manual') {
|
||||||
|
//Grab the manual SSID and PWD
|
||||||
|
selectedSSID=$('#manual_ssid').val();
|
||||||
|
pwd = $("#manual_pwd").val();
|
||||||
|
}else{
|
||||||
|
pwd = $("#pwd").val();
|
||||||
|
}
|
||||||
|
//reset connection
|
||||||
|
$( "#loading" ).show();
|
||||||
|
$( "#connect-success" ).hide();
|
||||||
|
$( "#connect-fail" ).hide();
|
||||||
|
|
||||||
|
$( "#ok-connect" ).prop("disabled",true);
|
||||||
|
$( "#ssid-wait" ).text(selectedSSID);
|
||||||
|
$( "#connect" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect_manual" ).slideUp( "fast", function() {});
|
||||||
|
$( "#connect-wait" ).slideDown( "fast", function() {});
|
||||||
|
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
url: '/connect.json',
|
||||||
|
dataType: 'json',
|
||||||
|
method: 'POST',
|
||||||
|
cache: false,
|
||||||
|
headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd },
|
||||||
|
data: { 'timestamp': Date.now()}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
//now we can re-set the intervals regardless of result
|
||||||
|
startCheckStatusInterval();
|
||||||
|
startRefreshAPInterval();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function rssiToIcon(rssi){
|
||||||
|
if(rssi >= -60){
|
||||||
|
return 'w0';
|
||||||
|
}
|
||||||
|
else if(rssi >= -67){
|
||||||
|
return 'w1';
|
||||||
|
}
|
||||||
|
else if(rssi >= -75){
|
||||||
|
return 'w2';
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return 'w3';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function refreshAP(){
|
||||||
|
$.getJSON( "/ap.json", function( data ) {
|
||||||
|
if(data.length > 0){
|
||||||
|
//sort by signal strength
|
||||||
|
data.sort(function (a, b) {
|
||||||
|
var x = a["rssi"]; var y = b["rssi"];
|
||||||
|
return ((x < y) ? 1 : ((x > y) ? -1 : 0));
|
||||||
|
});
|
||||||
|
apList = data;
|
||||||
|
refreshAPHTML(apList);
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
RepeatRefreshAPInterval();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshAPHTML(data){
|
||||||
|
var h = "";
|
||||||
|
data.forEach(function(e, idx, array) {
|
||||||
|
h += '<div class="ape{0}"><div class="{1}"><div class="{2}">{3}</div></div></div>'.format(idx === array.length - 1?'':' brdb', rssiToIcon(e.rssi), e.auth==0?'':'pw',e.ssid);
|
||||||
|
h += "\n";
|
||||||
|
});
|
||||||
|
|
||||||
|
$( "#wifi-list" ).html(h)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function checkStatus(){
|
||||||
|
$.getJSON( "/status.json", function( data ) {
|
||||||
|
if(data.hasOwnProperty('autoexec1') && data['autoexec1'] != ""){
|
||||||
|
$("#autoexec1_current").text(data["autoexec1"]);
|
||||||
|
}
|
||||||
|
if(data.hasOwnProperty('ssid') && data['ssid'] != ""){
|
||||||
|
if(data["ssid"] === selectedSSID){
|
||||||
|
//that's a connection attempt
|
||||||
|
if(data["urc"] === 0){
|
||||||
|
//got connection
|
||||||
|
$("#connected-to span").text(data["ssid"]);
|
||||||
|
$("#connect-details h1").text(data["ssid"]);
|
||||||
|
$("#ip").text(data["ip"]);
|
||||||
|
$("#netmask").text(data["netmask"]);
|
||||||
|
$("#gw").text(data["gw"]);
|
||||||
|
$("#wifi-status").slideDown( "fast", function() {});
|
||||||
|
|
||||||
|
//unlock the wait screen if needed
|
||||||
|
$( "#ok-connect" ).prop("disabled",false);
|
||||||
|
|
||||||
|
//update wait screen
|
||||||
|
$( "#loading" ).hide();
|
||||||
|
$( "#connect-success" ).show();
|
||||||
|
$( "#connect-fail" ).hide();
|
||||||
|
}
|
||||||
|
else if(data["urc"] === 1){
|
||||||
|
//failed attempt
|
||||||
|
$("#connected-to span").text('');
|
||||||
|
$("#connect-details h1").text('');
|
||||||
|
$("#ip").text('0.0.0.0');
|
||||||
|
$("#netmask").text('0.0.0.0');
|
||||||
|
$("#gw").text('0.0.0.0');
|
||||||
|
|
||||||
|
//don't show any connection
|
||||||
|
$("#wifi-status").slideUp( "fast", function() {});
|
||||||
|
|
||||||
|
//unlock the wait screen
|
||||||
|
$( "#ok-connect" ).prop("disabled",false);
|
||||||
|
|
||||||
|
//update wait screen
|
||||||
|
$( "#loading" ).hide();
|
||||||
|
$( "#connect-fail" ).show();
|
||||||
|
$( "#connect-success" ).hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(data.hasOwnProperty('urc') && data['urc'] === 0){
|
||||||
|
//ESP32 is already connected to a wifi without having the user do anything
|
||||||
|
if( !($("#wifi-status").is(":visible")) ){
|
||||||
|
$("#connected-to span").text(data["ssid"]);
|
||||||
|
$("#connect-details h1").text(data["ssid"]);
|
||||||
|
$("#ip").text(data["ip"]);
|
||||||
|
$("#netmask").text(data["netmask"]);
|
||||||
|
$("#gw").text(data["gw"]);
|
||||||
|
$("#wifi-status").slideDown( "fast", function() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(data.hasOwnProperty('urc') && data['urc'] === 2){
|
||||||
|
//that's a manual disconnect
|
||||||
|
if($("#wifi-status").is(":visible")){
|
||||||
|
$("#wifi-status").slideUp( "fast", function() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
//don't do anything, the server might be down while esp32 recalibrates radio
|
||||||
|
});
|
||||||
|
|
||||||
|
RepeatCheckStatusInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function checkConfig(){
|
||||||
|
var h = "";
|
||||||
|
//{ "autoexec" : 0, "list" : [{ 'autoexec1' : 'squeezelite -o "I2S" -b 500:2000 -d all=info -M esp32' }]}
|
||||||
|
$.getJSON( "/config.json", function( data ) {
|
||||||
|
if(data.hasOwnProperty('autoexec')) {
|
||||||
|
h+= '<div id="autoexec">Autoexec: {0}</div>'.format(data["autoexec"]===1?"Active":"Inactive");
|
||||||
|
}
|
||||||
|
if(data.hasOwnProperty('list')) {
|
||||||
|
data["list"].forEach(function(e, idx, array) {
|
||||||
|
for (const [key, value] of Object.entries(e)) {
|
||||||
|
h+= '<input id="{0}" type="text" maxlength="201" value="{1}"><br>'.format(key,value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
);
|
||||||
|
h += "\n";
|
||||||
|
$( "#command-list" ).html(h);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.fail(function() {
|
||||||
|
//don't do anything, the server might be down while esp32 recalibrates radio
|
||||||
|
});
|
||||||
|
|
||||||
|
RepeatCheckConfigInterval();
|
||||||
|
|
||||||
|
}
|
||||||
11
components/wifi-manager/component.mk
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#
|
||||||
|
# Component Makefile
|
||||||
|
#
|
||||||
|
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
|
||||||
|
# this will take the sources in the src/ directory, compile them and link them into
|
||||||
|
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||||
|
# please read the SDK documents if you need to do this.
|
||||||
|
#
|
||||||
|
COMPONENT_EMBED_FILES := style.css jquery.gz code.js index.html
|
||||||
|
CFLAGS += -D LOG_LOCAL_LEVEL=ESP_LOG_DEBUG
|
||||||
|
COMPONENT_ADD_INCLUDEDIRS := .
|
||||||
2
components/wifi-manager/compress.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
gzip index.html style.css jquery.js --best --keep --force
|
||||||
|
pause
|
||||||
2
components/wifi-manager/connect
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<html>
|
||||||
|
</html>
|
||||||
184
components/wifi-manager/dns_server.c
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 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 dns_server.c
|
||||||
|
@author Tony Pottier
|
||||||
|
@brief Defines an extremely basic DNS server for captive portal functionality.
|
||||||
|
It's basically a DNS hijack that replies to the esp's address no matter which
|
||||||
|
request is sent to it.
|
||||||
|
|
||||||
|
Contains the freeRTOS task for the DNS server that processes the requests.
|
||||||
|
|
||||||
|
@see https://idyl.io
|
||||||
|
@see https://github.com/tonyp7/esp32-wifi-manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "dns_server.h"
|
||||||
|
|
||||||
|
#include <lwip/sockets.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <freertos/FreeRTOS.h>
|
||||||
|
#include <freertos/task.h>
|
||||||
|
#include <freertos/event_groups.h>
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <esp_wifi.h>
|
||||||
|
#include <esp_event_loop.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include <esp_err.h>
|
||||||
|
#include <nvs_flash.h>
|
||||||
|
|
||||||
|
#include <lwip/err.h>
|
||||||
|
#include <lwip/sockets.h>
|
||||||
|
#include <lwip/sys.h>
|
||||||
|
#include <lwip/netdb.h>
|
||||||
|
#include <lwip/dns.h>
|
||||||
|
|
||||||
|
#include <byteswap.h>
|
||||||
|
|
||||||
|
#include "wifi_manager.h"
|
||||||
|
|
||||||
|
static const char TAG[] = "dns_server";
|
||||||
|
static TaskHandle_t task_dns_server = NULL;
|
||||||
|
int socket_fd;
|
||||||
|
|
||||||
|
void dns_server_start() {
|
||||||
|
xTaskCreate(&dns_server, "dns_server", 3072, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_dns_server);
|
||||||
|
}
|
||||||
|
|
||||||
|
void dns_server_stop(){
|
||||||
|
if(task_dns_server){
|
||||||
|
vTaskDelete(task_dns_server);
|
||||||
|
close(socket_fd);
|
||||||
|
task_dns_server = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void dns_server(void *pvParameters) {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct sockaddr_in sa, ra;
|
||||||
|
|
||||||
|
/* Set redirection DNS hijack to the access point IP */
|
||||||
|
ip4_addr_t ip_resolved;
|
||||||
|
inet_pton(AF_INET, DEFAULT_AP_IP, &ip_resolved);
|
||||||
|
|
||||||
|
|
||||||
|
/* Create UDP socket */
|
||||||
|
socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (socket_fd < 0){
|
||||||
|
ESP_LOGE(TAG, "Failed to create socket");
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
memset(&sa, 0, sizeof(struct sockaddr_in));
|
||||||
|
|
||||||
|
/* Bind to port 53 (typical DNS Server port) */
|
||||||
|
tcpip_adapter_ip_info_t ip;
|
||||||
|
tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip);
|
||||||
|
ra.sin_family = AF_INET;
|
||||||
|
ra.sin_addr.s_addr = ip.ip.addr;
|
||||||
|
ra.sin_port = htons(53);
|
||||||
|
if (bind(socket_fd, (struct sockaddr *)&ra, sizeof(struct sockaddr_in)) == -1) {
|
||||||
|
ESP_LOGE(TAG, "Failed to bind to 53/udp");
|
||||||
|
close(socket_fd);
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sockaddr_in client;
|
||||||
|
socklen_t client_len;
|
||||||
|
client_len = sizeof(client);
|
||||||
|
int length;
|
||||||
|
uint8_t data[DNS_QUERY_MAX_SIZE]; /* dns query buffer */
|
||||||
|
uint8_t response[DNS_ANSWER_MAX_SIZE]; /* dns response buffer */
|
||||||
|
char ip_address[INET_ADDRSTRLEN]; /* buffer to store IPs as text. This is only used for debug and serves no other purpose */
|
||||||
|
char *domain; /* This is only used for debug and serves no other purpose */
|
||||||
|
int err;
|
||||||
|
|
||||||
|
ESP_LOGI(TAG, "DNS Server listening on 53/udp");
|
||||||
|
|
||||||
|
/* Start loop to process DNS requests */
|
||||||
|
for(;;) {
|
||||||
|
|
||||||
|
memset(data, 0x00, sizeof(data)); /* reset buffer */
|
||||||
|
length = recvfrom(socket_fd, data, sizeof(data), 0, (struct sockaddr *)&client, &client_len); /* read udp request */
|
||||||
|
|
||||||
|
/*if the query is bigger than the buffer size we simply ignore it. This case should only happen in case of multiple
|
||||||
|
* queries within the same DNS packet and is not supported by this simple DNS hijack. */
|
||||||
|
if ( length > 0 && ((length + sizeof(dns_answer_t)-1) < DNS_ANSWER_MAX_SIZE) ) {
|
||||||
|
|
||||||
|
data[length] = '\0'; /*in case there's a bogus domain name that isn't null terminated */
|
||||||
|
|
||||||
|
/* Generate header message */
|
||||||
|
memcpy(response, data, sizeof(dns_header_t));
|
||||||
|
dns_header_t *dns_header = (dns_header_t*)response;
|
||||||
|
dns_header->QR = 1; /*response bit */
|
||||||
|
dns_header->OPCode = DNS_OPCODE_QUERY; /* no support for other type of response */
|
||||||
|
dns_header->AA = 1; /*authoritative answer */
|
||||||
|
dns_header->RCode = DNS_REPLY_CODE_NO_ERROR; /* no error */
|
||||||
|
dns_header->TC = 0; /*no truncation */
|
||||||
|
dns_header->RD = 0; /*no recursion */
|
||||||
|
dns_header->ANCount = dns_header->QDCount; /* set answer count = question count -- duhh! */
|
||||||
|
dns_header->NSCount = 0x0000; /* name server resource records = 0 */
|
||||||
|
dns_header->ARCount = 0x0000; /* resource records = 0 */
|
||||||
|
|
||||||
|
|
||||||
|
/* copy the rest of the query in the response */
|
||||||
|
memcpy(response + sizeof(dns_header_t), data + sizeof(dns_header_t), length - sizeof(dns_header_t));
|
||||||
|
|
||||||
|
|
||||||
|
/* extract domain name and request IP for debug */
|
||||||
|
inet_ntop(AF_INET, &(client.sin_addr), ip_address, INET_ADDRSTRLEN);
|
||||||
|
domain = (char*) &data[sizeof(dns_header_t) + 1];
|
||||||
|
for(char* c=domain; *c != '\0'; c++){
|
||||||
|
if(*c < ' ' || *c > 'z') *c = '.'; /* technically we should test if the first two bits are 00 (e.g. if( (*c & 0xC0) == 0x00) *c = '.') but this makes the code a lot more readable */
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG, "Replying to DNS request for %s from %s", domain, ip_address);
|
||||||
|
|
||||||
|
|
||||||
|
/* create DNS answer at the end of the query*/
|
||||||
|
dns_answer_t *dns_answer = (dns_answer_t*)&response[length];
|
||||||
|
dns_answer->NAME = __bswap_16(0xC00C); /* This is a pointer to the beginning of the question. As per DNS standard, first two bits must be set to 11 for some odd reason hence 0xC0 */
|
||||||
|
dns_answer->TYPE = __bswap_16(DNS_ANSWER_TYPE_A);
|
||||||
|
dns_answer->CLASS = __bswap_16(DNS_ANSWER_CLASS_IN);
|
||||||
|
dns_answer->TTL = (uint32_t)0x00000000; /* no caching. Avoids DNS poisoning since this is a DNS hijack */
|
||||||
|
dns_answer->RDLENGTH = __bswap_16(0x0004); /* 4 byte => size of an ipv4 address */
|
||||||
|
dns_answer->RDATA = ip_resolved.addr;
|
||||||
|
|
||||||
|
err = sendto(socket_fd, response, length+sizeof(dns_answer_t), 0, (struct sockaddr *)&client, client_len);
|
||||||
|
if (err < 0) {
|
||||||
|
ESP_LOGE(TAG, "UDP sendto failed: %d", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. DNS daemon should not be taxing on the system */
|
||||||
|
|
||||||
|
}
|
||||||
|
close(socket_fd);
|
||||||
|
|
||||||
|
vTaskDelete ( NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
140
components/wifi-manager/dns_server.h
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 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 dns_server.h
|
||||||
|
@author Tony Pottier
|
||||||
|
@brief Defines an extremly basic DNS server for captive portal functionality.
|
||||||
|
|
||||||
|
Contains the freeRTOS task for the DNS server that processes the requests.
|
||||||
|
|
||||||
|
@see https://idyl.io
|
||||||
|
@see https://github.com/tonyp7/esp32-wifi-manager
|
||||||
|
@see http://www.zytrax.com/books/dns/ch15
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MAIN_DNS_SERVER_H_
|
||||||
|
#define MAIN_DNS_SERVER_H_
|
||||||
|
#include <esp_system.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/** 12 byte header, 64 byte domain name, 4 byte qtype/qclass. This NOT compliant with the RFC, but it's good enough for a captive portal
|
||||||
|
* if a DNS query is too big it just wont be processed. */
|
||||||
|
#define DNS_QUERY_MAX_SIZE 80
|
||||||
|
|
||||||
|
/** Query + 2 byte ptr, 2 byte type, 2 byte class, 4 byte TTL, 2 byte len, 4 byte data */
|
||||||
|
#define DNS_ANSWER_MAX_SIZE (DNS_QUERY_MAX_SIZE+16)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RCODE values used in a DNS header message
|
||||||
|
*/
|
||||||
|
typedef enum dns_reply_code_t {
|
||||||
|
DNS_REPLY_CODE_NO_ERROR = 0,
|
||||||
|
DNS_REPLY_CODE_FORM_ERROR = 1,
|
||||||
|
DNS_REPLY_CODE_SERVER_FAILURE = 2,
|
||||||
|
DNS_REPLY_CODE_NON_EXISTANT_DOMAIN = 3,
|
||||||
|
DNS_REPLY_CODE_NOT_IMPLEMENTED = 4,
|
||||||
|
DNS_REPLY_CODE_REFUSED = 5,
|
||||||
|
DNS_REPLY_CODE_YXDOMAIN = 6,
|
||||||
|
DNS_REPLY_CODE_YXRRSET = 7,
|
||||||
|
DNS_REPLY_CODE_NXRRSET = 8
|
||||||
|
}dns_reply_code_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OPCODE values used in a DNS header message
|
||||||
|
*/
|
||||||
|
typedef enum dns_opcode_code_t {
|
||||||
|
DNS_OPCODE_QUERY = 0,
|
||||||
|
DNS_OPCODE_IQUERY = 1,
|
||||||
|
DNS_OPCODE_STATUS = 2
|
||||||
|
}dns_opcode_code_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Represents a 12 byte DNS header.
|
||||||
|
* __packed__ is needed to prevent potential unwanted memory alignments
|
||||||
|
*/
|
||||||
|
typedef struct __attribute__((__packed__)) dns_header_t{
|
||||||
|
uint16_t ID; // identification number
|
||||||
|
uint8_t RD : 1; // recursion desired
|
||||||
|
uint8_t TC : 1; // truncated message
|
||||||
|
uint8_t AA : 1; // authoritive answer
|
||||||
|
uint8_t OPCode : 4; // message_type
|
||||||
|
uint8_t QR : 1; // query/response flag
|
||||||
|
uint8_t RCode : 4; // response code
|
||||||
|
uint8_t Z : 3; // its z! reserved
|
||||||
|
uint8_t RA : 1; // recursion available
|
||||||
|
uint16_t QDCount; // number of question entries
|
||||||
|
uint16_t ANCount; // number of answer entries
|
||||||
|
uint16_t NSCount; // number of authority entries
|
||||||
|
uint16_t ARCount; // number of resource entries
|
||||||
|
}dns_header_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum dns_answer_type_t {
|
||||||
|
DNS_ANSWER_TYPE_A = 1,
|
||||||
|
DNS_ANSWER_TYPE_NS = 2,
|
||||||
|
DNS_ANSWER_TYPE_CNAME = 5,
|
||||||
|
DNS_ANSWER_TYPE_SOA = 6,
|
||||||
|
DNS_ANSWER_TYPE_WKS = 11,
|
||||||
|
DNS_ANSWER_TYPE_PTR = 12,
|
||||||
|
DNS_ANSWER_TYPE_MX = 15,
|
||||||
|
DNS_ANSWER_TYPE_SRV = 33,
|
||||||
|
DNS_ANSWER_TYPE_AAAA = 28
|
||||||
|
}dns_answer_type_t;
|
||||||
|
|
||||||
|
typedef enum dns_answer_class_t {
|
||||||
|
DNS_ANSWER_CLASS_IN = 1
|
||||||
|
}dns_answer_class_t;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct __attribute__((__packed__)) dns_answer_t{
|
||||||
|
uint16_t NAME; /* for the sake of simplicity only 16 bit pointers are supported */
|
||||||
|
uint16_t TYPE; /* Unsigned 16 bit value. The resource record types - determines the content of the RDATA field. */
|
||||||
|
uint16_t CLASS; /* Class of response. */
|
||||||
|
uint32_t TTL; /* The time in seconds that the record may be cached. A value of 0 indicates the record should not be cached. */
|
||||||
|
uint16_t RDLENGTH; /* Unsigned 16-bit value that defines the length in bytes of the RDATA record. */
|
||||||
|
uint32_t RDATA; /* For the sake of simplicity only ipv4 is supported, and as such it's a unsigned 32 bit */
|
||||||
|
}dns_answer_t;
|
||||||
|
|
||||||
|
void dns_server(void *pvParameters);
|
||||||
|
void dns_server_start();
|
||||||
|
void dns_server_stop();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* MAIN_DNS_SERVER_H_ */
|
||||||
367
components/wifi-manager/http_server.c
Normal file
@@ -0,0 +1,367 @@
|
|||||||
|
/*
|
||||||
|
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.c
|
||||||
|
@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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "http_server.h"
|
||||||
|
#include "cmd_system.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* @brief tag used for ESP serial console messages */
|
||||||
|
static const char TAG[] = "http_server";
|
||||||
|
static const char json_start[] = "{ \"autoexec\" : %u, \"list\" : [";
|
||||||
|
static const char json_end[] = "]}";
|
||||||
|
static const char template[] = "{ '%s' : '%s' }";
|
||||||
|
static const char array_separator[]=",";
|
||||||
|
|
||||||
|
/* @brief task handle for the http server */
|
||||||
|
static TaskHandle_t task_http_server = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief embedded binary data.
|
||||||
|
* @see file "component.mk"
|
||||||
|
* @see https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html#embedding-binary-data
|
||||||
|
*/
|
||||||
|
extern const uint8_t style_css_start[] asm("_binary_style_css_start");
|
||||||
|
extern const uint8_t style_css_end[] asm("_binary_style_css_end");
|
||||||
|
extern const uint8_t jquery_gz_start[] asm("_binary_jquery_gz_start");
|
||||||
|
extern const uint8_t jquery_gz_end[] asm("_binary_jquery_gz_end");
|
||||||
|
extern const uint8_t code_js_start[] asm("_binary_code_js_start");
|
||||||
|
extern const uint8_t code_js_end[] asm("_binary_code_js_end");
|
||||||
|
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
|
||||||
|
extern const uint8_t index_html_end[] asm("_binary_index_html_end");
|
||||||
|
|
||||||
|
|
||||||
|
/* const http headers stored in ROM */
|
||||||
|
const static char http_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\n\n";
|
||||||
|
const static char http_css_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/css\nCache-Control: public, max-age=31536000\n\n";
|
||||||
|
const static char http_js_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\n\n";
|
||||||
|
const static char http_jquery_gz_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\nAccept-Ranges: bytes\nContent-Length: 29995\nContent-Encoding: gzip\n\n";
|
||||||
|
const static char http_400_hdr[] = "HTTP/1.1 400 Bad Request\nContent-Length: 0\n\n";
|
||||||
|
const static char http_404_hdr[] = "HTTP/1.1 404 Not Found\nContent-Length: 0\n\n";
|
||||||
|
const static char http_503_hdr[] = "HTTP/1.1 503 Service Unavailable\nContent-Length: 0\n\n";
|
||||||
|
const static char http_ok_json_no_cache_hdr[] = "HTTP/1.1 200 OK\nContent-type: application/json\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\n\n";
|
||||||
|
const static char http_redirect_hdr_start[] = "HTTP/1.1 302 Found\nLocation: http://";
|
||||||
|
const static char http_redirect_hdr_end[] = "/\n\n";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void http_server_start(){
|
||||||
|
if(task_http_server == NULL){
|
||||||
|
xTaskCreate(&http_server, "http_server", 1024*3, NULL, WIFI_MANAGER_TASK_PRIORITY-1, &task_http_server);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_server(void *pvParameters) {
|
||||||
|
|
||||||
|
struct netconn *conn, *newconn;
|
||||||
|
err_t err;
|
||||||
|
conn = netconn_new(NETCONN_TCP);
|
||||||
|
netconn_bind(conn, IP_ADDR_ANY, 80);
|
||||||
|
netconn_listen(conn);
|
||||||
|
ESP_LOGI(TAG, "HTTP Server listening on 80/tcp");
|
||||||
|
do {
|
||||||
|
err = netconn_accept(conn, &newconn);
|
||||||
|
if (err == ERR_OK) {
|
||||||
|
http_server_netconn_serve(newconn);
|
||||||
|
netconn_delete(newconn);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG,"Error accepting new connection. Terminating HTTP server");
|
||||||
|
}
|
||||||
|
taskYIELD(); /* allows the freeRTOS scheduler to take over if needed. */
|
||||||
|
} while(err == ERR_OK);
|
||||||
|
|
||||||
|
netconn_close(conn);
|
||||||
|
netconn_delete(conn);
|
||||||
|
|
||||||
|
vTaskDelete( NULL );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
char* http_server_get_header(char *request, char *header_name, int *len) {
|
||||||
|
*len = 0;
|
||||||
|
char *ret = NULL;
|
||||||
|
char *ptr = NULL;
|
||||||
|
|
||||||
|
ptr = strstr(request, header_name);
|
||||||
|
if (ptr) {
|
||||||
|
ret = ptr + strlen(header_name);
|
||||||
|
ptr = ret;
|
||||||
|
while (*ptr != '\0' && *ptr != '\n' && *ptr != '\r') {
|
||||||
|
(*len)++;
|
||||||
|
ptr++;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void http_server_netconn_serve(struct netconn *conn) {
|
||||||
|
|
||||||
|
struct netbuf *inbuf;
|
||||||
|
char *buf = NULL;
|
||||||
|
u16_t buflen;
|
||||||
|
err_t err;
|
||||||
|
const char new_line[2] = "\n";
|
||||||
|
|
||||||
|
err = netconn_recv(conn, &inbuf);
|
||||||
|
if (err == ERR_OK) {
|
||||||
|
|
||||||
|
netbuf_data(inbuf, (void**)&buf, &buflen);
|
||||||
|
|
||||||
|
/* extract the first line of the request */
|
||||||
|
char *save_ptr = buf;
|
||||||
|
char *line = strtok_r(save_ptr, new_line, &save_ptr);
|
||||||
|
ESP_LOGD(TAG,"Processing line %s",line);
|
||||||
|
|
||||||
|
if(line) {
|
||||||
|
|
||||||
|
/* captive portal functionality: redirect to access point IP for HOST that are not the access point IP OR the STA IP */
|
||||||
|
int lenH = 0;
|
||||||
|
char *host = http_server_get_header(save_ptr, "Host: ", &lenH);
|
||||||
|
/* determine if Host is from the STA IP address */
|
||||||
|
wifi_manager_lock_sta_ip_string(portMAX_DELAY);
|
||||||
|
bool access_from_sta_ip = lenH > 0?strstr(host, wifi_manager_get_sta_ip_string()):false;
|
||||||
|
wifi_manager_unlock_sta_ip_string();
|
||||||
|
|
||||||
|
if (lenH > 0 && !strstr(host, DEFAULT_AP_IP) && !access_from_sta_ip) {
|
||||||
|
ESP_LOGI(TAG,"Redirecting to default AP IP Address : %s", DEFAULT_AP_IP);
|
||||||
|
netconn_write(conn, http_redirect_hdr_start, sizeof(http_redirect_hdr_start) - 1, NETCONN_NOCOPY);
|
||||||
|
netconn_write(conn, DEFAULT_AP_IP, sizeof(DEFAULT_AP_IP) - 1, NETCONN_NOCOPY);
|
||||||
|
netconn_write(conn, http_redirect_hdr_end, sizeof(http_redirect_hdr_end) - 1, NETCONN_NOCOPY);
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
/* default page */
|
||||||
|
if(strstr(line, "GET / ")) {
|
||||||
|
netconn_write(conn, http_html_hdr, sizeof(http_html_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
netconn_write(conn, index_html_start, index_html_end - index_html_start, NETCONN_NOCOPY);
|
||||||
|
}
|
||||||
|
else if(strstr(line, "GET /jquery.js ")) {
|
||||||
|
netconn_write(conn, http_jquery_gz_hdr, sizeof(http_jquery_gz_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
netconn_write(conn, jquery_gz_start, jquery_gz_end - jquery_gz_start, NETCONN_NOCOPY);
|
||||||
|
}
|
||||||
|
else if(strstr(line, "GET /code.js ")) {
|
||||||
|
netconn_write(conn, http_js_hdr, sizeof(http_js_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
netconn_write(conn, code_js_start, code_js_end - code_js_start, NETCONN_NOCOPY);
|
||||||
|
}
|
||||||
|
else if(strstr(line, "GET /ap.json ")) {
|
||||||
|
/* if we can get the mutex, write the last version of the AP list */
|
||||||
|
ESP_LOGI(TAG,"Processing ap.json request");
|
||||||
|
if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
|
||||||
|
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
char *buff = wifi_manager_get_ap_list_json();
|
||||||
|
netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
|
||||||
|
wifi_manager_unlock_json_buffer();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
ESP_LOGE(TAG, "http_server_netconn_serve: GET /ap.json failed to obtain mutex");
|
||||||
|
}
|
||||||
|
/* request a wifi scan */
|
||||||
|
ESP_LOGI(TAG,"Starting wifi scan");
|
||||||
|
wifi_manager_scan_async();
|
||||||
|
}
|
||||||
|
else if(strstr(line, "GET /style.css ")) {
|
||||||
|
netconn_write(conn, http_css_hdr, sizeof(http_css_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
netconn_write(conn, style_css_start, style_css_end - style_css_start, NETCONN_NOCOPY);
|
||||||
|
}
|
||||||
|
else if(strstr(line, "GET /status.json ")){
|
||||||
|
ESP_LOGI(TAG,"Serving status.json");
|
||||||
|
if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
|
||||||
|
char *buff = wifi_manager_get_ip_info_json();
|
||||||
|
if(buff){
|
||||||
|
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
|
||||||
|
|
||||||
|
wifi_manager_unlock_json_buffer();
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
ESP_LOGE(TAG, "http_server_netconn_serve: GET /status failed to obtain mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(strstr(line, "GET /config.json ")){
|
||||||
|
ESP_LOGI(TAG,"Serving config.json");
|
||||||
|
char autoexec_name[21]={0};
|
||||||
|
char * autoexec_value=NULL;
|
||||||
|
char * autoexec_flag_s=NULL;
|
||||||
|
uint8_t autoexec_flag=0;
|
||||||
|
int buflen=MAX_COMMAND_LINE_SIZE+strlen(template)+1;
|
||||||
|
char * buff = malloc(buflen);
|
||||||
|
if(!buff)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG,"Unable to allocate buffer for config.json!");
|
||||||
|
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int i=1;
|
||||||
|
size_t l = 0;
|
||||||
|
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
|
||||||
|
autoexec_flag = wifi_manager_get_flag();
|
||||||
|
snprintf(buff,buflen-1, json_start, autoexec_flag);
|
||||||
|
netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
|
||||||
|
do {
|
||||||
|
snprintf(autoexec_name,sizeof(autoexec_name)-1,"autoexec%u",i);
|
||||||
|
ESP_LOGD(TAG,"Getting command name %s", autoexec_name);
|
||||||
|
autoexec_value= wifi_manager_alloc_get_config(autoexec_name, &l);
|
||||||
|
if(autoexec_value!=NULL ){
|
||||||
|
if(i>1)
|
||||||
|
{
|
||||||
|
netconn_write(conn, array_separator, strlen(array_separator), NETCONN_NOCOPY);
|
||||||
|
ESP_LOGD(TAG,"%s", array_separator);
|
||||||
|
}
|
||||||
|
ESP_LOGI(TAG,"found command %s = %s", autoexec_name, autoexec_value);
|
||||||
|
snprintf(buff,buflen-1,template, autoexec_name,autoexec_value);
|
||||||
|
netconn_write(conn, buff, strlen(buff), NETCONN_NOCOPY);
|
||||||
|
ESP_LOGD(TAG,"%s", buff);
|
||||||
|
ESP_LOGD(TAG,"Freeing memory for command %s name", autoexec_name);
|
||||||
|
free(autoexec_value);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} while(1);
|
||||||
|
free(buff);
|
||||||
|
netconn_write(conn, json_end, strlen(json_end), NETCONN_NOCOPY);
|
||||||
|
ESP_LOGD(TAG,"%s", json_end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(strstr(line, "POST /factory.json ")){
|
||||||
|
guided_factory();
|
||||||
|
}
|
||||||
|
else if(strstr(line, "POST /config.json ")){
|
||||||
|
ESP_LOGI(TAG,"Serving POST config.json");
|
||||||
|
|
||||||
|
if(wifi_manager_lock_json_buffer(( TickType_t ) 10)){
|
||||||
|
int i=1;
|
||||||
|
int lenS = 0, lenA=0;
|
||||||
|
char autoexec_name[21]={0};
|
||||||
|
char * autoexec_value=NULL;
|
||||||
|
char * autoexec_flag_s=NULL;
|
||||||
|
uint8_t autoexec_flag=0;
|
||||||
|
autoexec_flag_s = http_server_get_header(save_ptr, "X-Custom-autoexec: ", &lenA);
|
||||||
|
if(autoexec_flag_s!=NULL && lenA > 0)
|
||||||
|
{
|
||||||
|
autoexec_flag = atoi(autoexec_flag_s);
|
||||||
|
wifi_manager_save_autoexec_flag(autoexec_flag);
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
snprintf(autoexec_name,sizeof(autoexec_name)-1,"X-Custom-autoexec%u:",i++);
|
||||||
|
ESP_LOGD(TAG,"Looking for command name %s", autoexec_name);
|
||||||
|
autoexec_value = http_server_get_header(save_ptr, autoexec_name, &lenS);
|
||||||
|
|
||||||
|
if(autoexec_value ){
|
||||||
|
if(lenS < MAX_COMMAND_LINE_SIZE ){
|
||||||
|
ESP_LOGD(TAG, "http_server_netconn_serve: config.json/ call, with %s: %s", autoexec_name, autoexec_value);
|
||||||
|
wifi_manager_save_autoexec_config(autoexec_value,autoexec_name,lenS);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG,"command line length is too long : %s = %s", autoexec_name, autoexec_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ESP_LOGD(TAG,"No matching command found for name %s", autoexec_name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while(1);
|
||||||
|
|
||||||
|
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
netconn_write(conn, http_503_hdr, sizeof(http_503_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
ESP_LOGE(TAG, "http_server_netconn_serve: GET /status failed to obtain mutex");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if(strstr(line, "DELETE /connect.json ")) {
|
||||||
|
ESP_LOGI(TAG, "http_server_netconn_serve: DELETE /connect.json");
|
||||||
|
/* request a disconnection from wifi and forget about it */
|
||||||
|
wifi_manager_disconnect_async();
|
||||||
|
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); /* 200 ok */
|
||||||
|
}
|
||||||
|
else if(strstr(line, "POST /connect.json ")) {
|
||||||
|
ESP_LOGI(TAG, "http_server_netconn_serve: POST /connect.json");
|
||||||
|
bool found = false;
|
||||||
|
int lenS = 0, lenP = 0;
|
||||||
|
char *ssid = NULL, *password = NULL;
|
||||||
|
ssid = http_server_get_header(save_ptr, "X-Custom-ssid: ", &lenS);
|
||||||
|
password = http_server_get_header(save_ptr, "X-Custom-pwd: ", &lenP);
|
||||||
|
|
||||||
|
if(ssid && lenS <= MAX_SSID_SIZE && password && lenP <= MAX_PASSWORD_SIZE){
|
||||||
|
wifi_config_t* config = wifi_manager_get_wifi_sta_config();
|
||||||
|
memset(config, 0x00, sizeof(wifi_config_t));
|
||||||
|
memcpy(config->sta.ssid, ssid, lenS);
|
||||||
|
memcpy(config->sta.password, password, lenP);
|
||||||
|
ESP_LOGD(TAG, "http_server_netconn_serve: wifi_manager_connect_async() call, with ssid: %s, password: %s", ssid, password);
|
||||||
|
wifi_manager_connect_async();
|
||||||
|
netconn_write(conn, http_ok_json_no_cache_hdr, sizeof(http_ok_json_no_cache_hdr) - 1, NETCONN_NOCOPY); //200ok
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found){
|
||||||
|
/* bad request the authentification header is not complete/not the correct format */
|
||||||
|
netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
ESP_LOGE(TAG, "bad request the authentification header is not complete/not the correct format");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
netconn_write(conn, http_400_hdr, sizeof(http_400_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
ESP_LOGE(TAG, "bad request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
ESP_LOGE(TAG, "URL Not found. Sending 404.");
|
||||||
|
netconn_write(conn, http_404_hdr, sizeof(http_404_hdr) - 1, NETCONN_NOCOPY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* free the buffer */
|
||||||
|
netbuf_delete(inbuf);
|
||||||
|
}
|
||||||
95
components/wifi-manager/http_server.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/*
|
||||||
|
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 "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"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RTOS task for the HTTP server. Do not start manually.
|
||||||
|
* @see void http_server_start()
|
||||||
|
*/
|
||||||
|
void http_server(void *pvParameters);
|
||||||
|
|
||||||
|
/* @brief helper function that processes one HTTP request at a time */
|
||||||
|
void http_server_netconn_serve(struct netconn *conn);
|
||||||
|
|
||||||
|
/* @brief create the task for the http server */
|
||||||
|
void 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* http_server_get_header(char *request, char *header_name, int *len);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
310
components/wifi-manager/index.html
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<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" />
|
||||||
|
<script src="/jquery.js"></script>
|
||||||
|
<link rel="stylesheet" href="/style.css">
|
||||||
|
<script src="/code.js"></script>
|
||||||
|
<title>esp32-wifi-manager</title>
|
||||||
|
</head>
|
||||||
|
<script>
|
||||||
|
var ws, sel, host, old, once = 0, jso, m;
|
||||||
|
var to = 0, set_int = 0;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function get_radio(name)
|
||||||
|
{
|
||||||
|
var s = document.getElementsByName(name), sel;
|
||||||
|
for ( var i = 0; i < s.length; i++)
|
||||||
|
if (s[i].checked) {
|
||||||
|
sel = s[i].value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sel;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_radio_index(name)
|
||||||
|
{
|
||||||
|
var s = document.getElementsByName(name), i;
|
||||||
|
|
||||||
|
for (i = 0; i < s.length; i++)
|
||||||
|
if (s[i].checked)
|
||||||
|
return i;
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function do_reset()
|
||||||
|
{
|
||||||
|
var s = "{\"reset\":\"1\"}";
|
||||||
|
try {
|
||||||
|
ws.send(s);
|
||||||
|
} catch(exception) {
|
||||||
|
alert('Sorry, there was a problem' + exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.close();
|
||||||
|
alert("Rebooting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
function file_change()
|
||||||
|
{
|
||||||
|
document.getElementById('update').disabled = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function do_upload(f)
|
||||||
|
{
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
document.getElementById('update').disabled = 1;
|
||||||
|
//ws.close();
|
||||||
|
document.getElementById("progr").class = "progr-ok";
|
||||||
|
|
||||||
|
xhr.upload.addEventListener("progress", function(e) {
|
||||||
|
document.getElementById("progr").value = parseInt(e.loaded / e.total * 100);
|
||||||
|
|
||||||
|
if (e.loaded == e.total) {
|
||||||
|
// document.getElementById("realpage").style.display = "none";
|
||||||
|
// document.getElementById("waiting").style.display = "block";
|
||||||
|
}
|
||||||
|
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function(e) {
|
||||||
|
console.log("rs" + xhr.readyState + " status " + xhr.status);
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
/* it completed, for good or for ill */
|
||||||
|
// document.getElementById("realpage").style.display = "none";
|
||||||
|
// document.getElementById("waiting").style.display = "block";
|
||||||
|
document.getElementById("progr").class = "progr-ok";
|
||||||
|
console.log("upload reached state 4: xhr status " + xhr.status);
|
||||||
|
setTimeout(function() { window.location.href = location.origin + "/"; }, 9000 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/* kill the heart timer */
|
||||||
|
clearInterval(set_int);
|
||||||
|
|
||||||
|
xhr.open("POST", f.action, true);
|
||||||
|
xhr.send(new FormData(f));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function do_settings(f)
|
||||||
|
{
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
|
xhr.onreadystatechange = function(e) {
|
||||||
|
console.log("do_settings" + xhr.readyState + " status " + xhr.status);
|
||||||
|
if (xhr.readyState == 4) {
|
||||||
|
document.getElementById("updsettings").style.opacity = "1.0";
|
||||||
|
document.getElementById("updsettings").disabled = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
xhr.open("POST", f.action, true);
|
||||||
|
document.getElementById("updsettings").style.opacity = "0.3";
|
||||||
|
document.getElementById("updsettings").disabled = 1;
|
||||||
|
xhr.send(new FormData(f));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_latest(n)
|
||||||
|
{
|
||||||
|
if (n == 0)
|
||||||
|
ws.send("update-ota");
|
||||||
|
else
|
||||||
|
ws.send("update-factory");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function heart_timer() {
|
||||||
|
var s;
|
||||||
|
|
||||||
|
s = Math.round((95 * to) / (40 * 10)) / 100;
|
||||||
|
|
||||||
|
if (s < 0) {
|
||||||
|
clearInterval(set_int);
|
||||||
|
set_int = 0;
|
||||||
|
|
||||||
|
ws.close();
|
||||||
|
|
||||||
|
document.getElementById("realpage").style.opacity = "0.3";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
to--;
|
||||||
|
document.getElementById("heart").style.opacity = s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function heartbeat()
|
||||||
|
{
|
||||||
|
to = 40 * 10;
|
||||||
|
if (!set_int) {
|
||||||
|
set_int = setInterval(heart_timer, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<div id="app-wrap">
|
||||||
|
<div id="wifi">
|
||||||
|
<header>
|
||||||
|
<h1>Wi-Fi</h1>
|
||||||
|
</header>
|
||||||
|
<div id="wifi-status">
|
||||||
|
<h2>Connected to:</h2>
|
||||||
|
<section id="connected-to">
|
||||||
|
<div class="ape"><div class="w0"><div class="pw"><span></span></div></div></div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<h2>Manual connect</h2>
|
||||||
|
<section id="manual_add">
|
||||||
|
<div class="ape">ADD (HIDDEN) SSID<div>
|
||||||
|
</section>
|
||||||
|
<h2>or choose a network...</h2>
|
||||||
|
<section id="wifi-list">
|
||||||
|
</section>
|
||||||
|
<div id="pwrdby"><em>Powered by </em><a id="acredits" href="#"><strong>esp32-wifi-manager</strong></a>.</div>
|
||||||
|
</div>
|
||||||
|
<div id="command_line">
|
||||||
|
<header>
|
||||||
|
<h1>Startup command</h1>
|
||||||
|
</header>
|
||||||
|
<h2>Squeezelite</span></h2>
|
||||||
|
|
||||||
|
<div id="autoexec1_current" ></div>
|
||||||
|
<section id="command-list">
|
||||||
|
<input id="autoexec1" type="text" maxlength="201" placeholder="squeezelite -o I2S -b 500:2000 -d all=info" value="">
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="update_command" type="button" value="Update" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ota">
|
||||||
|
<header><h1>Application</h1></header>
|
||||||
|
<form name="multipart" action="otaform" method="post" enctype="multipart/form-data" onsubmit="do_upload(this); return false;">
|
||||||
|
<progress id="progr" value="0" max="100" >Upload Progress</progress>
|
||||||
|
<input type="file" name="ota" id="ota" size="20" accept=".bin" onchange="file_change();" style="font-size: 12pt">
|
||||||
|
<span id="file_info" style="font-size:12pt;"></span>
|
||||||
|
<input type="submit" id="update" disabled="" value="upload">
|
||||||
|
<input type="submit" id="factory" disabled="" value="factory">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div id="connect_manual">
|
||||||
|
<header>
|
||||||
|
<h1>Enter Details</h1>
|
||||||
|
</header>
|
||||||
|
<h2>Manual Connection</span></h2>
|
||||||
|
<section>
|
||||||
|
<input id="manual_ssid" type="text" placeholder="SSID" value="">
|
||||||
|
<input id="manual_pwd" type="password" placeholder="Password" value="">
|
||||||
|
</section>
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="manual_join" type="button" value="Join" data-connect="manual" />
|
||||||
|
<input id="manual_cancel" type="button" value="Cancel"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="connect">
|
||||||
|
<header>
|
||||||
|
<h1>Enter Password</h1>
|
||||||
|
</header>
|
||||||
|
<h2>Password for <span id="ssid-pwd"></span></h2>
|
||||||
|
<section>
|
||||||
|
<input id="pwd" type="password" placeholder="Password" value="">
|
||||||
|
</section>
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="join" type="button" value="Join" />
|
||||||
|
<input id="cancel" type="button" value="Cancel"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="connect-wait">
|
||||||
|
<header>
|
||||||
|
<h1>Please wait...</h1>
|
||||||
|
</header>
|
||||||
|
<h2>Connecting to <span id="ssid-wait"></span></h2>
|
||||||
|
<section>
|
||||||
|
<div id="loading">
|
||||||
|
<div class="spinner"><div class="double-bounce1"></div><div class="double-bounce2"></div></div>
|
||||||
|
<p class="tctr">You may lose wifi access while the esp32 recalibrates its radio. Please wait until your device automatically reconnects. This can take up to 30s.</p>
|
||||||
|
</div>
|
||||||
|
<div id="connect-success">
|
||||||
|
<h3 class="gr">Success!</h3>
|
||||||
|
</div>
|
||||||
|
<div id="connect-fail">
|
||||||
|
<h3 class="rd">Connection failed</h3>
|
||||||
|
<p class="tctr">Please double-check wifi password if any and make sure the access point has good signal.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="ok-connect" type="button" value="OK" class="ctr" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="connect-details">
|
||||||
|
<div id="connect-details-wrap">
|
||||||
|
<header>
|
||||||
|
<h1></h1>
|
||||||
|
</header>
|
||||||
|
<h2></h2>
|
||||||
|
<section>
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="disconnect" type="button" value="Disconnect" class="ctr"/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<h2>IP Address</h2>
|
||||||
|
<section>
|
||||||
|
<div class="ape brdb">IP Address:<div id="ip" class="fr"></div></div>
|
||||||
|
<div class="ape brdb">Subnet Mask:<div id="netmask" class="fr"></div></div>
|
||||||
|
<div class="ape">Default Gateway:<div id="gw" class="fr"></div></div>
|
||||||
|
</section>
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="ok-details" type="button" value="OK" class="ctr" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="diag-disconnect" class="diag-box">
|
||||||
|
<div class="diag-box-win">
|
||||||
|
<p>Are you sure you would like to disconnect from this wifi?</p>
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="no-disconnect" type="button" value="No" />
|
||||||
|
<input id="yes-disconnect" type="button" value="Yes" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="credits">
|
||||||
|
<header>
|
||||||
|
<h1>About this app...</h1>
|
||||||
|
</header>
|
||||||
|
<h2></h2>
|
||||||
|
<section>
|
||||||
|
<p><strong>esp32-wifi-manager</strong>, © 2017-2019, Tony Pottier<br />Licender under the MIT License.</p>
|
||||||
|
<p>
|
||||||
|
This app would not be possible without the following libraries:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>SpinKit, © 2015, Tobias Ahlin. Licensed under the MIT License.</li>
|
||||||
|
<li>jQuery, The jQuery Foundation. Licensed under the MIT License.</li>
|
||||||
|
<li>cJSON, © 2009-2017, Dave Gamble and cJSON contributors. Licensed under the MIT License.</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<div class="buttons">
|
||||||
|
<input id="ok-credits" type="button" value="OK" class="ctr" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<html>
|
||||||
BIN
components/wifi-manager/jquery.gz
Normal file
4
components/wifi-manager/jquery.js
vendored
Normal file
144
components/wifi-manager/json.c
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
/*
|
||||||
|
@file json.c
|
||||||
|
@brief handles very basic JSON with a minimal footprint on the system
|
||||||
|
|
||||||
|
This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license:
|
||||||
|
Copyright (c) 2009 Dave Gamble
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
@see https://github.com/DaveGamble/cJSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "json.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
|
||||||
|
bool json_print_string(const unsigned char *input, unsigned char *output_buffer)
|
||||||
|
{
|
||||||
|
const unsigned char *input_pointer = NULL;
|
||||||
|
unsigned char *output = NULL;
|
||||||
|
unsigned char *output_pointer = NULL;
|
||||||
|
size_t output_length = 0;
|
||||||
|
/* numbers of additional characters needed for escaping */
|
||||||
|
size_t escape_characters = 0;
|
||||||
|
|
||||||
|
if (output_buffer == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* empty string */
|
||||||
|
if (input == NULL)
|
||||||
|
{
|
||||||
|
//output = ensure(output_buffer, sizeof("\"\""), hooks);
|
||||||
|
if (output == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
strcpy((char*)output, "\"\"");
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set "flag" to 1 if something needs to be escaped */
|
||||||
|
for (input_pointer = input; *input_pointer; input_pointer++)
|
||||||
|
{
|
||||||
|
if (strchr("\"\\\b\f\n\r\t", *input_pointer))
|
||||||
|
{
|
||||||
|
/* one character escape sequence */
|
||||||
|
escape_characters++;
|
||||||
|
}
|
||||||
|
else if (*input_pointer < 32)
|
||||||
|
{
|
||||||
|
/* UTF-16 escape sequence uXXXX */
|
||||||
|
escape_characters += 5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output_length = (size_t)(input_pointer - input) + escape_characters;
|
||||||
|
|
||||||
|
/* in the original cJSON it is possible to realloc here in case output buffer is too small.
|
||||||
|
* This is overkill for an embedded system. */
|
||||||
|
output = output_buffer;
|
||||||
|
|
||||||
|
/* no characters have to be escaped */
|
||||||
|
if (escape_characters == 0)
|
||||||
|
{
|
||||||
|
output[0] = '\"';
|
||||||
|
memcpy(output + 1, input, output_length);
|
||||||
|
output[output_length + 1] = '\"';
|
||||||
|
output[output_length + 2] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
output[0] = '\"';
|
||||||
|
output_pointer = output + 1;
|
||||||
|
/* copy the string */
|
||||||
|
for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++)
|
||||||
|
{
|
||||||
|
if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\'))
|
||||||
|
{
|
||||||
|
/* normal character, copy */
|
||||||
|
*output_pointer = *input_pointer;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* character needs to be escaped */
|
||||||
|
*output_pointer++ = '\\';
|
||||||
|
switch (*input_pointer)
|
||||||
|
{
|
||||||
|
case '\\':
|
||||||
|
*output_pointer = '\\';
|
||||||
|
break;
|
||||||
|
case '\"':
|
||||||
|
*output_pointer = '\"';
|
||||||
|
break;
|
||||||
|
case '\b':
|
||||||
|
*output_pointer = 'b';
|
||||||
|
break;
|
||||||
|
case '\f':
|
||||||
|
*output_pointer = 'f';
|
||||||
|
break;
|
||||||
|
case '\n':
|
||||||
|
*output_pointer = 'n';
|
||||||
|
break;
|
||||||
|
case '\r':
|
||||||
|
*output_pointer = 'r';
|
||||||
|
break;
|
||||||
|
case '\t':
|
||||||
|
*output_pointer = 't';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* escape and print as unicode codepoint */
|
||||||
|
sprintf((char*)output_pointer, "u%04x", *input_pointer);
|
||||||
|
output_pointer += 4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output[output_length + 1] = '\"';
|
||||||
|
output[output_length + 2] = '\0';
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
47
components/wifi-manager/json.h
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
@file json.h
|
||||||
|
@brief handles very basic JSON with a minimal footprint on the system
|
||||||
|
|
||||||
|
This code is a lightly modified version of cJSON 1.4.7. cJSON is licensed under the MIT license:
|
||||||
|
Copyright (c) 2009 Dave Gamble
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
@see https://github.com/DaveGamble/cJSON
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef JSON_H_INCLUDED
|
||||||
|
#define JSON_H_INCLUDED
|
||||||
|
#include <stdbool.h>
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Render the cstring provided to a JSON escaped version that can be printed.
|
||||||
|
* @param input the input buffer to be escaped.
|
||||||
|
* @param output_buffer the output buffer to write to. You must ensure it is big enough to contain the final string.
|
||||||
|
* @see cJSON equivlaent static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer)
|
||||||
|
*/
|
||||||
|
bool json_print_string(const unsigned char *input, unsigned char *output_buffer);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* JSON_H_INCLUDED */
|
||||||
BIN
components/wifi-manager/lock.png
Normal file
|
After Width: | Height: | Size: 433 B |
29
components/wifi-manager/main.c.txt
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
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 main.c
|
||||||
|
@author Tony Pottier
|
||||||
|
@brief Entry point for the ESP32 application.
|
||||||
|
@see https://idyl.io
|
||||||
|
@see https://github.com/tonyp7/esp32-wifi-manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
BIN
components/wifi-manager/settings.png
Normal file
|
After Width: | Height: | Size: 901 B |
1
components/wifi-manager/status
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"ssid":"zodmgbbq","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0}
|
||||||
250
components/wifi-manager/style.css
Normal file
@@ -0,0 +1,250 @@
|
|||||||
|
body {
|
||||||
|
background-color: #eee;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
font: 1.1em tahoma, arial, sans-serif;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: darkblue;
|
||||||
|
transition: color .2s ease-out;
|
||||||
|
text-decoration: none
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
display: none;
|
||||||
|
font: 1.1em tahoma, arial, sans-serif;
|
||||||
|
}
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus,
|
||||||
|
button:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
input[type="button"] {
|
||||||
|
width: 100px;
|
||||||
|
padding: 5px;
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
#credits {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#app {} #app-wrap {} #disconnect {
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
.diag-box {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.diag-box-win {
|
||||||
|
position: absolute;
|
||||||
|
left: 10%;
|
||||||
|
width: 80%;
|
||||||
|
text-align: center;
|
||||||
|
border: 2px outset #888;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
top: 20%;
|
||||||
|
}
|
||||||
|
.blur {
|
||||||
|
-webkit-filter: blur(2px);
|
||||||
|
-moz-filter: blur(2px);
|
||||||
|
-ms-filter: blur(2px);
|
||||||
|
-o-filter: blur(2px);
|
||||||
|
filter: blur(2px);
|
||||||
|
}
|
||||||
|
.ape {
|
||||||
|
margin-left: 20px;
|
||||||
|
padding: 10px 0px 10px 10px;
|
||||||
|
}
|
||||||
|
.ape:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.brdb {
|
||||||
|
border-bottom: 1px solid #888;
|
||||||
|
}
|
||||||
|
header {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #888;
|
||||||
|
}
|
||||||
|
section {
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #888;
|
||||||
|
border-top: 1px solid #888;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 1.4em
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #888;
|
||||||
|
font-size: 1.0em
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px 0px 20px 0px;
|
||||||
|
}
|
||||||
|
.gr {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
.rd {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
#wifi-status {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#connect {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#connect_manual {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#manual_ssid {
|
||||||
|
border: none;
|
||||||
|
width: 80%;
|
||||||
|
margin-left: 35px;
|
||||||
|
padding: 10px 0px 10px 10px;
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
#manual_pwd {
|
||||||
|
border: none;
|
||||||
|
width: 80%;
|
||||||
|
margin-left: 35px;
|
||||||
|
padding: 10px 0px 10px 10px;
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
#pwd {
|
||||||
|
border: none;
|
||||||
|
width: 80%;
|
||||||
|
margin-left: 35px;
|
||||||
|
padding: 10px 0px 10px 10px;
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
#join {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
#manual_join {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
#yes-disconnect {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
#no-disconnect {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.ctr {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.tctr {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#connect-wait {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#connect-success {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#connect-fail {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#connect-details {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.fr {
|
||||||
|
float: right;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.w0 {
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTJDBGvsAAABzUlEQVRIS+WUTShEURTH3zyRhjQ+8hWxmCJMoSzEwsbCgi1LZRYW9pONptiwka9iI81CWFpYaEqNMkVKmpWN1IhYKN9ZDL/z3p3mxZh5g9X4168799xz/vPefedeLeuVC+3gdTgc07CsmCQ2DI2gg21Jci30wSpGt/CeghickTsHPVACDkgqp67rPgpO4E0ZZMIj7OHhxSvPtEyomcVDeFXJv+EZNvEsNa01rZfAuSUhThR2wU+ObJkbyhRNMMDaDIThBqy1MdZ3wAPawqfFC2Lj0Ab5kpBGxdAJs9TeW72ITUhCPZMjFYwwbwXpnkwlDzOIx50yXwP5c0MeggHGanNqSDqqBqQ7/Kxvg2zHAfMN8IE8uZhYO6eBnBXGKnOakLWfaQZ9jMRjSPXhZUuC5A9JjVFpKkeNSVVA0Tq8KJN0yFl4gilqbW2tm+SQKoybXIG8jcT34RSsh1Byt6iVg2ZLlRCg6JpROqEDpFheXZ5S9rcLFsl5YJwHad+MVA5y13w5lRY5oRsKjdm/Vz/7LR86zG+5wr+9NX+iOowjEO+aELEic+lv1ILppeUPosRst6QduTANgnE2mC+BnYswI1VwfYzCCL9dZij7pWkf6UeSTYAuE/QAAAAASUVORK5CYII=') no-repeat right top;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.w1 {
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEQAACxEBf2RfkQAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xNkRpr/UAAAHiSURBVEhL5dRPKINxHMfxPVskpA35F3FYEVasHBYHFwcHrhyVZ8vBfblIceEi/4qLtINwdHCQUpQVKcnJRYqIg/J3OYz399nv0YPNtuzEt149+31/v+/n4fGYLVHpup4Rnyregd+K27TIghe63+8fx7wySqsPdbAj3qzha0MOV6ETiwTd4u0HUZxydgrtKISGj0xreG4gEAgycIRXFZCOR2yTQZSebeaa4Q1s7iOiDv/GM1bJLDJv0EHjzHLAdIFNjHBGHpkbxUo9utmbQBg3sM5G2d+AR24w82XznN4QmpGjXrCExRkXfJhk9t6aRW9YDtSwOFDNE9ZNyFLzKRczOegh406FL8ElG8JDM8S1Qtaq7KhEO0Y0TVtHGHusVxCEDy5oMLNqyVrgWm5kqaYw3mdVdmqQsENE8JbAPbY43yszMqiyHOr66QayL5XH0DJeVEgyUTxhjNmPR/vtBpZyc3hHDZohV5DfRvq7OMYtrDdZY7YwFpG8yhBi6JrrMFogww7IT1mOVsxy5oHrNIqRVpWgDtnGKn7log35xurfVxfPW/7QYT57Ybz7mapqgk9gvjU79ApiW5mpRkIvLTe4oJfyK5lKOQndgvG/wXoOSb8I061Svj4G0M9nZ6z198tmeweYtIrMYP17VAAAAABJRU5ErkJggg==') no-repeat right top;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.w2 {
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAALEQAACxEBf2RfkQAAABl0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMC4xNkRpr/UAAAHkSURBVEhL3dRLKERRGMDxuSPSkLzyilgo8iiUhVjYWFiwZancmSzsZSPFho28io1kISwtLKQURZGSrGykiFgo7yyG/zdzznRm5iK5Sk79uvd85/u++5hzx2Pb9q9yDLrJMWhIRB1sv98/ghlliFAXyuGFU21IbECSi9CKORrd4O0TQZyQO45mZMJCpKfZ3BcIBPooOMSravAdD9ikB63sJN1XN69kcQ8vKvknnrBMzyx9gRYCp0aCdo51DJIjr6wU2UoF2lkbxS6uYdYGWV9DtVxgMmbxjFg/apEM/ZQfyUADxqi9M3sRG5CEEib7KnjMvAaye2IbfUVupoMet6r5PDL0YjXBBY4Fai5kRxVCdscg66uQ17HDfAl9kDuXJzB3Thk5sxzzZa6DumHknN3QS+IBPvvh5ZVskN8ZU5+gz3XAlELRIp5Vk6/It/CIYWrjXm3URCkleUsV6iaXkKeR+DaOYH6EkrtCrXxoUf2iJoY8LFB0xXEA9ZBieXS5S3m/jZgi557jBGT7xvWKCxhyIP81ka/SgQ9NSDViURyDbvpTo82yrAPscl4HKxR1aRTT+BhvyhaxtPCSO6OKphfGBc6JZYaX3BnpNN1AUC7AfBrJoRUXR67X6+1BN+fp4dD/Hx7PO4o9VGuAapKIAAAAAElFTkSuQmCC') no-repeat right top;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.w3 {
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTZEaa/1AAACAElEQVRIS7XUP0gbYRjH8VSpiBZJWvEflXYQKtqACg6ig4uDg651LPQSHLpLlyDoUhdpVdBFxEG0YwcHCQgRFJSCFKcuUohY6iC0VYtD9PuE9w3vXZ74h16HD3fv733e53KX9y7ied5/pYZhUkPHQ3TBSyQS7zFvTBC9RivKoK3NCwZS3IxBLNLoBFc3yOEbtR/Qj8d4gEJPt3lVMpkcY8E+Lk2D+/iDTXrQyquwfW3zdiZ38dcU/4tzrNHzib3AAMGhU2BlsYFxauSRtaDWaMMwc1PYwU+4a3PMryMuF5gJTH4ne4dOVMLeZSkx9GCatb/cXmQpKXjOYM+EB4w7ILsn2Og28mNe0ePUNF9CzE7GCZc5NpmxkB31FLI7xpn/DHkc24xXMQb55XIH7s55Qc0Cx0YZ29A2LJyzG95S+AU3/fHySNLUjwTWl9tzG7iqWbSCC9PkNvIunGGStUWP1jcwWijOmIW2yTHkbiTfwle4L6HUfmKtvGi+fr6BowHLLPrBMYVuyGK5dfmV8nx7MUvNb44fIdu3qFdR4KiDfGsKb6WiCn145GQ+ahgmNQyTGpYwxPOWP3qHc/mE+76apaih4hmND2B3TYasJlCjUkPFS5oeORfIkhVtSY0aKqI0TSP/bjCew10+hPf6D+r5fIziDefRwFxJahgmNQyPF7kGEsc1es+A2E4AAAAASUVORK5CYII=') no-repeat right top;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.pw {
|
||||||
|
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMTJDBGvsAAABIUlEQVRIS+3VsU7CUBTGcYhBJCwqwcmEJ2DkCQgzb8ADmLgYWXTzMVjcGNjYGEAXgoSRhTg5OroYIyFY/h+hWGwvtzQ0LpzkF8i5l/uRQ2kTjuPEKrC5T79vzHWJO4wxwzeGuMY5AitsQBFvmEObvNQboQBfhQk4gQ5wD+zgBrcYrHrSwzE2KkxAHVrQWB6QgiqJLB7xA+2pYaNsAWm8QAsa0Sn+1gU+oT1NHGFdtoAcJtBCSw1DuaPqQiNdly0gj1doQaMwleavPc+IJUDffKeADO7Rxxe08A4dEOQD2qPXJ1xh+VuYAirQVaNGFFPov2MM0OXm/UAUZRwCtjoEWP1vQBXuLTgKPYRKMAacoY0oIboDNLB8+PgC4hLY3B8nsQCQEf56jLJoQAAAAABJRU5ErkJggg==') no-repeat right top;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 20px;
|
||||||
|
height: 24px;
|
||||||
|
margin-right: 30px;
|
||||||
|
}
|
||||||
|
/* SpinKit is licensed under the MIT License. Copyright (c) 2015 Tobias Ahlin */
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
position: relative;
|
||||||
|
margin: 100px auto;
|
||||||
|
}
|
||||||
|
.double-bounce1,
|
||||||
|
.double-bounce2 {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #333;
|
||||||
|
opacity: 0.6;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
-webkit-animation: sk-bounce 2.0s infinite ease-in-out;
|
||||||
|
animation: sk-bounce 2.0s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
.double-bounce2 {
|
||||||
|
-webkit-animation-delay: -1.0s;
|
||||||
|
animation-delay: -1.0s;
|
||||||
|
}
|
||||||
|
@-webkit-keyframes sk-bounce {
|
||||||
|
0%, 100% {
|
||||||
|
-webkit-transform: scale(0.0)
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
-webkit-transform: scale(1.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@keyframes sk-bounce {
|
||||||
|
0%, 100% {
|
||||||
|
transform: scale(0.0);
|
||||||
|
-webkit-transform: scale(0.0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.0);
|
||||||
|
-webkit-transform: scale(1.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/* end of SpinKit */
|
||||||
BIN
components/wifi-manager/wifi0.png
Normal file
|
After Width: | Height: | Size: 605 B |
BIN
components/wifi-manager/wifi1.png
Normal file
|
After Width: | Height: | Size: 613 B |
BIN
components/wifi-manager/wifi2.png
Normal file
|
After Width: | Height: | Size: 615 B |
BIN
components/wifi-manager/wifi24.png
Normal file
|
After Width: | Height: | Size: 605 B |
BIN
components/wifi-manager/wifi3.png
Normal file
|
After Width: | Height: | Size: 656 B |
1138
components/wifi-manager/wifi_manager.c
Normal file
397
components/wifi-manager/wifi_manager.h
Normal file
@@ -0,0 +1,397 @@
|
|||||||
|
/*
|
||||||
|
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 wifi_manager.h
|
||||||
|
@author Tony Pottier
|
||||||
|
@brief Defines all functions necessary for esp32 to connect to a wifi/scan wifis
|
||||||
|
|
||||||
|
Contains the freeRTOS task and all necessary support
|
||||||
|
|
||||||
|
@see https://idyl.io
|
||||||
|
@see https://github.com/tonyp7/esp32-wifi-manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef WIFI_MANAGER_H_INCLUDED
|
||||||
|
#define WIFI_MANAGER_H_INCLUDED
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_wifi_types.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define DEFAULT_COMMAND_LINE CONFIG_DEFAULT_COMMAND_LINE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the maximum size of a SSID name. 32 is IEEE standard.
|
||||||
|
* @warning limit is also hard coded in wifi_config_t. Never extend this value.
|
||||||
|
*/
|
||||||
|
#define MAX_SSID_SIZE 32
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the maximum size of a WPA2 passkey. 64 is IEEE standard.
|
||||||
|
* @warning limit is also hard coded in wifi_config_t. Never extend this value.
|
||||||
|
*/
|
||||||
|
#define MAX_PASSWORD_SIZE 64
|
||||||
|
#define MAX_COMMAND_LINE_SIZE 201
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the maximum number of access points that can be scanned.
|
||||||
|
*
|
||||||
|
* To save memory and avoid nasty out of memory errors,
|
||||||
|
* we can limit the number of APs detected in a wifi scan.
|
||||||
|
*/
|
||||||
|
#define MAX_AP_NUM 15
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines when a connection is lost/attempt to connect is made, how many retries should be made before giving up.
|
||||||
|
* Setting it to 2 for instance means there will be 3 attempts in total (original request + 2 retries)
|
||||||
|
*/
|
||||||
|
#define WIFI_MANAGER_MAX_RETRY CONFIG_WIFI_MANAGER_MAX_RETRY
|
||||||
|
|
||||||
|
/** @brief Defines the task priority of the wifi_manager.
|
||||||
|
*
|
||||||
|
* Tasks spawn by the manager will have a priority of WIFI_MANAGER_TASK_PRIORITY-1.
|
||||||
|
* For this particular reason, minimum task priority is 1. It it highly not recommended to set
|
||||||
|
* it to 1 though as the sub-tasks will now have a priority of 0 which is the priority
|
||||||
|
* of freeRTOS' idle task.
|
||||||
|
*/
|
||||||
|
#define WIFI_MANAGER_TASK_PRIORITY CONFIG_WIFI_MANAGER_TASK_PRIORITY
|
||||||
|
|
||||||
|
/** @brief Defines the auth mode as an access point
|
||||||
|
* Value must be of type wifi_auth_mode_t
|
||||||
|
* @see esp_wifi_types.h
|
||||||
|
* @warning if set to WIFI_AUTH_OPEN, passowrd me be empty. See DEFAULT_AP_PASSWORD.
|
||||||
|
*/
|
||||||
|
#define AP_AUTHMODE WIFI_AUTH_WPA2_PSK
|
||||||
|
|
||||||
|
/** @brief Defines visibility of the access point. 0: visible AP. 1: hidden */
|
||||||
|
#define DEFAULT_AP_SSID_HIDDEN 0
|
||||||
|
|
||||||
|
/** @brief Defines access point's name. Default value: esp32. Run 'make menuconfig' to setup your own value or replace here by a string */
|
||||||
|
#define DEFAULT_AP_SSID CONFIG_DEFAULT_AP_SSID
|
||||||
|
|
||||||
|
/** @brief Defines access point's password.
|
||||||
|
* @warning In the case of an open access point, the password must be a null string "" or "\0" if you want to be verbose but waste one byte.
|
||||||
|
* In addition, the AP_AUTHMODE must be WIFI_AUTH_OPEN
|
||||||
|
*/
|
||||||
|
#define DEFAULT_AP_PASSWORD CONFIG_DEFAULT_AP_PASSWORD
|
||||||
|
|
||||||
|
/** @brief Defines the hostname broadcasted by mDNS */
|
||||||
|
#define DEFAULT_HOSTNAME "esp32"
|
||||||
|
|
||||||
|
/** @brief Defines access point's bandwidth.
|
||||||
|
* Value: WIFI_BW_HT20 for 20 MHz or WIFI_BW_HT40 for 40 MHz
|
||||||
|
* 20 MHz minimize channel interference but is not suitable for
|
||||||
|
* applications with high data speeds
|
||||||
|
*/
|
||||||
|
#define DEFAULT_AP_BANDWIDTH WIFI_BW_HT20
|
||||||
|
|
||||||
|
/** @brief Defines access point's channel.
|
||||||
|
* Channel selection is only effective when not connected to another AP.
|
||||||
|
* Good practice for minimal channel interference to use
|
||||||
|
* For 20 MHz: 1, 6 or 11 in USA and 1, 5, 9 or 13 in most parts of the world
|
||||||
|
* For 40 MHz: 3 in USA and 3 or 11 in most parts of the world
|
||||||
|
*/
|
||||||
|
#define DEFAULT_AP_CHANNEL CONFIG_DEFAULT_AP_CHANNEL
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** @brief Defines the access point's default IP address. Default: "10.10.0.1 */
|
||||||
|
#define DEFAULT_AP_IP CONFIG_DEFAULT_AP_IP
|
||||||
|
|
||||||
|
/** @brief Defines the access point's gateway. This should be the same as your IP. Default: "10.10.0.1" */
|
||||||
|
#define DEFAULT_AP_GATEWAY CONFIG_DEFAULT_AP_GATEWAY
|
||||||
|
|
||||||
|
/** @brief Defines the access point's netmask. Default: "255.255.255.0" */
|
||||||
|
#define DEFAULT_AP_NETMASK CONFIG_DEFAULT_AP_NETMASK
|
||||||
|
|
||||||
|
/** @brief Defines access point's maximum number of clients. Default: 4 */
|
||||||
|
#define DEFAULT_AP_MAX_CONNECTIONS CONFIG_DEFAULT_AP_MAX_CONNECTIONS
|
||||||
|
|
||||||
|
/** @brief Defines access point's beacon interval. 100ms is the recommended default. */
|
||||||
|
#define DEFAULT_AP_BEACON_INTERVAL CONFIG_DEFAULT_AP_BEACON_INTERVAL
|
||||||
|
|
||||||
|
/** @brief Defines if esp32 shall run both AP + STA when connected to another AP.
|
||||||
|
* Value: 0 will have the own AP always on (APSTA mode)
|
||||||
|
* Value: 1 will turn off own AP when connected to another AP (STA only mode when connected)
|
||||||
|
* Turning off own AP when connected to another AP minimize channel interference and increase throughput
|
||||||
|
*/
|
||||||
|
#define DEFAULT_STA_ONLY 1
|
||||||
|
|
||||||
|
/** @brief Defines if wifi power save shall be enabled.
|
||||||
|
* Value: WIFI_PS_NONE for full power (wifi modem always on)
|
||||||
|
* Value: WIFI_PS_MODEM for power save (wifi modem sleep periodically)
|
||||||
|
* Note: Power save is only effective when in STA only mode
|
||||||
|
*/
|
||||||
|
#define DEFAULT_STA_POWER_SAVE WIFI_PS_NONE
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the maximum length in bytes of a JSON representation of an access point.
|
||||||
|
*
|
||||||
|
* maximum ap string length with full 32 char ssid: 75 + \\n + \0 = 77\n
|
||||||
|
* example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","chan":12,"rssi":-100,"auth":4},\n
|
||||||
|
* BUT: we need to escape JSON. Imagine a ssid full of \" ? so it's 32 more bytes hence 77 + 32 = 99.\n
|
||||||
|
* this is an edge case but I don't think we should crash in a catastrophic manner just because
|
||||||
|
* someone decided to have a funny wifi name.
|
||||||
|
*/
|
||||||
|
#define JSON_ONE_APP_SIZE 99
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the maximum length in bytes of a JSON representation of the IP information
|
||||||
|
* assuming all ips are 4*3 digits, and all characters in the ssid require to be escaped.
|
||||||
|
* example: {"ssid":"abcdefghijklmnopqrstuvwxyz012345","ip":"192.168.1.119","netmask":"255.255.255.0","gw":"192.168.1.1","urc":0}
|
||||||
|
*/
|
||||||
|
#define JSON_IP_INFO_SIZE 150
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the complete list of all messages that the wifi_manager can process.
|
||||||
|
*
|
||||||
|
* Some of these message are events ("EVENT"), and some of them are action ("ORDER")
|
||||||
|
* Each of these messages can trigger a callback function and each callback function is stored
|
||||||
|
* in a function pointer array for convenience. Because of this behavior, it is extremely important
|
||||||
|
* to maintain a strict sequence and the top level special element 'MESSAGE_CODE_COUNT'
|
||||||
|
*
|
||||||
|
* @see wifi_manager_set_callback
|
||||||
|
*/
|
||||||
|
typedef enum message_code_t {
|
||||||
|
NONE = 0,
|
||||||
|
ORDER_START_HTTP_SERVER = 1,
|
||||||
|
ORDER_STOP_HTTP_SERVER = 2,
|
||||||
|
ORDER_START_DNS_SERVICE = 3,
|
||||||
|
ORDER_STOP_DNS_SERVICE = 4,
|
||||||
|
ORDER_START_WIFI_SCAN = 5,
|
||||||
|
ORDER_LOAD_AND_RESTORE_STA = 6,
|
||||||
|
ORDER_CONNECT_STA = 7,
|
||||||
|
ORDER_DISCONNECT_STA = 8,
|
||||||
|
ORDER_START_AP = 9,
|
||||||
|
ORDER_START_HTTP = 10,
|
||||||
|
ORDER_START_DNS_HIJACK = 11,
|
||||||
|
EVENT_STA_DISCONNECTED = 12,
|
||||||
|
EVENT_SCAN_DONE = 13,
|
||||||
|
EVENT_STA_GOT_IP = 14,
|
||||||
|
MESSAGE_CODE_COUNT = 15 /* important for the callback array */
|
||||||
|
|
||||||
|
}message_code_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief simplified reason codes for a lost connection.
|
||||||
|
*
|
||||||
|
* esp-idf maintains a big list of reason codes which in practice are useless for most typical application.
|
||||||
|
*/
|
||||||
|
typedef enum update_reason_code_t {
|
||||||
|
UPDATE_CONNECTION_OK = 0,
|
||||||
|
UPDATE_FAILED_ATTEMPT = 1,
|
||||||
|
UPDATE_USER_DISCONNECT = 2,
|
||||||
|
UPDATE_LOST_CONNECTION = 3
|
||||||
|
}update_reason_code_t;
|
||||||
|
|
||||||
|
typedef enum connection_request_made_by_code_t{
|
||||||
|
CONNECTION_REQUEST_NONE = 0,
|
||||||
|
CONNECTION_REQUEST_USER = 1,
|
||||||
|
CONNECTION_REQUEST_AUTO_RECONNECT = 2,
|
||||||
|
CONNECTION_REQUEST_RESTORE_CONNECTION = 3,
|
||||||
|
CONNECTION_REQUEST_MAX = 0x7fffffff /*force the creation of this enum as a 32 bit int */
|
||||||
|
}connection_request_made_by_code_t;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The actual WiFi settings in use
|
||||||
|
*/
|
||||||
|
struct wifi_settings_t{
|
||||||
|
uint8_t ap_ssid[MAX_SSID_SIZE];
|
||||||
|
uint8_t ap_pwd[MAX_PASSWORD_SIZE];
|
||||||
|
uint8_t ap_channel;
|
||||||
|
uint8_t ap_ssid_hidden;
|
||||||
|
wifi_bandwidth_t ap_bandwidth;
|
||||||
|
bool sta_only;
|
||||||
|
wifi_ps_type_t sta_power_save;
|
||||||
|
bool sta_static_ip;
|
||||||
|
tcpip_adapter_ip_info_t sta_static_ip_config;
|
||||||
|
};
|
||||||
|
extern struct wifi_settings_t wifi_settings;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Structure used to store one message in the queue.
|
||||||
|
*/
|
||||||
|
typedef struct{
|
||||||
|
message_code_t code;
|
||||||
|
void *param;
|
||||||
|
} queue_message;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate heap memory for the wifi manager and start the wifi_manager RTOS task
|
||||||
|
*/
|
||||||
|
void wifi_manager_start();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Frees up all memory allocated by the wifi_manager and kill the task.
|
||||||
|
*/
|
||||||
|
void wifi_manager_destroy();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters the AP scan list to unique SSIDs
|
||||||
|
*/
|
||||||
|
void filter_unique( wifi_ap_record_t * aplist, uint16_t * ap_num);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main task for the wifi_manager
|
||||||
|
*/
|
||||||
|
void wifi_manager( void * pvParameters );
|
||||||
|
|
||||||
|
|
||||||
|
char* wifi_manager_get_ap_list_json();
|
||||||
|
char* wifi_manager_get_ip_info_json();
|
||||||
|
|
||||||
|
uint8_t wifi_manager_get_flag();
|
||||||
|
char * wifi_manager_alloc_get_config(char * name, size_t * l);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief saves the current STA wifi config to flash ram storage.
|
||||||
|
*/
|
||||||
|
esp_err_t wifi_manager_save_sta_config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief saves the current configuration to flash ram storage
|
||||||
|
*/
|
||||||
|
esp_err_t wifi_manager_save_autoexec_config(char * value, char * name, int len);
|
||||||
|
esp_err_t wifi_manager_save_autoexec_flag(uint8_t flag);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief fetch a previously STA wifi config in the flash ram storage.
|
||||||
|
* @return true if a previously saved config was found, false otherwise.
|
||||||
|
*/
|
||||||
|
bool wifi_manager_fetch_wifi_sta_config();
|
||||||
|
|
||||||
|
wifi_config_t* wifi_manager_get_wifi_sta_config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A standard wifi event handler as recommended by Espressif
|
||||||
|
*/
|
||||||
|
esp_err_t wifi_manager_event_handler(void *ctx, system_event_t *event);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief requests a connection to an access point that will be process in the main task thread.
|
||||||
|
*/
|
||||||
|
void wifi_manager_connect_async();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief requests a wifi scan
|
||||||
|
*/
|
||||||
|
void wifi_manager_scan_async();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief requests to disconnect and forget about the access point.
|
||||||
|
*/
|
||||||
|
void wifi_manager_disconnect_async();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tries to get access to json buffer mutex.
|
||||||
|
*
|
||||||
|
* The HTTP server can try to access the json to serve clients while the wifi manager thread can try
|
||||||
|
* to update it. These two tasks are synchronized through a mutex.
|
||||||
|
*
|
||||||
|
* The mutex is used by both the access point list json and the connection status json.\n
|
||||||
|
* These two resources should technically have their own mutex but we lose some flexibility to save
|
||||||
|
* on memory.
|
||||||
|
*
|
||||||
|
* This is a simple wrapper around freeRTOS function xSemaphoreTake.
|
||||||
|
*
|
||||||
|
* @param xTicksToWait The time in ticks to wait for the semaphore to become available.
|
||||||
|
* @return true in success, false otherwise.
|
||||||
|
*/
|
||||||
|
bool wifi_manager_lock_json_buffer(TickType_t xTicksToWait);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Releases the json buffer mutex.
|
||||||
|
*/
|
||||||
|
void wifi_manager_unlock_json_buffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generates the connection status json: ssid and IP addresses.
|
||||||
|
* @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
|
||||||
|
*/
|
||||||
|
void wifi_manager_generate_ip_info_json(update_reason_code_t update_reason_code);
|
||||||
|
/**
|
||||||
|
* @brief Clears the connection status json.
|
||||||
|
* @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
|
||||||
|
*/
|
||||||
|
void wifi_manager_clear_ip_info_json();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Generates the list of access points after a wifi scan.
|
||||||
|
* @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
|
||||||
|
*/
|
||||||
|
void wifi_manager_generate_acess_points_json();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Clear the list of access points.
|
||||||
|
* @note This is not thread-safe and should be called only if wifi_manager_lock_json_buffer call is successful.
|
||||||
|
*/
|
||||||
|
void wifi_manager_clear_access_points_json();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Start the mDNS service
|
||||||
|
*/
|
||||||
|
void wifi_manager_initialise_mdns();
|
||||||
|
|
||||||
|
|
||||||
|
bool wifi_manager_lock_sta_ip_string(TickType_t xTicksToWait);
|
||||||
|
void wifi_manager_unlock_sta_ip_string();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief gets the string representation of the STA IP address, e.g.: "192.168.1.69"
|
||||||
|
*/
|
||||||
|
char* wifi_manager_get_sta_ip_string();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief thread safe char representation of the STA IP update
|
||||||
|
*/
|
||||||
|
void wifi_manager_safe_update_sta_ip_string(uint32_t ip);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Register a callback to a custom function when specific event message_code happens.
|
||||||
|
*/
|
||||||
|
void wifi_manager_set_callback(message_code_t message_code, void (*func_ptr)(void*) );
|
||||||
|
|
||||||
|
|
||||||
|
BaseType_t wifi_manager_send_message(message_code_t code, void *param);
|
||||||
|
BaseType_t wifi_manager_send_message_to_front(message_code_t code, void *param);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* WIFI_MANAGER_H_INCLUDED */
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
set(COMPONENT_ADD_INCLUDEDIRS . )
|
set(COMPONENT_ADD_INCLUDEDIRS . )
|
||||||
|
|
||||||
set(COMPONENT_SRCS "esp_app_main.c" "platform_esp32.c" "cmd_wifi.c" "console.c" "nvs_utilities.c" "cmd_squeezelite.c")
|
set(COMPONENT_SRCS "esp_app_main.c" "platform_esp32.c" "cmd_wifi.c" "console.c" "nvs_utilities.c" "cmd_squeezelite.c")
|
||||||
set(REQUIRES esp_common)
|
set(REQUIRES esp_common )
|
||||||
set(REQUIRES_COMPONENTS freertos nvs_flash esp32 spi_flash newlib log console )
|
set(REQUIRES_COMPONENTS freertos nvs_flash esp32 spi_flash newlib log console wifi-manager )
|
||||||
|
|
||||||
register_component()
|
register_component()
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ menu "Squeezelite-ESP32"
|
|||||||
config BT_SINK_NAME
|
config BT_SINK_NAME
|
||||||
depends on BT_SINK
|
depends on BT_SINK
|
||||||
string "Name of Bluetooth A2DP device"
|
string "Name of Bluetooth A2DP device"
|
||||||
default "ESP32-BT"
|
default "ESP32"
|
||||||
help
|
help
|
||||||
This is the name of the bluetooth speaker that will be broadcasted
|
This is the name of the bluetooth speaker that will be broadcasted
|
||||||
config BT_SINK_PIN
|
config BT_SINK_PIN
|
||||||
@@ -237,20 +237,8 @@ menu "Squeezelite-ESP32"
|
|||||||
int "Bluetooth PIN code"
|
int "Bluetooth PIN code"
|
||||||
default 1234
|
default 1234
|
||||||
config AIRPLAY_SINK
|
config AIRPLAY_SINK
|
||||||
bool "AirPlay receiver"
|
bool "AirPlay receiver (not availabe now)"
|
||||||
default y
|
default n
|
||||||
config AIRPLAY_NAME
|
|
||||||
depends on AIRPLAY_SINK
|
|
||||||
string "Name of AirPlay device"
|
|
||||||
default "ESP32-AirPlay"
|
|
||||||
help
|
|
||||||
This is the name of the AirPlay speaker that will be broadcasted
|
|
||||||
config AIRPLAY_PORT
|
|
||||||
depends on AIRPLAY_SINK
|
|
||||||
string "AirPlay listening port"
|
|
||||||
default 5000
|
|
||||||
help
|
|
||||||
AirPlay service listening port
|
|
||||||
endmenu
|
endmenu
|
||||||
|
|
||||||
endmenu
|
endmenu
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
#include "nvs_flash.h"
|
#include "nvs_flash.h"
|
||||||
//extern char current_namespace[];
|
//extern char current_namespace[];
|
||||||
static const char * TAG = "squeezelite_cmd";
|
static const char * TAG = "squeezelite_cmd";
|
||||||
#define SQUEEZELITE_THREAD_STACK_SIZE (6*1024)
|
#define SQUEEZELITE_THREAD_STACK_SIZE 8192
|
||||||
extern int main(int argc, char **argv);
|
extern int main(int argc, char **argv);
|
||||||
static int launchsqueezelite(int argc, char **argv);
|
static int launchsqueezelite(int argc, char **argv);
|
||||||
pthread_t thread_squeezelite;
|
pthread_t thread_squeezelite;
|
||||||
@@ -45,8 +45,9 @@ static void * squeezelite_thread(){
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
isRunning=true;
|
isRunning=true;
|
||||||
ESP_LOGI(TAG,"Waiting for WiFi.");
|
// Let's not wait on WiFi to allow squeezelite to run in bluetooth mode
|
||||||
while(!wait_for_wifi()){usleep(100000);};
|
// ESP_LOGI(TAG,"Waiting for WiFi.");
|
||||||
|
// while(!wait_for_wifi()){usleep(100000);};
|
||||||
ESP_LOGD(TAG ,"Number of args received: %u",thread_parms.argc );
|
ESP_LOGD(TAG ,"Number of args received: %u",thread_parms.argc );
|
||||||
ESP_LOGD(TAG ,"Values:");
|
ESP_LOGD(TAG ,"Values:");
|
||||||
for(int i = 0;i<thread_parms.argc; i++){
|
for(int i = 0;i<thread_parms.argc; i++){
|
||||||
|
|||||||
181
main/cmd_wifi.c
@@ -7,183 +7,4 @@
|
|||||||
CONDITIONS OF ANY KIND, either express or implied.
|
CONDITIONS OF ANY KIND, either express or implied.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "cmd_wifi.h"
|
// cmd_wifi has been replaced by wifi-manager
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
|
||||||
|
|
||||||
#include "cmd_decl.h"
|
|
||||||
#include "esp_log.h"
|
|
||||||
#include "esp_console.h"
|
|
||||||
#include "argtable3/argtable3.h"
|
|
||||||
#include "freertos/FreeRTOS.h"
|
|
||||||
#include "freertos/event_groups.h"
|
|
||||||
#include "esp_wifi.h"
|
|
||||||
#include "tcpip_adapter.h"
|
|
||||||
#include "esp_event.h"
|
|
||||||
#include "led.h"
|
|
||||||
|
|
||||||
#define JOIN_TIMEOUT_MS (10000)
|
|
||||||
|
|
||||||
static EventGroupHandle_t wifi_event_group;
|
|
||||||
const int CONNECTED_BIT = BIT0;
|
|
||||||
static const char * TAG = "cmd_wifi";
|
|
||||||
/** Arguments used by 'join' function */
|
|
||||||
static struct {
|
|
||||||
struct arg_int *timeout;
|
|
||||||
struct arg_str *ssid;
|
|
||||||
struct arg_str *password;
|
|
||||||
struct arg_end *end;
|
|
||||||
} join_args;
|
|
||||||
|
|
||||||
///** Arguments used by 'join' function */
|
|
||||||
//static struct {
|
|
||||||
// struct arg_int *autoconnect;
|
|
||||||
// struct arg_end *end;
|
|
||||||
//} auto_connect_args;
|
|
||||||
|
|
||||||
static void event_handler(void* arg, esp_event_base_t event_base,
|
|
||||||
int32_t event_id, void* event_data)
|
|
||||||
{
|
|
||||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
|
||||||
led_blink_pushed(LED_GREEN, 250, 250);
|
|
||||||
esp_wifi_connect();
|
|
||||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
|
||||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
|
||||||
led_unpush(LED_GREEN);
|
|
||||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool wait_for_wifi(){
|
|
||||||
|
|
||||||
bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
|
|
||||||
|
|
||||||
if(!connected){
|
|
||||||
ESP_LOGD(TAG,"Waiting for WiFi...");
|
|
||||||
connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
|
||||||
pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
|
|
||||||
if(!connected){
|
|
||||||
ESP_LOGD(TAG,"wifi timeout.");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ESP_LOGI(TAG,"WiFi Connected!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return connected;
|
|
||||||
|
|
||||||
}
|
|
||||||
static void initialise_wifi(void)
|
|
||||||
{
|
|
||||||
static bool initialized = false;
|
|
||||||
if (initialized) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
tcpip_adapter_init();
|
|
||||||
wifi_event_group = xEventGroupCreate();
|
|
||||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
|
||||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
|
||||||
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
|
|
||||||
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL) );
|
|
||||||
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
|
|
||||||
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
|
|
||||||
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
|
|
||||||
ESP_ERROR_CHECK( esp_wifi_start() );
|
|
||||||
initialized = true;
|
|
||||||
led_blink(LED_GREEN, 250, 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
|
|
||||||
{
|
|
||||||
initialise_wifi();
|
|
||||||
wifi_config_t wifi_config = { 0 };
|
|
||||||
strncpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
|
|
||||||
if (pass) {
|
|
||||||
strncpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password));
|
|
||||||
}
|
|
||||||
|
|
||||||
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
|
|
||||||
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
|
|
||||||
ESP_ERROR_CHECK( esp_wifi_connect() );
|
|
||||||
|
|
||||||
int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
|
||||||
pdFALSE, pdTRUE, timeout_ms / portTICK_PERIOD_MS);
|
|
||||||
return (bits & CONNECTED_BIT) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
static int set_auto_connect(int argc, char **argv)
|
|
||||||
{
|
|
||||||
// int nerrors = arg_parse(argc, argv, (void **) &join_args);
|
|
||||||
// if (nerrors != 0) {
|
|
||||||
// arg_print_errors(stderr, join_args.end, argv[0]);
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
// ESP_LOGI(__func__, "Connecting to '%s'",
|
|
||||||
// join_args.ssid->sval[0]);
|
|
||||||
//
|
|
||||||
// /* set default value*/
|
|
||||||
// if (join_args.timeout->count == 0) {
|
|
||||||
// join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// bool connected = wifi_join(join_args.ssid->sval[0],
|
|
||||||
// join_args.password->sval[0],
|
|
||||||
// join_args.timeout->ival[0]);
|
|
||||||
// if (!connected) {
|
|
||||||
// ESP_LOGW(__func__, "Connection timed out");
|
|
||||||
// return 1;
|
|
||||||
// }
|
|
||||||
// ESP_LOGI(__func__, "Connected");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
static int connect(int argc, char **argv)
|
|
||||||
{
|
|
||||||
int nerrors = arg_parse(argc, argv, (void **) &join_args);
|
|
||||||
if (nerrors != 0) {
|
|
||||||
arg_print_errors(stderr, join_args.end, argv[0]);
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
ESP_LOGI(__func__, "Connecting to '%s'",
|
|
||||||
join_args.ssid->sval[0]);
|
|
||||||
|
|
||||||
/* set default value*/
|
|
||||||
if (join_args.timeout->count == 0) {
|
|
||||||
join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool connected = wifi_join(join_args.ssid->sval[0],
|
|
||||||
join_args.password->sval[0],
|
|
||||||
join_args.timeout->ival[0]);
|
|
||||||
if (!connected) {
|
|
||||||
ESP_LOGW(__func__, "Connection timed out");
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
ESP_LOGI(__func__, "Connected");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
void register_wifi_join()
|
|
||||||
{
|
|
||||||
join_args.timeout = arg_int0(NULL, "timeout", "<t>", "Connection timeout, ms");
|
|
||||||
join_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of AP");
|
|
||||||
join_args.password = arg_str0(NULL, NULL, "<pass>", "PSK of AP");
|
|
||||||
join_args.end = arg_end(2);
|
|
||||||
|
|
||||||
const esp_console_cmd_t join_cmd = {
|
|
||||||
.command = "join",
|
|
||||||
.help = "Join WiFi AP as a station",
|
|
||||||
.hint = NULL,
|
|
||||||
.func = &connect,
|
|
||||||
.argtable = &join_args
|
|
||||||
};
|
|
||||||
ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) );
|
|
||||||
}
|
|
||||||
|
|
||||||
void register_wifi()
|
|
||||||
{
|
|
||||||
register_wifi_join();
|
|
||||||
initialise_wifi();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -12,8 +12,7 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Register WiFi functions
|
|
||||||
void register_wifi();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "cmd_squeezelite.h"
|
#include "cmd_squeezelite.h"
|
||||||
#include "nvs_utilities.h"
|
#include "nvs_utilities.h"
|
||||||
pthread_t thread_console;
|
pthread_t thread_console;
|
||||||
|
|
||||||
static void * console_thread();
|
static void * console_thread();
|
||||||
void console_start();
|
void console_start();
|
||||||
static const char * TAG = "console";
|
static const char * TAG = "console";
|
||||||
@@ -145,12 +146,12 @@ void process_autoexec(){
|
|||||||
{
|
{
|
||||||
ESP_LOGD(TAG,"No matching command found for name autoexec. Adding default entries");
|
ESP_LOGD(TAG,"No matching command found for name autoexec. Adding default entries");
|
||||||
uint8_t autoexec_dft=0;
|
uint8_t autoexec_dft=0;
|
||||||
char autoexec1_dft[64];
|
//char autoexec1_dft[64];
|
||||||
char autoexec2_dft[256]="squeezelite -o \"I2S\" -b 500:2000 -d all=info -M esp32";
|
char autoexec1_dft[]="squeezelite -o \"I2S\" -b 500:2000 -d all=info -M esp32";
|
||||||
snprintf(autoexec1_dft, 64, "join %s %s", CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
|
//snprintf(autoexec1_dft, 64, "join %s %s", CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD);
|
||||||
store_nvs_value(NVS_TYPE_U8,"autoexec",&autoexec_dft);
|
store_nvs_value(NVS_TYPE_U8,"autoexec",&autoexec_dft);
|
||||||
store_nvs_value(NVS_TYPE_STR,"autoexec1",autoexec1_dft);
|
store_nvs_value(NVS_TYPE_STR,"autoexec1",autoexec1_dft);
|
||||||
store_nvs_value(NVS_TYPE_STR,"autoexec2",autoexec2_dft);
|
//store_nvs_value(NVS_TYPE_STR,"autoexec2",autoexec2_dft);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
static void initialize_filesystem() {
|
static void initialize_filesystem() {
|
||||||
@@ -237,7 +238,6 @@ void console_start() {
|
|||||||
/* Register commands */
|
/* Register commands */
|
||||||
esp_console_register_help_command();
|
esp_console_register_help_command();
|
||||||
register_system();
|
register_system();
|
||||||
register_wifi();
|
|
||||||
register_nvs();
|
register_nvs();
|
||||||
register_squeezelite();
|
register_squeezelite();
|
||||||
register_i2ctools();
|
register_i2ctools();
|
||||||
@@ -276,6 +276,7 @@ void console_start() {
|
|||||||
pthread_attr_t attr;
|
pthread_attr_t attr;
|
||||||
pthread_attr_init(&attr);
|
pthread_attr_init(&attr);
|
||||||
pthread_create(&thread_console, &attr, console_thread, NULL);
|
pthread_create(&thread_console, &attr, console_thread, NULL);
|
||||||
|
|
||||||
pthread_attr_destroy(&attr);
|
pthread_attr_destroy(&attr);
|
||||||
}
|
}
|
||||||
void run_command(char * line){
|
void run_command(char * line){
|
||||||
|
|||||||
@@ -20,6 +20,33 @@
|
|||||||
*/
|
*/
|
||||||
#include "platform_esp32.h"
|
#include "platform_esp32.h"
|
||||||
#include "led.h"
|
#include "led.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "driver/spi_master.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_spi_flash.h"
|
||||||
|
#include "esp_wifi.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_event_loop.h"
|
||||||
|
#include "nvs_flash.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "freertos/event_groups.h"
|
||||||
|
#include "mdns.h"
|
||||||
|
#include "lwip/api.h"
|
||||||
|
#include "lwip/err.h"
|
||||||
|
#include "lwip/netdb.h"
|
||||||
|
|
||||||
|
#include "http_server.h"
|
||||||
|
#include "wifi_manager.h"
|
||||||
|
static EventGroupHandle_t wifi_event_group;
|
||||||
|
const int CONNECTED_BIT = BIT0;
|
||||||
|
#define JOIN_TIMEOUT_MS (10000)
|
||||||
|
|
||||||
|
static const char TAG[] = "esp_app_main";
|
||||||
|
|
||||||
|
|
||||||
#ifdef CONFIG_SQUEEZEAMP
|
#ifdef CONFIG_SQUEEZEAMP
|
||||||
#define LED_GREEN_GPIO 12
|
#define LED_GREEN_GPIO 12
|
||||||
@@ -29,10 +56,48 @@
|
|||||||
#define LED_RED_GPIO 0
|
#define LED_RED_GPIO 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* brief this is an exemple of a callback that you can setup in your own app to get notified of wifi manager event */
|
||||||
|
void cb_connection_got_ip(void *pvParameter){
|
||||||
|
ESP_LOGI(TAG, "I have a connection!");
|
||||||
|
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||||
|
led_unpush(LED_GREEN);
|
||||||
|
}
|
||||||
|
void cb_connection_sta_disconnected(void *pvParameter){
|
||||||
|
led_blink_pushed(LED_GREEN, 250, 250);
|
||||||
|
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||||
|
}
|
||||||
|
bool wait_for_wifi(){
|
||||||
|
bool connected=(xEventGroupGetBits(wifi_event_group) & CONNECTED_BIT)!=0;
|
||||||
|
if(!connected){
|
||||||
|
ESP_LOGD(TAG,"Waiting for WiFi...");
|
||||||
|
connected = (xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
||||||
|
pdFALSE, pdTRUE, JOIN_TIMEOUT_MS / portTICK_PERIOD_MS)& CONNECTED_BIT)!=0;
|
||||||
|
if(!connected){
|
||||||
|
ESP_LOGW(TAG,"wifi timeout.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG,"WiFi Connected!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return connected;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void app_main()
|
void app_main()
|
||||||
{
|
{
|
||||||
led_config(LED_GREEN, LED_GREEN_GPIO, 0);
|
led_config(LED_GREEN, LED_GREEN_GPIO, 0);
|
||||||
led_config(LED_RED, LED_RED_GPIO, 0);
|
led_config(LED_RED, LED_RED_GPIO, 0);
|
||||||
|
wifi_event_group = xEventGroupCreate();
|
||||||
|
|
||||||
|
/* start the wifi manager */
|
||||||
|
led_blink(LED_GREEN, 250, 250);
|
||||||
|
wifi_manager_start();
|
||||||
|
wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip);
|
||||||
|
wifi_manager_set_callback(WIFI_EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected);
|
||||||
|
|
||||||
|
|
||||||
console_start();
|
console_start();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Name, Type, SubType, Offset, Size, Flags
|
nvs, data, nvs, 0x9000, 0x4000,
|
||||||
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
|
otadata, data, ota, 0xD000, 0x2000,
|
||||||
nvs, data, nvs, 0x9000, 0x6000,
|
phy_init, data, phy, 0xF000, 0x1000,
|
||||||
phy_init, data, phy, 0xf000, 0x1000,
|
factory, app, factory, 0x10000, 0x140000,
|
||||||
factory, app, factory, 0x10000, 2M,
|
ota_0, app, ota_0, 0x150000, 0x270000,
|
||||||
storage, data, fat, , 819200,
|
coredump, data, coredump, 0x3C0000, 0x10000,
|
||||||
coredump, data, coredump,, 64K
|
storage, data, fat, 0x3D0000, 0x30000,
|
||||||
|
|||||||
|
@@ -77,10 +77,9 @@ CONFIG_SPIRAM_SIZE=-1
|
|||||||
CONFIG_SPIRAM_SPEED_80M=y
|
CONFIG_SPIRAM_SPEED_80M=y
|
||||||
CONFIG_SPIRAM_MEMTEST=y
|
CONFIG_SPIRAM_MEMTEST=y
|
||||||
CONFIG_SPIRAM_CACHE_WORKAROUND=y
|
CONFIG_SPIRAM_CACHE_WORKAROUND=y
|
||||||
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=256
|
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=512
|
||||||
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536
|
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
|
||||||
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
|
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
|
||||||
CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
|
|
||||||
CONFIG_SPIRAM_OCCUPY_VSPI_HOST=y
|
CONFIG_SPIRAM_OCCUPY_VSPI_HOST=y
|
||||||
CONFIG_SPIRAM_BANKSWITCH_ENABLE=n
|
CONFIG_SPIRAM_BANKSWITCH_ENABLE=n
|
||||||
CONFIG_D0WD_PSRAM_CLK_IO=17
|
CONFIG_D0WD_PSRAM_CLK_IO=17
|
||||||
@@ -114,7 +113,6 @@ CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
|
|||||||
CONFIG_ESP32_PHY_MAX_TX_POWER=20
|
CONFIG_ESP32_PHY_MAX_TX_POWER=20
|
||||||
CONFIG_FREERTOS_HZ=100
|
CONFIG_FREERTOS_HZ=100
|
||||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
|
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
|
||||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=32
|
|
||||||
CONFIG_LWIP_NETIF_LOOPBACK=y
|
CONFIG_LWIP_NETIF_LOOPBACK=y
|
||||||
CONFIG_LWIP_TCP_MSL=60000
|
CONFIG_LWIP_TCP_MSL=60000
|
||||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=8192
|
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=8192
|
||||||
|
|||||||