/* * Copyright (c) 2019-2023, Erich Styger * * SPDX-License-Identifier: BSD-3-Clause */ #include "platform.h" #if PL_CONFIG_USE_SHELL #include "shell.h" #include "McuShell.h" #include "McuRTOS.h" #if PL_CONFIG_USE_RTT #include "McuRTT.h" #endif #if McuLib_CONFIG_CPU_IS_ARM_CORTEX_M #include "McuArmTools.h" #endif #include "McuShellUart.h" #include "McuTimeDate.h" #include "McuLog.h" #include "McuUtility.h" #if PL_CONFIG_USE_BUTTONS #include "buttons.h" #endif #if PL_CONFIG_USE_LEDS #include "leds.h" #endif #if PL_CONFIG_USE_BLINKY #include "blinky.h" #endif #if PL_CONFIG_USE_USB_CDC #include "pico/stdlib.h" #endif #if PL_CONFIG_USE_WIFI && McuLib_CONFIG_CPU_IS_ESP32 #include "WiFi.h" #elif PL_CONFIG_USE_WIFI && PL_CONFIG_USE_PICO_W #include "PicoWiFi.h" #endif #if PL_CONFIG_USE_PING #include "ping_shell.h" #endif #if PL_CONFIG_USE_UDP_SERVER #include "udp_server_shell.h" #endif #if PL_CONFIG_USE_UDP_CLIENT #include "udp_client.h" #endif #if PL_CONFIG_USE_MQTT_CLIENT #include "mqtt_client.h" #endif #if PL_CONFIG_USE_NTP_CLIENT #include "ntp_client.h" #endif #if PL_CONFIG_USE_SENSOR #include "sensor.h" #endif #if PL_CONFIG_USE_SENSOR && PL_CONFIG_USE_SHT31 #include "McuSHT31.h" #elif PL_CONFIG_USE_SENSOR && PL_CONFIG_USE_SHT40 #include "McuSHT40.h" #endif #if PL_CONFIG_USE_NVMC #include "nvmc.h" #endif #if McuFlash_CONFIG_IS_ENABLED #include "McuFlash.h" #endif #if PL_CONFIG_USE_RS485 #include "rs485.h" #include "McuUart485.h" #endif #if PL_CONFIG_USE_MININI #include "minIni/McuMinINI.h" #endif #if PL_HAS_RADIO #include "RNet/McuRNet.h" #include "RNet/RStdIO.h" #include "RNet_App.h" #endif #if PL_CONFIG_USE_ROBO_REMOTE #include "robot.h" #endif #if PL_CONFIG_USE_CHALLENGE #include "challenge.h" #endif #if McuLib_CONFIG_CPU_IS_ESP32 #include "driver/uart.h" #include "driver/gpio.h" #endif #if PL_CONFIG_USE_MINI #include "minIni/McuMinINI.h" #endif #if PL_CONFIG_USE_TUD_CDC #include "McuShellCdcDevice.h" #endif #include "application.h" /* table of shell parsers */ static const McuShell_ParseCommandCallback CmdParserTable[] = { McuShell_ParseCommand, McuRTOS_ParseCommand, McuLog_ParseCommand, McuTimeDate_ParseCommand, #if PL_CONFIG_USE_BUTTONS BTN_ParseCommand, #endif #if McuLib_CONFIG_CPU_IS_ARM_CORTEX_M McuArmTools_ParseCommand, #endif #if PL_CONFIG_USE_WIFI && McuLib_CONFIG_CPU_IS_ESP32 WiFi_ParseCommand, #endif #if PL_CONFIG_USE_WIFI && PL_CONFIG_USE_PICO_W PicoWiFi_ParseCommand, #endif #if PL_CONFIG_USE_UDP_CLIENT UDP_Client_ParseCommand, #endif #if PL_CONFIG_USE_UDP_SERVER UDP_Server_ParseCommand, #endif #if PL_CONFIG_USE_PING PING_ParseCommand, #endif #if PL_CONFIG_USE_SENSOR && PL_CONFIG_USE_SHT31 McuSHT31_ParseCommand, #elif PL_CONFIG_USE_SENSOR && PL_CONFIG_USE_SHT40 McuSHT40_ParseCommand, #endif #if PL_CONFIG_USE_RS485 && McuUart485_CONFIG_USE_RS_485 McuUart485_ParseCommand, #endif #if PL_CONFIG_USE_RS485 && PL_CONFIG_USE_RS485_SHELL RS485_ParseCommand, #endif #if PL_HAS_RADIO McuRNet_ParseCommand, RNETA_ParseCommand, #endif #if PL_CONFIG_USE_LEDS Leds_ParseCommand, #endif #if PL_CONFIG_USE_BLINKY Blinky_ParseCommand, #endif #if PL_CONFIG_USE_ROBO_REMOTE ROBOT_ParseCommand, #endif #if PL_CONFIG_USE_CHALLENGE Challenge_ParseCommand, #endif #if McuFlash_CONFIG_IS_ENABLED McuFlash_ParseCommand, #endif #if PL_CONFIG_USE_MINI McuMinINI_ParseCommand, ini_ParseCommand, #endif #if PL_CONFIG_USE_MQTT_CLIENT MqttClient_ParseCommand, #endif #if PL_CONFIG_USE_NTP_CLIENT NtpClient_ParseCommand, #endif #if PL_CONFIG_USE_TUD_CDC McuShellCdcDevice_ParseCommand, #endif NULL /* Sentinel */ }; typedef struct SHELL_IODesc { McuShell_ConstStdIOType *stdio; unsigned char *buf; size_t bufSize; } SHELL_IODesc; #if McuLib_CONFIG_CPU_IS_RPxxxx && PL_CONFIG_USE_USB_CDC static void cdc_StdIOReadChar(uint8_t *c) { int res; res = getchar_timeout_us(500); if (res==-1) { /* no character present */ *c = '\0'; } else { *c = (uint8_t)res; /* return character */ } } bool cdc_StdIOKeyPressed(void) { return true; /* hack, don't know if there is any other way? */ } void cdc_StdIOSendChar(uint8_t ch) { putchar_raw(ch); } /* default standard I/O struct */ static McuShell_ConstStdIOType cdc_stdio = { .stdIn = (McuShell_StdIO_In_FctType)cdc_StdIOReadChar, .stdOut = (McuShell_StdIO_OutErr_FctType)cdc_StdIOSendChar, .stdErr = (McuShell_StdIO_OutErr_FctType)cdc_StdIOSendChar, .keyPressed = cdc_StdIOKeyPressed, /* if input is not empty */ #if McuShell_CONFIG_ECHO_ENABLED .echoEnabled = false, #endif }; static uint8_t cdc_DefaultShellBuffer[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; /* default buffer which can be used by the application */ #endif #if McuLib_CONFIG_CPU_IS_ESP32 #define SHELL_ESP32_UART_DEVICE (UART_NUM_0) /* Uart for bootloader and connection to robot */ static SemaphoreHandle_t SHELL_stdioMutex; /* mutex to protect access to ESP32 standard I/O */ static void Uart_SendString(const unsigned char *str) { size_t len; int written; len = strlen((const char*)str); written = uart_write_bytes(SHELL_ESP32_UART_DEVICE, (const char*)str, len); if (written!=len) { McuLog_error("failed sending uart bytes"); } } static void Uart_SendChar(unsigned char ch) { uart_write_bytes(SHELL_ESP32_UART_DEVICE, &ch, 1); } static void Uart_ReadChar(uint8_t *c) { unsigned char ch = '\0'; int len = 0; if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ len = uart_read_bytes(SHELL_ESP32_UART_DEVICE, &ch, 1, 0); (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */ } if (len==0) { *c = '\0'; } else { *c = ch; } } static bool Uart_CharPresent(void) { size_t size=0; if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ uart_get_buffered_data_len(SHELL_ESP32_UART_DEVICE, &size); (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */ } return size!=0; } static McuShell_ConstStdIOType Uart_stdio = { .stdIn = (McuShell_StdIO_In_FctType)Uart_ReadChar, .stdOut = (McuShell_StdIO_OutErr_FctType)Uart_SendChar, .stdErr = (McuShell_StdIO_OutErr_FctType)Uart_SendChar, .keyPressed = Uart_CharPresent, /* if input is not empty */ #if McuShell_CONFIG_ECHO_ENABLED .echoEnabled = true, #endif }; static uint8_t Uart_DefaultShellBuffer[McuShell_DEFAULT_SHELL_BUFFER_SIZE]; /* default buffer which can be used by the application */ #endif /* McuLib_CONFIG_CPU_IS_ESP */ static const SHELL_IODesc ios[] = { #if McuLib_CONFIG_CPU_IS_ESP32 {&Uart_stdio, Uart_DefaultShellBuffer, sizeof(Uart_DefaultShellBuffer)}, #endif #if PL_CONFIG_USE_SHELL_UART {&McuShellUart_stdio, McuShellUart_DefaultShellBuffer, sizeof(McuShellUart_DefaultShellBuffer)}, #endif #if PL_CONFIG_USE_USB_CDC {&cdc_stdio, cdc_DefaultShellBuffer, sizeof(cdc_DefaultShellBuffer)}, #endif #if PL_CONFIG_USE_SHELL_CDC {&McuShellCdcDevice_stdio, McuShellCdcDevice_DefaultShellBuffer, sizeof(McuShellCdcDevice_DefaultShellBuffer)}, #endif #if PL_CONFIG_USE_RTT {&McuRTT_stdio, McuRTT_DefaultShellBuffer, sizeof(McuRTT_DefaultShellBuffer)}, #endif #if RNET_CONFIG_REMOTE_STDIO {&RSTDIO_stdio, RSTDIO_DefaultShellBuffer, sizeof(RSTDIO_DefaultShellBuffer)}, #endif }; void SHELL_SendChar(unsigned char ch) { for(int i=0;istdOut); } } uint8_t SHELL_ParseCommand(unsigned char *cmd) { return McuShell_ParseWithCommandTable(cmd, McuShell_GetStdio(), CmdParserTable); } void SHELL_SendString(const unsigned char *str) { #if McuLib_CONFIG_CPU_IS_ESP32 /* need to improve write speed, as writing character by character is too slow */ Uart_SendString(str); #else for(int i=0;istdOut); } #endif } void SHELL_SendStringToIO(const unsigned char *str, McuShell_ConstStdIOType *io) { #if McuLib_CONFIG_CPU_IS_ESP32 if (io->stdOut == Uart_SendChar) { /* ESP32 UART? */ /* if out channel is ESP32 UART: speed it up by sending whole buffer */ Uart_SendString(str); } else { /* send it char by char */ McuShell_SendStr(str, io->stdOut); } #else McuShell_SendStr(str, io->stdOut); #endif } uint8_t SHELL_ParseCommandIO(const unsigned char *command, McuShell_ConstStdIOType *io, bool silent) { if (io==NULL) { /* use a default */ #if PL_CONFIG_USE_SHELL_UART io = &McuShellUart_stdio; #elif PL_CONFIG_USE_USB_CDC io = &cdc_stdio; #elif PL_CONFIG_USE_RTT io = &McuRTT_stdio; #else #error "no shell std IO?" #endif } return McuShell_ParseWithCommandTableExt(command, io, CmdParserTable, silent); } #if PL_CONFIG_USE_ROTOT_HAT /* ----------------- buffer handling for shell messages sent to ESP32 */ static unsigned char *esp_io_buf; /* pointer to buffer */ static size_t esp_io_buf_size; /* size of buffer */ static void esp_io_buf_SendChar(unsigned char ch) { McuUtility_chcat(esp_io_buf, esp_io_buf_size, ch); } static void esp_io_buf_ReadChar(uint8_t *c) { *c = '\0'; } static bool esp_io_buf_CharPresent(void) { return false; } static McuShell_ConstStdIOType esp_stdio = { .stdIn = (McuShell_StdIO_In_FctType)esp_io_buf_ReadChar, .stdOut = (McuShell_StdIO_OutErr_FctType)esp_io_buf_SendChar, .stdErr = (McuShell_StdIO_OutErr_FctType)esp_io_buf_SendChar, .keyPressed = esp_io_buf_CharPresent, /* if input is not empty */ #if McuShell_CONFIG_ECHO_ENABLED .echoEnabled = false, /* echo enabled for idf.py monitor */ #endif }; void SHELL_SendToESPAndGetResponse(const unsigned char *msg, unsigned char *response, size_t responseSize) { esp_io_buf = response; esp_io_buf_size = responseSize; esp_io_buf[0] = '\0'; /* initialize buffer */ McuLog_info("Sending to ESP Shell: %s", msg); McuShell_ParseWithCommandTableExt(msg, &esp_stdio, CmdParserTable, true); /* send to ESP32 shell */ if (response[0]=='\0') { /* empty response? add a default */ McuUtility_strcpy(response, responseSize, (unsigned char*)"OK"); /* default response */ } } /* ----------------------------------------------------------------------*/ void SHELL_SendToRobotAndGetResponse(const unsigned char *send, unsigned char *response, size_t responseSize) { unsigned char buffer[128]; /* buffer for sending command to robot */ /* build a frame around the message: that way the robot is able to recognize it */ McuUtility_strcpy(buffer, sizeof(buffer), (unsigned char*)"@robot:cmd "); McuUtility_strcat(buffer, sizeof(buffer), send); McuUtility_strcat(buffer, sizeof(buffer), (unsigned char*)"!\r\n"); SHELL_SendString(buffer); /* send to UART, which is read by the robot */ /* get response */ #if 1 /* Important: this consumes directly all characters coming from the robot. That way the ESP32 shell does not get it. * A mutex is used to block the shell from getting the UART stream. */ #define TIMEOUT_MS (500) /* stop if we don't get new input after this timeout */ int timeoutMs = TIMEOUT_MS; *response = '\0'; if (xSemaphoreTakeRecursive(SHELL_stdioMutex, portMAX_DELAY)==pdPASS) { /* take mutex */ while (true) { /* breaks after timeout */ if (!Uart_stdio.keyPressed()) { /* no input: wait for timeout */ timeoutMs -= 50; if (timeoutMs<=0) { break; /* timeout */ } vTaskDelay(pdMS_TO_TICKS(50)); } else { /* character available */ unsigned char ch; Uart_stdio.stdIn(&ch); if (ch!='\r') { /* filter out '\r' in "\r\n" */ McuUtility_chcat(response, responseSize, ch); } timeoutMs = TIMEOUT_MS; /* reset timeout */ } /* if */ } /* while */ (void)xSemaphoreGiveRecursive(SHELL_stdioMutex); /* give back mutex */ } if (*response=='\0') { /* if response is empty, send back at least an acknowledgment */ McuUtility_strcpy(response, responseSize, (unsigned char*)"OK"); /* default response */ } #else McuUtility_strcpy(response, responseSize, (unsigned char*)"OK"); /* default response */ #endif } #endif /* PL_CONFIG_USE_ROTOT_HAT */ /* ----------------------------------------------------------------------*/ static void ShellTask(void *pvParameters) { int i; (void)pvParameters; /* not used */ McuLog_trace("started shell task"); /* initialize buffers */ for(i=0;i