/*
 * Copyright (c) 2019, Erich Styger
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include "McuESP32config.h"
#if McuESP32_CONFIG_SHELL_UART!=McuShellUart_CONFIG_UART_NONE
#include "McuESP32.h"
#include "McuGPIO.h"
#include "McuRTOS.h"
#include "McuUtility.h"
#include "McuWait.h"
#include "McuLog.h"
#include "McuShellUart.h"
#if McuESP32_CONFIG_USE_USB_CDC
  #include "virtual_com.h"
#endif

#if McuESP32_CONFIG_USE_CTRL_PINS
  static McuGPIO_Handle_t McuESP32_RF_EN_Pin;  /* pin pulled LOW to reset the module */
  static McuGPIO_Handle_t McuESP32_RF_IO0_Pin; /* pin pulled LOW to enable programming mode */
#endif
static QueueHandle_t uartRxQueue;  /* Rx from ESP32 module */
#define McuESP32_UART_RX_QUEUE_LENGTH                 (4096)
static QueueHandle_t uartTxQueue;  /* Tx to ESP32 module */
#define McuESP32_UART_TX_QUEUE_LENGTH                 (4096)

#if McuESP32_CONFIG_USE_USB_CDC
  typedef enum McuESP32_USB_PrgMode_e {
    McuESP32_USB_PRG_MODE_AUTO,
    McuESP32_USB_PRG_MODE_ON,
    McuESP32_USB_PRG_MODE_OFF,
  } McuESP32_USB_PrgMode_e;
  static McuESP32_USB_PrgMode_e McuESP32_UsbPrgMode = McuESP32_USB_PRG_MODE_AUTO;
  static bool McuESP32_IsProgramming = false; /* if we are currently programming the ESP32 */
  static bool McuESP32_ScheduleReset = true; /* do an initial reset at restart time */
#endif
static bool McuESP32_CopyUartToShell = true; /* if we copy the ESP32 UART to the Shell */

/* Below is the I/O handler for the console: data from the ESP is sent to that stdout (e.g. shell console).
 * Optionally with McuESP32_CONFIG_USE_USB_CDC enabled all CDC data is sent to the ESP32 as well.
 */
static McuShell_ConstStdIOType *McuESP32_RxFromESPStdIO = NULL; /* can be overwritten with McuESP32_SetRxFromESPStdio(); */

void McuESP32_SetRxFromESPStdio(McuShell_ConstStdIOTypePtr stdio) {
 McuESP32_RxFromESPStdIO = stdio;
}

McuShell_ConstStdIOTypePtr McuESP32_GetRxFromESPStdio(void) {
  return McuESP32_RxFromESPStdIO;
}

#if McuESP32_CONFIG_USE_CTRL_PINS
static void AssertReset(void) {
  McuGPIO_SetAsOutput(McuESP32_RF_EN_Pin, false); /* output, LOW */
}
#endif

#if McuESP32_CONFIG_USE_CTRL_PINS
static void DeassertReset(void) {
  McuGPIO_SetAsInput(McuESP32_RF_EN_Pin);
}
#endif

#if McuESP32_CONFIG_USE_CTRL_PINS
static void DoReset(void) {
  AssertReset();
  vTaskDelay(pdMS_TO_TICKS(1));
  DeassertReset();
}
#endif

#if McuESP32_CONFIG_USE_CTRL_PINS
static void AssertBootloaderMode(void) {
  McuGPIO_SetAsOutput(McuESP32_RF_IO0_Pin, false); /* output, LOW */
}
#endif

#if McuESP32_CONFIG_USE_CTRL_PINS
static void DeassertBootloaderMode(void) {
  McuGPIO_SetAsInput(McuESP32_RF_IO0_Pin);
}
#endif

#if McuESP32_CONFIG_USE_USB_CDC
/* idf.py flash sequence:
 *
 * 00> State: 3, DtrRts: 3

 * 00> State: 2, DtrRts: 1
 * 00> State: 3, DtrRts: 3
 * 00> State: 1, DtrRts: 2
 * 00> State: 0, DtrRts: 0

 * 00> State: 2, DtrRts: 1
 * 00> State: 3, DtrRts: 3
 * 00> State: 1, DtrRts: 2
 * 00> State: 0, DtrRts: 0
 *
 * reset at the end:
 * 00> State: 2, DtrRts: 1
 * 00> State: 0, DtrRts: 0
 */

void McuESP32_UartState_Callback(uint8_t state) { /* callback for DTR and RTS lines */
  static uint8_t prevState = -1;
  static uint8_t prevPrevState = -1;
  uint8_t DtrRts;

#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
  McuLog_trace("state: %d, prev: %d, prevprev: %d", state, prevState, prevPrevState);
#endif
  if (state != prevState) {
    if (McuESP32_UsbPrgMode==McuESP32_USB_PRG_MODE_AUTO || McuESP32_UsbPrgMode==McuESP32_USB_PRG_MODE_ON) {
      /*
       * DTR  RTS  EN  GPIO0
       * 1    1    1   1
       * 0    0    1   1
       * 1    0    0   0
       * 0    1    1   0
       */
      DtrRts = 0;
      if ((state&1)==1) { /* DTR */
        DtrRts |= 2; /* DTR set */
      }
      if ((state&2)==2) { /* DTR */
        DtrRts |= 1; /* RTS set */
      }
    #if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
      McuLog_trace("State: %d, DtrRts: %d", state, DtrRts);
    #endif
      switch(DtrRts) {
        default:
        case 0:
          DeassertReset();
          McuWait_Waitus(100); /* block for a short time (in the ISR!!!) ==> should have a 100 uF added to the reset line */
          DeassertBootloaderMode();
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
         McuLog_trace("Release both: %d", DtrRts);
#endif
          break;
        case 1:
          AssertBootloaderMode();
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
          McuLog_trace("assert BL: %d", DtrRts);
#endif
          break;
        case 2:
          if (McuGPIO_IsLow(McuESP32_RF_EN_Pin)) {
            if (McuGPIO_IsLow(McuESP32_RF_IO0_Pin)) {
              McuESP32_IsProgramming = true; /* the DeassertReset() below will enter bootloader mode */
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
              McuLog_trace("Enter Bootloader Mode");
#endif
            } else {
              McuESP32_IsProgramming = false; /* the DeassertReset() below will do a reset without bootloader */
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
              McuLog_trace("Reset");
#endif
            }
          }
          DeassertReset();
          McuWait_Waitus(100); /* block for a short time (in the ISR!!!) ==> should have a 100 uF added to the reset line */
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
          McuLog_trace("release reset: %d", DtrRts);
#endif
          break;
        case 3:
          AssertReset();
          //McuLog_trace("assert reset: %d", DtrRts);
          break;
      } /* switch */
      if (state==0 && prevState==2 && prevPrevState==0) {
        // reset sequence with idf.py and Arduino IDE:
        // State: 0 DtrRts: 0 Release both: 0
        // State: 2 DtrRts: 1 assert BL: 1
        // State: 0 DtrRts: 0 Release both: 0
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
        McuLog_info("Request Reset");
#endif
        McuESP32_ScheduleReset = true; /* cannot do reset sequence here, as called from an interrupt, so we cannot block */
        McuESP32_IsProgramming = false;
      }
    }
    prevPrevState = prevState;
    prevState = state;
  } /* if state!=prevState */
}
#endif

/*********************************************************************************************************/
/* Stdio Handler for sending text to the ESP32 */
static void QueueTxChar(unsigned char ch) {
 (void)xQueueSendToBack(uartTxQueue, &ch, 0); /* put it back in to the Tx queue */
}

static void Dummy_ReadChar(uint8_t *c) {
  *c = '\0'; /* nothing received */
}

static bool Dummy_CharPresent(void) {
  return false;
}

/* for sending data to the ESP32 (tx only) */
static const McuShell_ConstStdIOType McuESP32_Tx_stdio = {
    .stdIn = (McuShell_StdIO_In_FctType)Dummy_ReadChar,
    .stdOut = (McuShell_StdIO_OutErr_FctType)QueueTxChar,
    .stdErr = (McuShell_StdIO_OutErr_FctType)QueueTxChar,
    .keyPressed = Dummy_CharPresent, /* if input is not empty */
 #if McuShell_CONFIG_ECHO_ENABLED
    .echoEnabled = false,
  #endif
  };

McuShell_ConstStdIOTypePtr McuESP32_GetTxToESPStdio(void) {
  return &McuESP32_Tx_stdio;
}
/*********************************************************************************************************/
void McuESP32_CONFIG_UART_IRQ_HANDLER(void) {
  uint8_t data;
  uint32_t flags=0;
  BaseType_t xHigherPriorityTaskWoken;

  flags = McuESP32_CONFIG_UART_GET_FLAGS(McuESP32_CONFIG_UART_DEVICE);
  /* If new data arrived. */
  if (flags&McuESP32_CONFIG_UART_HW_RX_READY_FLAGS) {
    data = McuESP32_CONFIG_UART_READ_BYTE(McuESP32_CONFIG_UART_DEVICE);
    (void)xQueueSendFromISR(uartRxQueue, &data, &xHigherPriorityTaskWoken);
    if (xHigherPriorityTaskWoken != pdFALSE) {
      __DSB();
      vPortYieldFromISR();
    }
  }
  __DSB();
}

static uint8_t McuESP32_PrintHelp(const McuShell_StdIOType *io) {
  McuShell_SendHelpStr((unsigned char*)"esp32", (unsigned char*)"Group of ESP32 WiFi module commands\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  help|status", (unsigned char*)"Shows ESP32 help or status\r\n", io->stdOut);
#if McuESP32_CONFIG_USE_CTRL_PINS
  McuShell_SendHelpStr((unsigned char*)"  reset", (unsigned char*)"Perform reset sequence\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  assert|deassart reset", (unsigned char*)"Assert or deassert reset pin\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  assert|deassart bl", (unsigned char*)"Assert or deassert bootloader pin\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  prg start|stop", (unsigned char*)"Start and stop programming sequence\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  uarttoshell on|off", (unsigned char*)"Copy UART Rx to Shell\r\n", io->stdOut);
#if McuESP32_CONFIG_USE_USB_CDC
  McuShell_SendHelpStr((unsigned char*)"  usbprg auto|on|off", (unsigned char*)"Use USB CDC to UART for programming\r\n", io->stdOut);
#endif
#endif
  McuShell_SendHelpStr((unsigned char*)"  send <cmd>", (unsigned char*)"Send a command or string to the ESP32 (non-blocking), <cmd> can be double quoted\r\n", io->stdOut);
  McuShell_SendHelpStr((unsigned char*)"  sendwait <ms> <cmd>", (unsigned char*)"Send a command or string to the ESP32 and wait ms time for the response, cmd can be double quoted\r\n", io->stdOut);
  return ERR_OK;
}

static uint8_t McuESP32_PrintStatus(const McuShell_StdIOType *io) {
#if McuESP32_CONFIG_USE_CTRL_PINS || McuESP32_CONFIG_USE_USB_CDC
  uint8_t buf[64];
#endif

  McuShell_SendStatusStr((unsigned char*)"esp32", (unsigned char*)"ESP32 status\r\n", io->stdOut);
#if McuESP32_CONFIG_USE_CTRL_PINS
  McuGPIO_GetPinStatusString(McuESP32_RF_EN_Pin, buf, sizeof(buf));
  McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  McuShell_SendStatusStr((unsigned char*)"  EN", buf, io->stdOut);

  McuGPIO_GetPinStatusString(McuESP32_RF_IO0_Pin, buf, sizeof(buf));
  McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
  McuShell_SendStatusStr((unsigned char*)"  IO0", buf, io->stdOut);
#endif
#if McuESP32_CONFIG_USE_USB_CDC
  if (McuESP32_UsbPrgMode==McuESP32_USB_PRG_MODE_ON) {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"on");
    if (McuESP32_CopyUartToShell) {
      McuUtility_strcat(buf, sizeof(buf), (unsigned char*)", nothing will be copied to shell.\r\n");
    } else {
      McuUtility_strcat(buf, sizeof(buf), (unsigned char*)"\r\n");
    }
  } else if (McuESP32_UsbPrgMode==McuESP32_USB_PRG_MODE_AUTO) {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"auto\r\n");
  } else if (McuESP32_UsbPrgMode==McuESP32_USB_PRG_MODE_OFF) {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"off\r\n");
  } else {
    McuUtility_strcpy(buf, sizeof(buf), (unsigned char*)"ERROR\r\n");
  }
  McuShell_SendStatusStr((unsigned char*)"  usbprg", buf, io->stdOut);
  McuShell_SendStatusStr((unsigned char*)"  programming", McuESP32_IsProgramming?(unsigned char*)"yes\r\n":(unsigned char*)"no\r\n", io->stdOut);
#endif

  McuShell_SendStatusStr((unsigned char*)"  uarttoshell", McuESP32_CopyUartToShell?(unsigned char*)"on\r\n":(unsigned char*)"off\r\n", io->stdOut);
  return ERR_OK;
}

uint8_t McuESP32_ParseCommand(const unsigned char *cmd, bool *handled, const McuShell_StdIOType *io) {
  unsigned char cmd_buffer[McuShell_CONFIG_DEFAULT_SHELL_BUFFER_SIZE];
  const unsigned char *p;

  if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_HELP)==0 || McuUtility_strcmp((char*)cmd, (char*)"esp32 help")==0) {
    *handled = true;
    return McuESP32_PrintHelp(io);
  } else if (McuUtility_strcmp((char*)cmd, (char*)McuShell_CMD_STATUS)==0 || McuUtility_strcmp((char*)cmd, (char*)"esp32 status")==0) {
    *handled = true;
    return McuESP32_PrintStatus(io);
#if McuESP32_CONFIG_USE_CTRL_PINS
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 reset")==0) {
    *handled = true;
    DoReset();
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 prg start")==0) {
    /* pulling prg pin low, followed by a reset */
    *handled = true;
    AssertBootloaderMode(); /* pull prg pin low: during reset, device will enter serial programming mode */
    vTaskDelay(pdMS_TO_TICKS(1));
    DoReset();
    vTaskDelay(pdMS_TO_TICKS(1));
    DeassertBootloaderMode();
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 prg stop")==0) {
    /* release prg pin, followed by a reset */
    *handled = true;
    DeassertBootloaderMode(); /* return prg pin to high (normal) again */
    vTaskDelay(pdMS_TO_TICKS(1));
    DoReset();
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 assert bl")==0) {
    *handled = true;
    AssertBootloaderMode();
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 deassert bl")==0) {
    *handled = true;
    DeassertBootloaderMode();
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 assert reset")==0) {
    *handled = true;
    AssertReset();
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 deassert reset")==0) {
    *handled = true;
    DeassertReset();
    return ERR_OK;
#endif
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 uarttoshell on")==0) {
    *handled = true;
    McuESP32_CopyUartToShell = true;
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 uarttoshell off")==0) {
    *handled = true;
    McuESP32_CopyUartToShell = false;
    return ERR_OK;
#if McuESP32_CONFIG_USE_USB_CDC
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 usbprg auto")==0) {
    *handled = true;
    McuESP32_UsbPrgMode = McuESP32_USB_PRG_MODE_AUTO;
    McuESP32_IsProgramming = false;
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 usbprg on")==0) {
    *handled = true;
    McuESP32_UsbPrgMode = McuESP32_USB_PRG_MODE_ON;
    McuESP32_IsProgramming = true;
    return ERR_OK;
  } else if (McuUtility_strcmp((char*)cmd, (char*)"esp32 usbprg off")==0) {
    *handled = true;
    McuESP32_UsbPrgMode = McuESP32_USB_PRG_MODE_OFF;
    McuESP32_IsProgramming = false;
    return ERR_OK;
#endif
  } else if (McuUtility_strncmp((char*)cmd, (char*)"esp32 send ", sizeof("esp32 send ")-1)==0) {
    *handled = true;
    p = cmd+sizeof("esp32 send ")-1;
    if (*p=='"') { /* double-quoted command: it can contain multiple commands */
      if (McuUtility_ScanDoubleQuotedString(&p, cmd_buffer, sizeof(cmd_buffer))!=ERR_OK) {
        return ERR_FAILED;
      }
      p = cmd_buffer;
    }
    McuShell_SendStr(p, McuESP32_GetTxToESPStdio()->stdOut);
    McuShell_SendStr((unsigned char*)"\r\n", McuESP32_GetTxToESPStdio()->stdOut);
    return ERR_OK;
  } else if (McuUtility_strncmp((char*)cmd, (char*)"esp32 sendwait ", sizeof("esp32 sendwait ")-1)==0) {
    /* this sends a command, but captures output with a timeout so it can show it on the io used for the command, e.g. on RTT */
    uint32_t ms;

    *handled = true;
    p = cmd+sizeof("esp32 sendwait ")-1;
    if (McuUtility_ScanDecimal32uNumber(&p, &ms)!=ERR_OK) {
      return ERR_FAILED;
    }
    while(*p==' ') {
      p++; /* skip spaces */
    }
    if (*p=='"') { /* double-quoted command: it can contain multiple commands */
      if (McuUtility_ScanDoubleQuotedString(&p, cmd_buffer, sizeof(cmd_buffer))!=ERR_OK) {
        return ERR_FAILED;
      }
      p = cmd_buffer;
    }
    /* send command string: temporarily change where the response from the ESP32 goes to  */
    McuShell_ConstStdIOType *prevIO;

    prevIO = McuESP32_GetRxFromESPStdio(); /* get current I/O */
    McuESP32_SetRxFromESPStdio(io); /* set current shell I/O as output channel for what's coming from the ESP */
    McuShell_SendStr(p, McuESP32_GetTxToESPStdio()->stdOut);
    McuShell_SendStr((unsigned char*)"\r\n", McuESP32_GetTxToESPStdio()->stdOut);
    vTaskDelay(pdMS_TO_TICKS(ms)); /* wait for the ESP to send the response */
    McuESP32_SetRxFromESPStdio(prevIO); /* restore previous io */

    return ERR_OK;
  }
  return ERR_OK;
}

static void UartRxTask(void *pv) { /* task handling characters sent by the ESP32 module and coming from the UART of the ESP32 */
  unsigned char ch;
  BaseType_t res;
  McuShell_ConstStdIOType *io;

  (void)pv; /* not used */
  for(;;) {
    res = xQueueReceive(uartRxQueue, &ch, portMAX_DELAY);
    if (res==pdPASS) {
  #if McuESP32_CONFIG_USE_USB_CDC
      if (USB_CdcIsConnected()) { /* send directly to programmer attached on the USB or to the IDF monitor */
        USB_CdcStdio.stdOut(ch); /* forward to USB CDC and the programmer on the host */
      }
  #endif
      if (   McuESP32_CopyUartToShell
  #if McuESP32_CONFIG_USE_USB_CDC
          && !McuESP32_IsProgramming
  #endif
         )
      { /* only write to shell if not in programming mode. Programming mode might crash RTT */
        io = McuESP32_GetRxFromESPStdio();
        if (io!=NULL) {
          McuShell_SendCh(ch, io->stdOut); /* forward character */
        }
      }
    } else {
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
      McuLog_fatal("ESP32 UartRxTask queue failed");
#endif
    }
  }
}

static void UartTxTask(void *pv) { /* task handling sending data to the ESP32 module */
  unsigned char ch;
  BaseType_t res;
  bool workToDo;

  (void)pv; /* not used */
  for(;;) {
#if McuESP32_CONFIG_USE_USB_CDC
    if (McuESP32_ScheduleReset) {
      McuESP32_ScheduleReset = false;
#if McuESP32_CONFIG_VERBOSE_CONTROL_SIGNALS
      McuLog_info("Performing reset");
#endif
      DoReset();
    }
#endif
    workToDo = false;
    do {
      res = xQueueReceive(uartTxQueue, &ch, 0); /* poll queue */
      if (res==pdPASS) { /* write data to ESP over UART */
        workToDo = true;
        McuESP32_CONFIG_UART_WRITE_BLOCKING(McuESP32_CONFIG_UART_DEVICE, &ch, 1); /* send to ESP */
      }
    } while (res==pdPASS);
#if McuESP32_CONFIG_USE_USB_CDC
    while (USB_CdcStdio.keyPressed()) { /* check USB CDC data stream */
      workToDo = true;
      USB_CdcStdio.stdIn(&ch); /* read byte */
      McuESP32_CONFIG_UART_WRITE_BLOCKING(McuESP32_CONFIG_UART_DEVICE, &ch, 1); /* send to ESP */
      /* check if we can copy the USB CDC data to shell console too */
      if (McuESP32_CopyUartToShell && !McuESP32_IsProgramming) {
        McuShell_ConstStdIOTypePtr io = McuESP32_GetRxFromESPStdio();
        if (io!=NULL) {
          McuShell_SendCh(ch, io->stdOut); /* write to console */
        }
      }
    }
#endif
    if (!workToDo) { /* only delay if we are not busy */
      vTaskDelay(pdMS_TO_TICKS(5));
    }
  }
}

static void InitUart(void) {
  McuESP32_CONFIG_UART_CONFIG_STRUCT config;

  McuESP32_CONFIG_UART_SET_UART_CLOCK();
  McuESP32_CONFIG_UART_GET_DEFAULT_CONFIG(&config);
  config.baudRate_Bps = McuESP32_CONFIG_UART_BAUDRATE;
  config.enableRx     = true;
  config.enableTx     = true;

  /* Initialize the USART with configuration. */
  McuESP32_CONFIG_UART_INIT(McuESP32_CONFIG_UART_DEVICE, &config, CLOCK_GetFreq(McuESP32_CONFIG_UART_GET_CLOCK_FREQ_SELECT));
  McuESP32_CONFIG_UART_ENABLE_INTERRUPTS(McuESP32_CONFIG_UART_DEVICE, McuESP32_CONFIG_UART_ENABLE_INTERRUPT_FLAGS);
  EnableIRQ(McuESP32_CONFIG_UART_IRQ_NUMBER);
  NVIC_SetPriority(McuESP32_CONFIG_UART_IRQ_NUMBER, configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY);

  McuShellUart_MuxUartPins(McuESP32_CONFIG_SHELL_UART); /* mux the UART pins */

  uartRxQueue = xQueueCreate(McuESP32_UART_RX_QUEUE_LENGTH, sizeof(uint8_t));
  if (uartRxQueue==NULL) {
    McuLog_fatal("not able to create Rx queue");
    for(;;){} /* out of memory? */
  }
  vQueueAddToRegistry(uartRxQueue, "ESP32UartRxQueue");

  uartTxQueue = xQueueCreate(McuESP32_UART_TX_QUEUE_LENGTH, sizeof(uint8_t));
  if (uartTxQueue==NULL) {
    McuLog_fatal("not able to create Tx queue");
    for(;;){} /* out of memory? */
  }
  vQueueAddToRegistry(uartTxQueue, "ESP32UartTxQueue");
}

static void InitPins(void) {
#if McuESP32_CONFIG_USE_CTRL_PINS
  McuGPIO_Config_t gpioConfig;

  McuGPIO_GetDefaultConfig(&gpioConfig);
  gpioConfig.isInput = true;
  gpioConfig.hw.gpio = McuESP32_CONFIG_EN_GPIO;
  gpioConfig.hw.port = McuESP32_CONFIG_EN_PORT;
  gpioConfig.hw.pin = McuESP32_CONFIG_EN_PIN;
  McuESP32_RF_EN_Pin = McuGPIO_InitGPIO(&gpioConfig);

  gpioConfig.hw.gpio = McuESP32_CONFIG_RST_GPIO;
  gpioConfig.hw.port = McuESP32_CONFIG_RST_PORT;
  gpioConfig.hw.pin = McuESP32_CONFIG_RST_PIN;
  McuESP32_RF_IO0_Pin = McuGPIO_InitGPIO(&gpioConfig);
#endif
}

void McuESP32_Deinit(void) {
#if McuESP32_CONFIG_USE_CTRL_PINS
  McuESP32_RF_EN_Pin = McuGPIO_DeinitGPIO(McuESP32_RF_EN_Pin);
  McuESP32_RF_IO0_Pin = McuGPIO_DeinitGPIO(McuESP32_RF_IO0_Pin);
#endif
  vQueueDelete(uartRxQueue);
  uartRxQueue = NULL;
}

void McuESP32_Init(void) {
  InitPins();
  InitUart();
  if (xTaskCreate(
      UartRxTask,  /* pointer to the task */
      "ESP32UartRx", /* task name for kernel awareness debugging */
      500/sizeof(StackType_t), /* task stack size */
      (void*)NULL, /* optional task startup argument */
      tskIDLE_PRIORITY+4,  /* initial priority */
      (TaskHandle_t*)NULL /* optional task handle to create */
    ) != pdPASS)
  {
    McuLog_fatal("failed creating ESP32 Rx Task");
    for(;;){} /* error! probably out of memory */
  }
  if (xTaskCreate(
      UartTxTask,  /* pointer to the task */
      "ESP32UartTx", /* task name for kernel awareness debugging */
      500/sizeof(StackType_t), /* task stack size */
      (void*)NULL, /* optional task startup argument */
      tskIDLE_PRIORITY+4,  /* initial priority */
      (TaskHandle_t*)NULL /* optional task handle to create */
    ) != pdPASS)
  {
    McuLog_fatal("failed creating ESP32 Tx Task");
    for(;;){} /* error! probably out of memory */
  }
}
#endif /* #if McuESP32_CONFIG_SHELL_UART */
