diff --git a/.gitmodules b/.gitmodules index 928a2595..501dbe4c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "components/lws-esp32"] path = components/lws-esp32 url = https://github.com/huming2207/lws-esp32.git +[submodule "components/telnet/libtelnet"] + path = components/telnet/libtelnet + url = https://github.com/seanmiddleditch/libtelnet diff --git a/components/cmd_nvs/cmd_nvs.c b/components/cmd_nvs/cmd_nvs.c index 3c763757..bbd59600 100644 --- a/components/cmd_nvs/cmd_nvs.c +++ b/components/cmd_nvs/cmd_nvs.c @@ -29,7 +29,7 @@ extern "C" { static const char *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob"; -static const char * TAG = "platform_esp32"; +static const char * TAG = "cmd_nvs"; static struct { struct arg_str *key; diff --git a/components/driver_bt/bt_app_core.c b/components/driver_bt/bt_app_core.c index 48f57e2f..aa628f27 100644 --- a/components/driver_bt/bt_app_core.c +++ b/components/driver_bt/bt_app_core.c @@ -18,7 +18,7 @@ #include "freertos/queue.h" #include "freertos/task.h" -static const char * TAG = "platform_esp32"; +static const char * TAG = "btappcore"; static void bt_app_task_handler(void *arg); static bool bt_app_send_msg(bt_app_msg_t *msg); diff --git a/components/raop/raop.c b/components/raop/raop.c index 1f739c17..db6c24cb 100644 --- a/components/raop/raop.c +++ b/components/raop/raop.c @@ -542,7 +542,7 @@ static bool handle_rtsp(raop_ctx_t *ctx, int sock) ctx->rtp = rtp.ctx; - if (cport * tport * rtp.cport * rtp.tport * rtp.aport && rtp.ctx) { + if ( (cport * tport * rtp.cport * rtp.tport * rtp.aport) != 0 && rtp.ctx) { char *transport; asprintf(&transport, "RTP/AVP/UDP;unicast;mode=record;control_port=%u;timing_port=%u;server_port=%u", rtp.cport, rtp.tport, rtp.aport); LOG_DEBUG("[%p]: audio=(%hu:%hu), timing=(%hu:%hu), control=(%hu:%hu)", ctx, 0, rtp.aport, tport, rtp.tport, cport, rtp.cport); diff --git a/components/squeezelite-ota/cmd_ota.c b/components/squeezelite-ota/cmd_ota.c index 9b20c6b3..4bf283db 100644 --- a/components/squeezelite-ota/cmd_ota.c +++ b/components/squeezelite-ota/cmd_ota.c @@ -26,7 +26,7 @@ #include "esp32/rom/uart.h" #include "sdkconfig.h" -static const char * TAG = "platform_esp32"; +static const char * TAG = "ota"; extern esp_err_t start_ota(const char * bin_url); static struct { struct arg_str *url; diff --git a/components/telnet/CMakeLists.txt b/components/telnet/CMakeLists.txt new file mode 100644 index 00000000..36bcf9cb --- /dev/null +++ b/components/telnet/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "telnet.c" + INCLUDE_DIRS . + INCLUDE_DIRS . ../tools/ + +) diff --git a/components/telnet/component.mk b/components/telnet/component.mk new file mode 100644 index 00000000..6c466cfb --- /dev/null +++ b/components/telnet/component.mk @@ -0,0 +1,15 @@ +# +# 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_SRCDIRS := . +COMPONENT_SRCDIRS += ./libtelnet +COMPONENT_ADD_INCLUDEDIRS := . +COMPONENT_ADD_INCLUDEDIRS += ./libtelnet +COMPONENT_EXTRA_INCLUDES += $(PROJECT_PATH)/main/ + \ No newline at end of file diff --git a/components/telnet/telnet.c b/components/telnet/telnet.c new file mode 100644 index 00000000..18aa9d1b --- /dev/null +++ b/components/telnet/telnet.c @@ -0,0 +1,420 @@ + /** + * Test the telnet functions. + * + * Perform a test using the telnet functions. + * This code exports two new global functions: + * + * void telnet_listenForClients(void (*callback)(uint8_t *buffer, size_t size)) + * void telnet_sendData(uint8_t *buffer, size_t size) + * + * For additional details and documentation see: + * * Free book on ESP32 - https://leanpub.com/kolban-ESP32 + * + * + * Neil Kolban + * + * **************************** + * Additional portions were taken from + * https://github.com/PocketSprite/8bkc-sdk/blob/master/8bkc-components/8bkc-hal/vfs-stdout.c + * + */ +#include // Required for libtelnet.h +#include +#include "libtelnet.h" +#include "stdbool.h" +#include +#include +#include +#include +#include "sdkconfig.h" +#include "freertos/ringbuf.h" +#include "esp_app_trace.h" +#include "telnet.h" +#include "esp_vfs.h" +#include "esp_vfs_dev.h" +#include "esp_attr.h" +#include "soc/uart_struct.h" +#include "driver/uart.h" +#include "config.h" +#include "nvs_utilities.h" +#include "platform_esp32.h" + + +#define TELNET_STACK_SIZE 8048 + +RingbufHandle_t buf_handle; +SemaphoreHandle_t xSemaphore = NULL; +static size_t send_chunk=300; +static size_t log_buf_size=2000; //32-bit aligned size +static bool bIsEnabled=false; +const static char tag[] = "telnet"; + +int _log_vprintf(const char *fmt, va_list args); +void telnet_esp32_listenForClients(); +int telnet_esp32_vprintf(const char *fmt, va_list va); + +static void telnetTask(void *data); + + +static int uart_fd=0; + + +// The global tnHandle ... since we are only processing ONE telnet +// client at a time, this can be a global static. +static telnet_t *tnHandle; +static void handleLogBuffer(int partnerSocket, UBaseType_t bytes); + +struct telnetUserData { + int sockfd; +}; + + + +static void telnetTask(void *data) { + ESP_LOGD(tag, ">> telnetTask"); + telnet_esp32_listenForClients(); + ESP_LOGD(tag, "<< telnetTask"); + vTaskDelete(NULL); +} + +void start_telnet(void * pvParameter){ + static bool isStarted=false; + StaticTask_t *xTaskBuffer = (StaticTask_t*) heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + StackType_t *xStack = malloc(TELNET_STACK_SIZE); + + if(!isStarted && bIsEnabled) { + // xTaskCreatePinnedToCore(&telnetTask, "telnet", 8048, NULL, 5, NULL, 0); + xTaskCreateStatic( (TaskFunction_t) &telnetTask, "telnet", TELNET_STACK_SIZE, NULL, ESP_TASK_PRIO_MIN + 1, xStack, xTaskBuffer); + isStarted=true; + } +} + +static ssize_t stdout_write(int fd, const void * data, size_t size) { + if (xSemaphoreTake(xSemaphore, (TickType_t) 10) == pdTRUE) { + // #1 Write to ringbuffer + if (buf_handle == NULL) { + printf("%s() ABORT. file handle _log_remote_fp is NULL\n", + __FUNCTION__); + } else { + //Send an item + UBaseType_t res = xRingbufferSend(buf_handle, data, size, + pdMS_TO_TICKS(100)); + if (res != pdTRUE) { + // flush some entries + handleLogBuffer(0, size); + res = xRingbufferSend(buf_handle, data, size, + pdMS_TO_TICKS(100)); + if (res != pdTRUE) { + + printf("%s() ABORT. Unable to store log entry in buffer\n", + __FUNCTION__); + } + } + } + xSemaphoreGive(xSemaphore); + } else { + // We could not obtain the semaphore and can therefore not access + // the shared resource safely. + } + return write(uart_fd, data, size); +} + +static ssize_t stdout_read(int fd, void* data, size_t size) { + return read(fd, data, size); +} + +static int stdout_open(const char * path, int flags, int mode) { + return 0; +} + +static int stdout_close(int fd) { + return 0; +} + +static int stdout_fstat(int fd, struct stat * st) { + st->st_mode = S_IFCHR; + return 0; +} + + +void kchal_stdout_register() { + const esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_DEFAULT, + .write = &stdout_write, + .open = &stdout_open, + .fstat = &stdout_fstat, + .close = &stdout_close, + .read = &stdout_read, + }; + uart_fd=open("/dev/uart/0", O_RDWR); + ESP_ERROR_CHECK(esp_vfs_register("/dev/pkspstdout", &vfs, NULL)); + freopen("/dev/pkspstdout", "w", stdout); + freopen("/dev/pkspstdout", "w", stderr); + //printf("8bkc_hal_stdout_register: Custom stdout/stderr handler installed.\n"); +} +/********************************* + * Telnet Support + */ + + +void init_telnet(){ + + char *val= get_nvs_value_alloc(NVS_TYPE_STR, "telnet_enable"); + + if (!val || !strcasestr("YX",val) ) { + ESP_LOGI(tag,"Telnet support disabled"); + if(val) free(val); + return; + } + val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_block"); + if(val){ + send_chunk=atol(val); + free(val); + send_chunk=send_chunk>0?send_chunk:500; + } + val=get_nvs_value_alloc(NVS_TYPE_STR, "telnet_buffer"); + if(val){ + log_buf_size=atol(val); + free(val); + log_buf_size=log_buf_size>0?log_buf_size:4000; + } + bIsEnabled=true; + + // Create the semaphore to guard a shared resource. + vSemaphoreCreateBinary( xSemaphore ); + + // First thing we need to do here is to redirect the output to our telnet handler + //Allocate ring buffer data structure and storage area into external RAM + StaticRingbuffer_t *buffer_struct = (StaticRingbuffer_t *)heap_caps_malloc(sizeof(StaticRingbuffer_t), MALLOC_CAP_SPIRAM); + uint8_t *buffer_storage = (uint8_t *)heap_caps_malloc(sizeof(uint8_t)*log_buf_size, MALLOC_CAP_SPIRAM); + + //Create a ring buffer with manually allocated memory + buf_handle = xRingbufferCreateStatic(log_buf_size, RINGBUF_TYPE_BYTEBUF, buffer_storage, buffer_struct); + + if (buf_handle == NULL) { + ESP_LOGE(tag,"Failed to create ring buffer for telnet!"); + return; + } + + ESP_LOGI(tag, "***Redirecting log output to telnet"); + //esp_log_set_vprintf(&_log_vprintf); + kchal_stdout_register(); +} +/** + * Convert a telnet event type to its string representation. + */ +static char *eventToString(telnet_event_type_t type) { + switch(type) { + case TELNET_EV_COMPRESS: + return "TELNET_EV_COMPRESS"; + case TELNET_EV_DATA: + return "TELNET_EV_DATA"; + case TELNET_EV_DO: + return "TELNET_EV_DO"; + case TELNET_EV_DONT: + return "TELNET_EV_DONT"; + case TELNET_EV_ENVIRON: + return "TELNET_EV_ENVIRON"; + case TELNET_EV_ERROR: + return "TELNET_EV_ERROR"; + case TELNET_EV_IAC: + return "TELNET_EV_IAC"; + case TELNET_EV_MSSP: + return "TELNET_EV_MSSP"; + case TELNET_EV_SEND: + return "TELNET_EV_SEND"; + case TELNET_EV_SUBNEGOTIATION: + return "TELNET_EV_SUBNEGOTIATION"; + case TELNET_EV_TTYPE: + return "TELNET_EV_TTYPE"; + case TELNET_EV_WARNING: + return "TELNET_EV_WARNING"; + case TELNET_EV_WILL: + return "TELNET_EV_WILL"; + case TELNET_EV_WONT: + return "TELNET_EV_WONT"; + case TELNET_EV_ZMP: + return "TELNET_EV_ZMP"; + } + return "Unknown type"; +} // eventToString + + +/** + * Send data to the telnet partner. + */ +void telnet_esp32_sendData(uint8_t *buffer, size_t size) { + if (tnHandle != NULL) { + telnet_send(tnHandle, (char *)buffer, size); + } +} // telnet_esp32_sendData + + +/** + * Send a vprintf formatted output to the telnet partner. + */ +int telnet_esp32_vprintf(const char *fmt, va_list va) { + if (tnHandle == NULL) { + return 0; + } + return telnet_vprintf(tnHandle, fmt, va); +} // telnet_esp32_vprintf + +/** + * Telnet handler. + */ +void processReceivedData(const char * buffer, size_t size){ + //ESP_LOGD(tag, "received data, len=%d", event->data.size); + + char * command = malloc(size+1); + memcpy(command,buffer,size); + command[size]='\0'; + // todo: implement conditional remote echo + //telnet_esp32_sendData((uint8_t *)command, size); + if(command[0]!='\r' && command[0]!='\n'){ + // some telnet clients will send data and crlf in two separate buffers + printf(command); + printf("\r\n"); + run_command((char *)command); + + } + free(command); + +} +static void telnetHandler( + telnet_t *thisTelnet, + telnet_event_t *event, + void *userData) { + int rc; + struct telnetUserData *telnetUserData = (struct telnetUserData *)userData; + switch(event->type) { + case TELNET_EV_SEND: + rc = send(telnetUserData->sockfd, event->data.buffer, event->data.size, 0); + if (rc < 0) { + //printf("ERROR: (telnet) send: %d (%s)", errno, strerror(errno)); + } + break; + + case TELNET_EV_DATA: + processReceivedData(event->data.buffer, event->data.size); + break; + + default: + printf("telnet event: %s\n", eventToString(event->type)); + break; + } // End of switch event type +} // myTelnetHandler + + +static void handleLogBuffer(int partnerSocket, UBaseType_t count){ + //Receive an item from no-split ring buffer + size_t item_size; + UBaseType_t uxItemsWaiting; + UBaseType_t uxBytesToSend=count; + + vRingbufferGetInfo(buf_handle, NULL, NULL, NULL, &uxItemsWaiting); + if( partnerSocket ==0 && (uxItemsWaiting*100 / log_buf_size) <75){ + // We still have some room in the ringbuffer and there's no telnet + // connection yet, so bail out for now. + //printf("%s() Log buffer used %u of %u bytes used\n", __FUNCTION__, uxItemsWaiting, log_buf_size); + return; + } + + while(uxBytesToSend>0){ + char *item = (char *)xRingbufferReceiveUpTo(buf_handle, &item_size, pdMS_TO_TICKS(50), uxBytesToSend); + //Check received data + + if (item != NULL) { + uxBytesToSend-=item_size; + if(partnerSocket!=0) + telnet_esp32_sendData((uint8_t *)item, item_size); + else{ + //printf("%s() flushing %u bytes from log buffer\n", __FUNCTION__, item_size); + } + //Return Item + vRingbufferReturnItem(buf_handle, (void *)item); + } + else{ + break; + } + } +} + +static void doTelnet(int partnerSocket) { + //ESP_LOGD(tag, ">> doTelnet"); + static const telnet_telopt_t my_telopts[] = { + { TELNET_TELOPT_ECHO, TELNET_WILL, TELNET_DONT }, + { TELNET_TELOPT_TTYPE, TELNET_WILL, TELNET_DONT }, + { TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DO }, + { TELNET_TELOPT_BINARY, TELNET_WILL, TELNET_DO }, + { TELNET_TELOPT_NAWS, TELNET_WILL, TELNET_DONT }, + { -1, 0, 0 } + }; + struct telnetUserData *pTelnetUserData = (struct telnetUserData *)malloc(sizeof(struct telnetUserData)); + pTelnetUserData->sockfd = partnerSocket; + + + tnHandle = telnet_init(my_telopts, telnetHandler, 0, pTelnetUserData); + + uint8_t buffer[1024]; + while(1) { + //ESP_LOGD(tag, "waiting for data"); + ssize_t len = recv(partnerSocket, (char *)buffer, sizeof(buffer), MSG_DONTWAIT); + if (len >0 ) { + //ESP_LOGD(tag, "received %d bytes", len); + telnet_recv(tnHandle, (char *)buffer, len); + } + else if (errno != EAGAIN && errno !=EWOULDBLOCK ){ + telnet_free(tnHandle); + tnHandle = NULL; + free(pTelnetUserData); + return; + } + handleLogBuffer(partnerSocket, send_chunk); + + taskYIELD(); + } + +} // doTelnet + +/** + * Listen for telnet clients and handle them. + */ +void telnet_esp32_listenForClients() { + //ESP_LOGD(tag, ">> telnet_listenForClients"); + int serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + + struct sockaddr_in serverAddr; + serverAddr.sin_family = AF_INET; + serverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + serverAddr.sin_port = htons(23); + + int rc = bind(serverSocket, (struct sockaddr *)&serverAddr, sizeof(serverAddr)); + if (rc < 0) { + ESP_LOGE(tag, "bind: %d (%s)", errno, strerror(errno)); + return; + } + + rc = listen(serverSocket, 5); + if (rc < 0) { + ESP_LOGE(tag, "listen: %d (%s)", errno, strerror(errno)); + return; + } + + while(1) { + socklen_t len = sizeof(serverAddr); + rc = accept(serverSocket, (struct sockaddr *)&serverAddr, &len); + if (rc < 0 ){ + ESP_LOGE(tag, "accept: %d (%s)", errno, strerror(errno)); + return; + } + else { + int partnerSocket = rc; + ESP_LOGD(tag, "We have a new client connection!"); + doTelnet(partnerSocket); + ESP_LOGD(tag, "Telnet connection terminated"); + } + } +} // listenForNewClient diff --git a/components/telnet/telnet.h b/components/telnet/telnet.h new file mode 100644 index 00000000..db619f08 --- /dev/null +++ b/components/telnet/telnet.h @@ -0,0 +1,4 @@ + +void init_telnet(); +void start_telnet(void * pvParameter); +void telnet_esp32_sendData(uint8_t *buffer, size_t size); diff --git a/components/wifi-manager/http_server.c b/components/wifi-manager/http_server.c index d0d071b8..44d233a7 100644 --- a/components/wifi-manager/http_server.c +++ b/components/wifi-manager/http_server.c @@ -84,7 +84,6 @@ const static char http_hdr_template[] = "HTTP/1.1 200 OK\nContent-type: %s\nAcce const static char http_html_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/html\nAccess-Control-Allow-Origin: *\nAccept-Encoding: identity\n\n"; const static char http_css_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/css\nCache-Control: public, max-age=31536000\nAccess-Control-Allow-Origin: *\n\n"; const static char http_js_hdr[] = "HTTP/1.1 200 OK\nContent-type: text/javascript\nAccess-Control-Allow-Origin: *\n\n"; -const static char http_svg_hdr[] = "HTTP/1.1 200 OK\nContent-type: image/svg+xml\nAccess-Control-Allow-Origin: *\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"; diff --git a/main/console.c b/main/console.c index 23dd406a..2b83d68d 100644 --- a/main/console.c +++ b/main/console.c @@ -226,14 +226,14 @@ void run_command(char * line){ esp_err_t err = esp_console_run(line, &ret); if (err == ESP_ERR_NOT_FOUND) { - printf("Unrecognized command\n"); + ESP_LOGE(TAG,"Unrecognized command: %s\n", line); } else if (err == ESP_ERR_INVALID_ARG) { // command was empty } else if (err == ESP_OK && ret != ESP_OK) { - printf("Command returned non-zero error code: 0x%x (%s)\n", ret, + ESP_LOGW(TAG,"Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(err)); } else if (err != ESP_OK) { - printf("Internal error: %s\n", esp_err_to_name(err)); + ESP_LOGE(TAG,"Internal error: %s\n", esp_err_to_name(err)); } } static void * console_thread() { diff --git a/main/esp_app_main.c b/main/esp_app_main.c index 37090474..3119f110 100644 --- a/main/esp_app_main.c +++ b/main/esp_app_main.c @@ -47,6 +47,7 @@ #include #include "config.h" #include "audio_controls.h" +#include "telnet.h" static const char certs_namespace[] = "certificates"; static const char certs_key[] = "blob"; @@ -312,23 +313,34 @@ void register_default_nvs(){ ESP_LOGD(TAG,"Registering default value for key %s", "metadata_config"); config_set_default(NVS_TYPE_STR, "metadata_config", "", 0); + ESP_LOGD(TAG,"Registering default value for key %s", "telnet_enable"); + config_set_default(NVS_TYPE_STR, "telnet_enable", "", 0); + + ESP_LOGD(TAG,"Registering default value for key %s", "telnet_buffer"); + config_set_default(NVS_TYPE_STR, "telnet_buffer", "40000", 0); + + ESP_LOGD(TAG,"Registering default value for key %s", "telnet_block"); + config_set_default(NVS_TYPE_STR, "telnet_block", "500", 0); + ESP_LOGD(TAG,"Done setting default values in nvs."); } void app_main() { char * fwurl = NULL; - esp_err_t update_certificates(); + ESP_LOGI(TAG,"Starting app_main"); + initialize_nvs(); + ESP_LOGI(TAG,"Setting up telnet."); + init_telnet(); // align on 32 bits boundaries + + ESP_LOGI(TAG,"Setting up config subsystem."); + config_init(); + ESP_LOGD(TAG,"Creating event group for wifi"); wifi_event_group = xEventGroupCreate(); ESP_LOGD(TAG,"Clearing CONNECTED_BIT from wifi group"); xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); - ESP_LOGI(TAG,"Starting app_main"); - initialize_nvs(); - - ESP_LOGI(TAG,"Setting up config subsystem."); - config_init(); ESP_LOGI(TAG,"Registering default values"); register_default_nvs(); @@ -382,6 +394,10 @@ void app_main() wifi_manager_start(); wifi_manager_set_callback(EVENT_STA_GOT_IP, &cb_connection_got_ip); wifi_manager_set_callback(EVENT_STA_DISCONNECTED, &cb_connection_sta_disconnected); + /* Start the telnet service after we are certain that the network stack has been properly initialized. + * This can be either after we're started the AP mode, or after we've started the STA mode */ + wifi_manager_set_callback(ORDER_START_AP, &start_telnet); + wifi_manager_set_callback(ORDER_CONNECT_STA, &start_telnet); } console_start(); if(fwurl && strlen(fwurl)>0){