跳转至

rocketpi_uart_shell_microrl

效果展示

shell

功能说明

集成 microrl 命令行库,在 USART2(115200 8N1)上提供交互式 shell,并扩展了简单的板载功能控制。

  1. shell 接口
  2. 通过串口工具连接 USART2,复位后会显示 IRin > 提示符。
  3. 支持 help / version / echo 等 microrl 默认命令。
  4. 自持自动补全以及历史命令回看

  5. LED 控制命令

  6. 实现 led <blue|green|pink> <on|off|toggle> 指令,可在 shell 中直接点亮/熄灭/翻转三色 LED(LED_B/LED_G/LED_P 引脚)。
  7. 命令支持大小写混输,错误输入会返回提示。

  8. 按键回显优化

  9. 接收中断使用环形缓冲,兼容 CR / CRLF 终端换行,保证回显与命令解析稳定。

驱动以及测试代码

Core/Src/main.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "microrl.h"
#include <string.h>
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef struct {
  const char *name;
  const char *description;
} shell_cmd_desc_t;

typedef struct {
  const char *name;
  GPIO_TypeDef *port;
  uint16_t pin;
} led_desc_t;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define SHELL_RX_BUFFER_SIZE 128U
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define LED_ON_STATE GPIO_PIN_RESET
#define LED_OFF_STATE GPIO_PIN_SET
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
static microrl_t s_shell;
static uint8_t s_uart2_rx;
static volatile uint8_t s_shell_rx_buffer[SHELL_RX_BUFFER_SIZE];
static volatile uint16_t s_shell_rx_head;
static volatile uint16_t s_shell_rx_tail;

static const shell_cmd_desc_t kShellCmdTable[] = {
  {"help", "List available commands"},
  {"version", "Show shell information"},
  {"echo", "Echo back the provided text"},
  {"led", "Control on-board LEDs"},
};

static const led_desc_t kLedTable[] = {
  {"blue", LED_B_GPIO_Port, LED_B_Pin},
  {"green", LED_G_GPIO_Port, LED_G_Pin},
  {"pink", LED_P_GPIO_Port, LED_P_Pin},
};

static const char *kLedActionTable[] = {"on", "off", "toggle"};
static const char *s_shell_completion[_COMMAND_TOKEN_NMB + 1];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
static void shell_print(const char *str);
static void shell_print_line(const char *text);
static void shell_show_banner(void);
static void shell_start_rx(void);
static void shell_queue_char(uint8_t value);
static void shell_process_input(void);
static int shell_execute(int argc, const char * const * argv);
static int shell_cmd_led(int argc, const char * const * argv);
static void shell_print_led_usage(void);
static int shell_str_casecmp(const char *lhs, const char *rhs);
static const led_desc_t *shell_find_led(const char *name);
static size_t shell_add_completion(const char *candidate, size_t index);
static int shell_prefix_match(const char *token, const char *candidate);
static char **shell_complete(int argc, const char * const * argv);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* 通过 USART2 输出字符串(LF 自动转换为 CRLF) */
static void shell_print(const char *str)
{
  if ((str == NULL) || (huart2.gState == HAL_UART_STATE_RESET)) {
    return;
  }
  while (*str != '\0') {
    if (*str == '\n') {
      const uint8_t newline[2] = {'\r', '\n'};
      HAL_UART_Transmit(&huart2, (uint8_t *)newline, sizeof(newline), HAL_MAX_DELAY);
    } else {
      uint8_t ch = (uint8_t)(*str);
      HAL_UART_Transmit(&huart2, &ch, 1, HAL_MAX_DELAY);
    }
    str++;
  }
}

/* 打印一行文本并追加换行 */
static void shell_print_line(const char *text)
{
  if (text != NULL) {
    shell_print(text);
  }
  shell_print(ENDL);
}

/* 输出开机提示语 */
static void shell_show_banner(void)
{
  shell_print_line("");
  shell_print_line("RocketPi USART2 microrl shell ready.");
}

/* 启动一次 USART2 中断接收 */
static void shell_start_rx(void)
{
  HAL_UART_Receive_IT(&huart2, &s_uart2_rx, 1);
}

/* 将接收的字节写入环形缓冲区 */
static void shell_queue_char(uint8_t value)
{
  uint16_t next_head = (uint16_t)((s_shell_rx_head + 1U) % SHELL_RX_BUFFER_SIZE);
  if (next_head != s_shell_rx_tail) {
    s_shell_rx_buffer[s_shell_rx_head] = value;
    s_shell_rx_head = next_head;
  }
}

/* 在主循环中解析缓冲区数据 */
static void shell_process_input(void)
{
  while (s_shell_rx_head != s_shell_rx_tail) {
    uint8_t ch = s_shell_rx_buffer[s_shell_rx_tail];
    s_shell_rx_tail = (uint16_t)((s_shell_rx_tail + 1U) % SHELL_RX_BUFFER_SIZE);
    if (ch == '\r') {
      ch = '\n';
      if (s_shell_rx_head != s_shell_rx_tail) {
        uint8_t peek = s_shell_rx_buffer[s_shell_rx_tail];
        if (peek == '\n') {
          s_shell_rx_tail = (uint16_t)((s_shell_rx_tail + 1U) % SHELL_RX_BUFFER_SIZE);
        }
      }
    }
    microrl_insert_char(&s_shell, ch);
  }
}

/* microrl 执行回调,根据命令执行功能 */
static int shell_execute(int argc, const char * const * argv)
{
  if (argc <= 0) {
    return 0;
  }

  if (strcmp(argv[0], "help") == 0) {
    shell_print_line("Available commands:");
    for (size_t i = 0; i < ARRAY_SIZE(kShellCmdTable); ++i) {
      shell_print("  ");
      shell_print(kShellCmdTable[i].name);
      shell_print(" - ");
      shell_print(kShellCmdTable[i].description);
      shell_print(ENDL);
    }
    return 0;
  }

  if (strcmp(argv[0], "version") == 0) {
    shell_print("microrl ");
    shell_print(MICRORL_LIB_VER);
    shell_print_line(" on USART2 (115200 8N1)");
    return 0;
  }

  if (strcmp(argv[0], "echo") == 0) {
    for (int i = 1; i < argc; ++i) {
      shell_print(argv[i]);
      if (i < (argc - 1)) {
        shell_print(" ");
      }
    }
    shell_print(ENDL);
    return 0;
  }

  if (strcmp(argv[0], "led") == 0) {
    return shell_cmd_led(argc, argv);
  }

  shell_print("Unknown command: ");
  shell_print(argv[0]);
  shell_print(ENDL);
  shell_print_line("Type 'help' to list commands.");
  return 0;
}

/* LED 命令:led <颜色> <on|off|toggle> */
static int shell_cmd_led(int argc, const char * const * argv)
{
  if (argc < 3) {
    shell_print_led_usage();
    return -1;
  }

  const led_desc_t *led = shell_find_led(argv[1]);
  if (led == NULL) {
    shell_print("Unknown LED name: ");
    shell_print(argv[1]);
    shell_print(ENDL);
    shell_print_led_usage();
    return -1;
  }

  const char *action = argv[2];
  if (shell_str_casecmp(action, "on") == 0) {
    HAL_GPIO_WritePin(led->port, led->pin, LED_ON_STATE);
    shell_print("LED ");
    shell_print(led->name);
    shell_print_line(" is ON");
    return 0;
  }

  if (shell_str_casecmp(action, "off") == 0) {
    HAL_GPIO_WritePin(led->port, led->pin, LED_OFF_STATE);
    shell_print("LED ");
    shell_print(led->name);
    shell_print_line(" is OFF");
    return 0;
  }

  if (shell_str_casecmp(action, "toggle") == 0) {
    HAL_GPIO_TogglePin(led->port, led->pin);
    shell_print("LED ");
    shell_print(led->name);
    shell_print_line(" toggled");
    return 0;
  }

  shell_print("Unknown LED action: ");
  shell_print(action);
  shell_print(ENDL);
  shell_print_led_usage();
  return -1;
}

/* 打印 LED 命令可选参数 */
static void shell_print_led_usage(void)
{
  shell_print_line("Usage: led <blue|green|pink> <on|off|toggle>");
}

/* 忽略大小写比较字符串 */
static int shell_str_casecmp(const char *lhs, const char *rhs)
{
  while ((*lhs != '\0') && (*rhs != '\0')) {
    char lc = (*lhs >= 'A' && *lhs <= 'Z') ? (char)(*lhs + ('a' - 'A')) : *lhs;
    char rc = (*rhs >= 'A' && *rhs <= 'Z') ? (char)(*rhs + ('a' - 'A')) : *rhs;
    if (lc != rc) {
      return (int)(lc - rc);
    }
    ++lhs;
    ++rhs;
  }
  return (int)((unsigned char)*lhs - (unsigned char)*rhs);
}

/* 在 LED 描述表中查找指定名称 */
static const led_desc_t *shell_find_led(const char *name)
{
  for (size_t i = 0; i < ARRAY_SIZE(kLedTable); ++i) {
    if (shell_str_casecmp(name, kLedTable[i].name) == 0) {
      return &kLedTable[i];
    }
  }
  return NULL;
}

/* 把候选词添加到补全数组 */
static size_t shell_add_completion(const char *candidate, size_t index)
{
  if ((candidate != NULL) && (index < (ARRAY_SIZE(s_shell_completion) - 1U))) {
    s_shell_completion[index++] = candidate;
  }
  return index;
}

/* 判断 token 是否是候选词的前缀 */
static int shell_prefix_match(const char *token, const char *candidate)
{
  if ((token == NULL) || (*token == '\0')) {
    return 1;
  }
  size_t prefix_len = strlen(token);
  return strncmp(candidate, token, prefix_len) == 0;
}

/* TAB 自动补全回调 */
static char **shell_complete(int argc, const char * const * argv)
{
  size_t idx = 0;
  memset((void *)s_shell_completion, 0, sizeof(s_shell_completion));

  const char *current = "";
  if (argc > 0) {
    current = argv[argc - 1];
    if (current == NULL) {
      current = "";
    }
  }

  if (argc <= 1) {
    for (size_t i = 0; i < ARRAY_SIZE(kShellCmdTable); ++i) {
      const char *name = kShellCmdTable[i].name;
      if (shell_prefix_match(current, name)) {
        idx = shell_add_completion(name, idx);
      }
    }
    return (char **)s_shell_completion;
  }

  if (strcmp(argv[0], "led") != 0) {
    return (char **)s_shell_completion;
  }

  if (argc == 2) {
    for (size_t i = 0; i < ARRAY_SIZE(kLedTable); ++i) {
      if (shell_prefix_match(current, kLedTable[i].name)) {
        idx = shell_add_completion(kLedTable[i].name, idx);
      }
    }
  } else if (argc == 3) {
    for (size_t i = 0; i < ARRAY_SIZE(kLedActionTable); ++i) {
      if (shell_prefix_match(current, kLedActionTable[i])) {
        idx = shell_add_completion(kLedActionTable[i], idx);
      }
    }
  }

  return (char **)s_shell_completion;
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART2_UART_Init();

  shell_show_banner();
  microrl_init(&s_shell, shell_print);
  microrl_set_execute_callback(&s_shell, shell_execute);
  microrl_set_complete_callback(&s_shell, shell_complete);
  shell_start_rx();

  while (1)
  {
    shell_process_input();
  }
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);

  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = 4;
  RCC_OscInitStruct.PLL.PLLN = 84;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
/* UART 接收完成回调:收集数据并继续接收 */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART2) {
    shell_queue_char(s_uart2_rx);
    shell_start_rx();
  }
}

/* UART 出错回调:重启接收以保持 shell 可用 */
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
  if (huart->Instance == USART2) {
    shell_start_rx();
  }
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
}
#endif /* USE_FULL_ASSERT */
Core/Src/stm32f4xx_it.c
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    stm32f4xx_it.c
  * @brief   Interrupt Service Routines.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2025 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f4xx_it.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "usart.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN TD */

/* USER CODE END TD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/* External variables --------------------------------------------------------*/
extern UART_HandleTypeDef huart2;
/* USER CODE BEGIN EV */

/* USER CODE END EV */

/******************************************************************************/
/*           Cortex-M4 Processor Interruption and Exception Handlers          */
/******************************************************************************/
/**
  * @brief This function handles Non maskable interrupt.
  */
void NMI_Handler(void)
{
  /* USER CODE BEGIN NonMaskableInt_IRQn 0 */

  /* USER CODE END NonMaskableInt_IRQn 0 */
  /* USER CODE BEGIN NonMaskableInt_IRQn 1 */
   while (1)
  {
  }
  /* USER CODE END NonMaskableInt_IRQn 1 */
}

/**
  * @brief This function handles Hard fault interrupt.
  */
void HardFault_Handler(void)
{
  /* USER CODE BEGIN HardFault_IRQn 0 */

  /* USER CODE END HardFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_HardFault_IRQn 0 */
    /* USER CODE END W1_HardFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Memory management fault.
  */
void MemManage_Handler(void)
{
  /* USER CODE BEGIN MemoryManagement_IRQn 0 */

  /* USER CODE END MemoryManagement_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_MemoryManagement_IRQn 0 */
    /* USER CODE END W1_MemoryManagement_IRQn 0 */
  }
}

/**
  * @brief This function handles Pre-fetch fault, memory access fault.
  */
void BusFault_Handler(void)
{
  /* USER CODE BEGIN BusFault_IRQn 0 */

  /* USER CODE END BusFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_BusFault_IRQn 0 */
    /* USER CODE END W1_BusFault_IRQn 0 */
  }
}

/**
  * @brief This function handles Undefined instruction or illegal state.
  */
void UsageFault_Handler(void)
{
  /* USER CODE BEGIN UsageFault_IRQn 0 */

  /* USER CODE END UsageFault_IRQn 0 */
  while (1)
  {
    /* USER CODE BEGIN W1_UsageFault_IRQn 0 */
    /* USER CODE END W1_UsageFault_IRQn 0 */
  }
}

/**
  * @brief This function handles System service call via SWI instruction.
  */
void SVC_Handler(void)
{
  /* USER CODE BEGIN SVCall_IRQn 0 */

  /* USER CODE END SVCall_IRQn 0 */
  /* USER CODE BEGIN SVCall_IRQn 1 */

  /* USER CODE END SVCall_IRQn 1 */
}

/**
  * @brief This function handles Debug monitor.
  */
void DebugMon_Handler(void)
{
  /* USER CODE BEGIN DebugMonitor_IRQn 0 */

  /* USER CODE END DebugMonitor_IRQn 0 */
  /* USER CODE BEGIN DebugMonitor_IRQn 1 */

  /* USER CODE END DebugMonitor_IRQn 1 */
}

/**
  * @brief This function handles Pendable request for system service.
  */
void PendSV_Handler(void)
{
  /* USER CODE BEGIN PendSV_IRQn 0 */

  /* USER CODE END PendSV_IRQn 0 */
  /* USER CODE BEGIN PendSV_IRQn 1 */

  /* USER CODE END PendSV_IRQn 1 */
}

/**
  * @brief This function handles System tick timer.
  */
void SysTick_Handler(void)
{
  /* USER CODE BEGIN SysTick_IRQn 0 */

  /* USER CODE END SysTick_IRQn 0 */
  HAL_IncTick();
  /* USER CODE BEGIN SysTick_IRQn 1 */

  /* USER CODE END SysTick_IRQn 1 */
}

/******************************************************************************/
/* STM32F4xx Peripheral Interrupt Handlers                                    */
/* Add here the Interrupt Handlers for the used peripherals.                  */
/* For the available peripheral interrupt handler names,                      */
/* please refer to the startup file (startup_stm32f4xx.s).                    */
/******************************************************************************/

/**
  * @brief This function handles USART2 global interrupt.
  */
void USART2_IRQHandler(void)
{
  /* USER CODE BEGIN USART2_IRQn 0 */

  /* USER CODE END USART2_IRQn 0 */
  HAL_UART_IRQHandler(&huart2);
  /* USER CODE BEGIN USART2_IRQn 1 */

  /* USER CODE END USART2_IRQn 1 */
}

/* USER CODE BEGIN 1 */

/* USER CODE END 1 */
component/microrl/src/config.h
/*
Microrl library config files
Autor: Eugene Samoylov aka Helius (ghelius@gmail.com)
*/
#ifndef _MICRORL_CONFIG_H_
#define _MICRORL_CONFIG_H_

#define MICRORL_LIB_VER "1.5.1"

/*********** CONFIG SECTION **************/
/*
Command line length, define cmdline buffer size. Set max number of chars + 1,
because last byte of buffer need to contain '\0' - NULL terminator, and 
not use for storing inputed char.
If user input chars more then it parametrs-1, chars not added to command line.*/
#define _COMMAND_LINE_LEN (1+100)                                   // for 32 chars

/*
Command token number, define max token it command line, if number of token 
typed in command line exceed this value, then prints message about it and
command line not to be parced and 'execute' callback will not calls.
Token is word separate by white space, for example 3 token line:
"IRin> set mode test" */
#define _COMMAND_TOKEN_NMB 8

/*
Define you prompt string here. You can use colors escape code, for highlight you prompt,
for example this prompt will green color (if you terminal supports color)*/
//#define _PROMPT_DEFAULT "\033[32mIRin >\033[0m "  // green color
#define _PROMPT_DEFAULT "\033[32mIRin >\033[0m "    // green color
//#define _PROMPT_DEFAULT "IRin > "

/*
Define prompt text (without ESC sequence, only text) prompt length, it needs because if you use
ESC sequence, it's not possible detect only text length*/
#define _PROMPT_LEN       7

/*Define it, if you wanna use completion functional, also set completion callback in you code,
now if user press TAB calls 'copmlitetion' callback. If you no need it, you can just set 
NULL to callback ptr and do not use it, but for memory saving tune, 
if you are not going to use it - disable this define.*/
#define _USE_COMPLETE

/*Define it, if you wanna use history. It s work's like bash history, and
set stored value to cmdline, if UP and DOWN key pressed. Using history add
memory consuming, depends from _RING_HISTORY_LEN parametr */
#define _USE_HISTORY

/*
History ring buffer length, define static buffer size.
For saving memory, each entered cmdline store to history in ring buffer,
so we can not say, how many line we can store, it depends from cmdline len,
but memory using more effective. We not prefer dinamic memory allocation for
small and embedded devices. Overhead is 2 char on each saved line*/
#define _RING_HISTORY_LEN 64

/*
Enable Handling terminal ESC sequence. If disabling, then cursor arrow, HOME, END will not work,
use Ctrl+A(B,F,P,N,A,E,H,K,U,C) see README, but decrease code memory.*/
#define _USE_ESC_SEQ

/*
Use snprintf from you standard complier library, but it gives some overhead.
If not defined, use my own u16int_to_str variant, it's save about 800 byte of code size
on AVR (avr-gcc build).
Try to build with and without, and compare total code size for tune library.
*/
#define _USE_LIBC_STDIO

/*
Enable 'interrupt signal' callback, if user press Ctrl+C */
#define _USE_CTLR_C

/*
Print prompt at 'microrl_init', if enable, prompt will print at startup, 
otherwise first prompt will print after first press Enter in terminal
NOTE!: Enable it, if you call 'microrl_init' after your communication subsystem 
already initialize and ready to print message */
#define _ENABLE_INIT_PROMPT

/*
New line symbol */
#define _ENDL_LF

#if defined(_ENDL_CR)
#define ENDL "\r"
#elif defined(_ENDL_CRLF)
#define ENDL "\r\n"
#elif defined(_ENDL_LF)
#define ENDL "\n"
#elif defined(_ENDL_LFCR)
#define ENDL "\n\r"
#else
#error "You must define new line symbol."
#endif

/********** END CONFIG SECTION ************/


#if _RING_HISTORY_LEN > 256
#error "This history implementation (ring buffer with 1 byte iterator) allow 256 byte buffer size maximum"
#endif

#endif
component/microrl/src/microrl.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
/*
Author: Samoylov Eugene aka Helius (ghelius@gmail.com)
BUGS and TODO:
-- add echo_off feature
-- rewrite history for use more than 256 byte buffer
*/

#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include "microrl.h"
#ifdef _USE_LIBC_STDIO
#include <stdio.h>
#endif

//#define DBG(...) fprintf(stderr, "\033[33m");fprintf(stderr,__VA_ARGS__);fprintf(stderr,"\033[0m");

char * prompt_default = _PROMPT_DEFAULT;

#ifdef _USE_HISTORY

#ifdef _HISTORY_DEBUG
//*****************************************************************************
// print buffer content on screen
static void print_hist (ring_history_t * pThis)
{
    printf ("\n");
    for (int i = 0; i < _RING_HISTORY_LEN; i++) {
        if (i == pThis->begin)
            printf ("b");
        else 
            printf (" ");
    }
    printf ("\n");
    for (int i = 0; i < _RING_HISTORY_LEN; i++) {
        if (isalpha(pThis->ring_buf[i]))
            printf ("%c", pThis->ring_buf[i]);
        else 
            printf ("%d", pThis->ring_buf[i]);
    }
    printf ("\n");
    for (int i = 0; i < _RING_HISTORY_LEN; i++) {
        if (i == pThis->end)
            printf ("e");
        else 
            printf (" ");
    }
    printf ("\n");
}
#endif

//*****************************************************************************
// remove older message from ring buffer
static void hist_erase_older (ring_history_t * pThis)
{
    int new_pos = pThis->begin + pThis->ring_buf [pThis->begin] + 1;
    if (new_pos >= _RING_HISTORY_LEN)
        new_pos = new_pos - _RING_HISTORY_LEN;

    pThis->begin = new_pos;
}

//*****************************************************************************
// check space for new line, remove older while not space
static int hist_is_space_for_new (ring_history_t * pThis, int len)
{
    if (pThis->ring_buf [pThis->begin] == 0)
        return true;
    if (pThis->end >= pThis->begin) {
        if (_RING_HISTORY_LEN - pThis->end + pThis->begin - 1 > len)
            return true;
    }   else {
        if (pThis->begin - pThis->end - 1> len)
            return true;
    }
    return false;
}

//*****************************************************************************
// put line to ring buffer
static void hist_save_line (ring_history_t * pThis, char * line, int len)
{
    if (len > _RING_HISTORY_LEN - 2)
        return;
    while (!hist_is_space_for_new (pThis, len)) {
        hist_erase_older (pThis);
    }
    // if it's first line
    if (pThis->ring_buf [pThis->begin] == 0) 
        pThis->ring_buf [pThis->begin] = len;

    // store line
    if (len < _RING_HISTORY_LEN-pThis->end-1)
        memcpy (pThis->ring_buf + pThis->end + 1, line, len);
    else {
        int part_len = _RING_HISTORY_LEN-pThis->end-1;
        memcpy (pThis->ring_buf + pThis->end + 1, line, part_len);
        memcpy (pThis->ring_buf, line + part_len, len - part_len);
    }
    pThis->ring_buf [pThis->end] = len;
    pThis->end = pThis->end + len + 1;
    if (pThis->end >= _RING_HISTORY_LEN)
        pThis->end -= _RING_HISTORY_LEN;
    pThis->ring_buf [pThis->end] = 0;
    pThis->cur = 0;
#ifdef _HISTORY_DEBUG
    print_hist (pThis);
#endif
}

//*****************************************************************************
// copy saved line to 'line' and return size of line
static int hist_restore_line (ring_history_t * pThis, char * line, int dir)
{
    int cnt = 0;
    // count history record 
    int header = pThis->begin;
    while (pThis->ring_buf [header] != 0) {
        header += pThis->ring_buf [header] + 1;
        if (header >= _RING_HISTORY_LEN)
            header -= _RING_HISTORY_LEN; 
        cnt++;
    }

    if (dir == _HIST_UP) {
        if (cnt >= pThis->cur) {
            int header = pThis->begin;
            int j = 0;
            // found record for 'pThis->cur' index
            while ((pThis->ring_buf [header] != 0) && (cnt - j -1 != pThis->cur)) {
                header += pThis->ring_buf [header] + 1;
                if (header >= _RING_HISTORY_LEN)
                    header -= _RING_HISTORY_LEN;
                j++;
            }
            if (pThis->ring_buf[header]) {
                    pThis->cur++;
                // obtain saved line
                if (pThis->ring_buf [header] + header < _RING_HISTORY_LEN) {
                    memset (line, 0, _COMMAND_LINE_LEN);
                    memcpy (line, pThis->ring_buf + header + 1, pThis->ring_buf[header]);
                } else {
                    int part0 = _RING_HISTORY_LEN - header - 1;
                    memset (line, 0, _COMMAND_LINE_LEN);
                    memcpy (line, pThis->ring_buf + header + 1, part0);
                    memcpy (line + part0, pThis->ring_buf, pThis->ring_buf[header] - part0);
                }
                return pThis->ring_buf[header];
            }
        }
    } else {
        if (pThis->cur > 0) {
                pThis->cur--;
            int header = pThis->begin;
            int j = 0;

            while ((pThis->ring_buf [header] != 0) && (cnt - j != pThis->cur)) {
                header += pThis->ring_buf [header] + 1;
                if (header >= _RING_HISTORY_LEN)
                    header -= _RING_HISTORY_LEN;
                j++;
            }
            if (pThis->ring_buf [header] + header < _RING_HISTORY_LEN) {
                memcpy (line, pThis->ring_buf + header + 1, pThis->ring_buf[header]);
            } else {
                int part0 = _RING_HISTORY_LEN - header - 1;
                memcpy (line, pThis->ring_buf + header + 1, part0);
                memcpy (line + part0, pThis->ring_buf, pThis->ring_buf[header] - part0);
            }
            return pThis->ring_buf[header];
        } else {
            /* empty line */
            return 0;
        }
    }
    return -1;
}
#endif








//*****************************************************************************
// split cmdline to tkn array and return nmb of token
static int split (microrl_t * pThis, int limit, char const ** tkn_arr)
{
    int i = 0;
    int ind = 0;
    while (1) {
        // go to the first whitespace (zerro for us)
        while ((pThis->cmdline [ind] == '\0') && (ind < limit)) {
            ind++;
        }
        if (!(ind < limit)) return i;
        tkn_arr[i++] = pThis->cmdline + ind;
        if (i >= _COMMAND_TOKEN_NMB) {
            return -1;
        }
        // go to the first NOT whitespace (not zerro for us)
        while ((pThis->cmdline [ind] != '\0') && (ind < limit)) {
            ind++;
        }
        if (!(ind < limit)) return i;
    }
    return i;
}


//*****************************************************************************
inline static void print_prompt (microrl_t * pThis)
{
    pThis->print (pThis->prompt_str);
}

//*****************************************************************************
inline static void terminal_backspace (microrl_t * pThis)
{
        pThis->print ("\033[D \033[D");
}

//*****************************************************************************
inline static void terminal_newline (microrl_t * pThis)
{
    pThis->print (ENDL);
}

#ifndef _USE_LIBC_STDIO
//*****************************************************************************
// convert 16 bit value to string
// 0 value not supported!!! just make empty string
// Returns pointer to a buffer tail
static char *u16bit_to_str (unsigned int nmb, char * buf)
{
    char tmp_str [6] = {0,};
    int i = 0, j;
    if (nmb <= 0xFFFF) {
        while (nmb > 0) {
            tmp_str[i++] = (nmb % 10) + '0';
            nmb /=10;
        }
        for (j = 0; j < i; ++j)
            *(buf++) = tmp_str [i-j-1];
    }
    *buf = '\0';
    return buf;
}
#endif


//*****************************************************************************
// set cursor at position from begin cmdline (after prompt) + offset
static void terminal_move_cursor (microrl_t * pThis, int offset)
{
    char str[16] = {0,};
#ifdef _USE_LIBC_STDIO 
    if (offset > 0) {
        snprintf (str, 16, "\033[%dC", offset);
    } else if (offset < 0) {
        snprintf (str, 16, "\033[%dD", -(offset));
    }
#else 
    char *endstr;
    strcpy (str, "\033[");
    if (offset > 0) {
        endstr = u16bit_to_str (offset, str+2);
        strcpy (endstr, "C");
    } else if (offset < 0) {
        endstr = u16bit_to_str (-(offset), str+2);
        strcpy (endstr, "D");
    } else
        return;
#endif  
    pThis->print (str);
}

//*****************************************************************************
static void terminal_reset_cursor (microrl_t * pThis)
{
    char str[16];
#ifdef _USE_LIBC_STDIO
    snprintf (str, 16, "\033[%dD\033[%dC", \
                        _COMMAND_LINE_LEN + _PROMPT_LEN + 2, _PROMPT_LEN);

#else
    char *endstr;
    strcpy (str, "\033[");
    endstr = u16bit_to_str ( _COMMAND_LINE_LEN + _PROMPT_LEN + 2,str+2);
    strcpy (endstr, "D\033["); endstr += 3;
    endstr = u16bit_to_str (_PROMPT_LEN, endstr);
    strcpy (endstr, "C");
#endif
    pThis->print (str);
}

//*****************************************************************************
// print cmdline to screen, replace '\0' to wihitespace 
static void terminal_print_line (microrl_t * pThis, int pos, int cursor)
{
    pThis->print ("\033[K");    // delete all from cursor to end

    char nch [] = {0,0};
    int i;
    for (i = pos; i < pThis->cmdlen; i++) {
        nch [0] = pThis->cmdline [i];
        if (nch[0] == '\0')
            nch[0] = ' ';
        pThis->print (nch);
    }

    terminal_reset_cursor (pThis);
    terminal_move_cursor (pThis, cursor);
}

//*****************************************************************************
void microrl_init (microrl_t * pThis, void (*print) (const char *)) 
{
    memset(pThis->cmdline, 0, _COMMAND_LINE_LEN);
#ifdef _USE_HISTORY
    memset(pThis->ring_hist.ring_buf, 0, _RING_HISTORY_LEN);
    pThis->ring_hist.begin = 0;
    pThis->ring_hist.end = 0;
    pThis->ring_hist.cur = 0;
#endif
    pThis->cmdlen =0;
    pThis->cursor = 0;
    pThis->execute = NULL;
    pThis->get_completion = NULL;
#ifdef _USE_CTLR_C
    pThis->sigint = NULL;
#endif
    pThis->prompt_str = prompt_default;
    pThis->print = print;
#ifdef _ENABLE_INIT_PROMPT
    print_prompt (pThis);
#endif
}

//*****************************************************************************
void microrl_set_complete_callback (microrl_t * pThis, char ** (*get_completion)(int, const char* const*))
{
    pThis->get_completion = get_completion;
}

//*****************************************************************************
void microrl_set_execute_callback (microrl_t * pThis, int (*execute)(int, const char* const*))
{
    pThis->execute = execute;
}
#ifdef _USE_CTLR_C
//*****************************************************************************
void microrl_set_sigint_callback (microrl_t * pThis, void (*sigintf)(void))
{
    pThis->sigint = sigintf;
}
#endif

#ifdef _USE_HISTORY
static void hist_search (microrl_t * pThis, int dir)
{
    int len = hist_restore_line (&pThis->ring_hist, pThis->cmdline, dir);
    if (len >= 0) {
        pThis->cmdline[len] = '\0';
        pThis->cursor = pThis->cmdlen = len;
        terminal_reset_cursor (pThis);
        terminal_print_line (pThis, 0, pThis->cursor);
    }
}
#endif

#ifdef _USE_ESC_SEQ
//*****************************************************************************
// handling escape sequences
static int escape_process (microrl_t * pThis, char ch)
{
    if (ch == '[') {
        pThis->escape_seq = _ESC_BRACKET;
        return 0;
    } else if (pThis->escape_seq == _ESC_BRACKET) {
        if (ch == 'A') {
#ifdef _USE_HISTORY
            hist_search (pThis, _HIST_UP);
#endif
            return 1;
        } else if (ch == 'B') {
#ifdef _USE_HISTORY
            hist_search (pThis, _HIST_DOWN);
#endif
            return 1;
        } else if (ch == 'C') {
            if (pThis->cursor < pThis->cmdlen) {
                terminal_move_cursor (pThis, 1);
                pThis->cursor++;
            }
            return 1;
        } else if (ch == 'D') {
            if (pThis->cursor > 0) {
                terminal_move_cursor (pThis, -1);
                pThis->cursor--;
            }
            return 1;
        } else if (ch == '7') {
            pThis->escape_seq = _ESC_HOME;
            return 0;
        } else if (ch == '8') {
            pThis->escape_seq = _ESC_END;
            return 0;
        } 
    } else if (ch == '~') {
        if (pThis->escape_seq == _ESC_HOME) {
            terminal_reset_cursor (pThis);
            pThis->cursor = 0;
            return 1;
        } else if (pThis->escape_seq == _ESC_END) {
            terminal_move_cursor (pThis, pThis->cmdlen-pThis->cursor);
            pThis->cursor = pThis->cmdlen;
            return 1;
        }
    }

    /* unknown escape sequence, stop */
    return 1;
}
#endif

//*****************************************************************************
// insert len char of text at cursor position
static int microrl_insert_text (microrl_t * pThis, char * text, int len)
{
    int i;
    if (pThis->cmdlen + len < _COMMAND_LINE_LEN) {
        memmove (pThis->cmdline + pThis->cursor + len,
                         pThis->cmdline + pThis->cursor,
                         pThis->cmdlen - pThis->cursor);
        for (i = 0; i < len; i++) {
            pThis->cmdline [pThis->cursor + i] = text [i];
            if (pThis->cmdline [pThis->cursor + i] == ' ') {
                pThis->cmdline [pThis->cursor + i] = 0;
            }
        }
        pThis->cursor += len;
        pThis->cmdlen += len;
        pThis->cmdline [pThis->cmdlen] = '\0';
        return true;
    }
    return false;
}

//*****************************************************************************
// remove one char at cursor
static void microrl_backspace (microrl_t * pThis)
{
    if (pThis->cursor > 0) {
        terminal_backspace (pThis);
        memmove (pThis->cmdline + pThis->cursor-1,
                         pThis->cmdline + pThis->cursor,
                         pThis->cmdlen-pThis->cursor+1);
        pThis->cursor--;
        pThis->cmdline [pThis->cmdlen] = '\0';
        pThis->cmdlen--;
    }
}


#ifdef _USE_COMPLETE

//*****************************************************************************
static int common_len (char ** arr)
{
    int i;
    int j;
    char *shortest = arr[0];
    int shortlen = strlen(shortest);

    for (i = 0; arr[i] != NULL; ++i)
        if (strlen(arr[i]) < shortlen) {
            shortest = arr[i];
            shortlen = strlen(shortest);
        }

    for (i = 0; i < shortlen; ++i)
        for (j = 0; arr[j] != 0; ++j)
            if (shortest[i] != arr[j][i])
                return i;

    return i;
}

//*****************************************************************************
static void microrl_get_complite (microrl_t * pThis) 
{
    char const * tkn_arr[_COMMAND_TOKEN_NMB];
    char ** compl_token; 

    if (pThis->get_completion == NULL) // callback was not set
        return;

    int status = split (pThis, pThis->cursor, tkn_arr);
    if (pThis->cmdline[pThis->cursor-1] == '\0')
        tkn_arr[status++] = "";
    compl_token = pThis->get_completion (status, tkn_arr);
    if (compl_token[0] != NULL) {
        int i = 0;
        int len;

        if (compl_token[1] == NULL) {
            len = strlen (compl_token[0]);
        } else {
            len = common_len (compl_token);
            terminal_newline (pThis);
            while (compl_token [i] != NULL) {
                pThis->print (compl_token[i]);
                pThis->print (" ");
                i++;
            }
            terminal_newline (pThis);
            print_prompt (pThis);
        }

        if (len) {
            microrl_insert_text (pThis, compl_token[0] + strlen(tkn_arr[status-1]), 
                                                                    len - strlen(tkn_arr[status-1]));
            if (compl_token[1] == NULL) 
                microrl_insert_text (pThis, " ", 1);
        }
        terminal_reset_cursor (pThis);
        terminal_print_line (pThis, 0, pThis->cursor);
    } 
}
#endif

//*****************************************************************************
void new_line_handler(microrl_t * pThis){
    char const * tkn_arr [_COMMAND_TOKEN_NMB];
    int status;

    terminal_newline (pThis);
#ifdef _USE_HISTORY
    if (pThis->cmdlen > 0)
        hist_save_line (&pThis->ring_hist, pThis->cmdline, pThis->cmdlen);
#endif
    status = split (pThis, pThis->cmdlen, tkn_arr);
    if (status == -1){
        //          pThis->print ("ERROR: Max token amount exseed\n");
        pThis->print ("ERROR:too many tokens");
        pThis->print (ENDL);
    }
    if ((status > 0) && (pThis->execute != NULL))
        pThis->execute (status, tkn_arr);
    print_prompt (pThis);
    pThis->cmdlen = 0;
    pThis->cursor = 0;
    memset(pThis->cmdline, 0, _COMMAND_LINE_LEN);
#ifdef _USE_HISTORY
    pThis->ring_hist.cur = 0;
#endif
}

//*****************************************************************************

void microrl_insert_char (microrl_t * pThis, int ch)
{
#ifdef _USE_ESC_SEQ
    if (pThis->escape) {
        if (escape_process(pThis, ch))
            pThis->escape = 0;
    } else {
#endif
        switch (ch) {
            //-----------------------------------------------------
#ifdef _ENDL_CR
            case KEY_CR:
                new_line_handler(pThis);
            break;
            case KEY_LF:
            break;
#elif defined(_ENDL_CRLF)
            case KEY_CR:
                pThis->tmpch = KEY_CR;
            break;
            case KEY_LF:
            if (pThis->tmpch == KEY_CR)
                new_line_handler(pThis);
            break;
#elif defined(_ENDL_LFCR)
            case KEY_LF:
                pThis->tmpch = KEY_LF;
            break;
            case KEY_CR:
            if (pThis->tmpch == KEY_LF)
                new_line_handler(pThis);
            break;
#else
            case KEY_CR:
            break;
            case KEY_LF:
                new_line_handler(pThis);
            break;
#endif
            //-----------------------------------------------------
#ifdef _USE_COMPLETE
            case KEY_HT:
                microrl_get_complite (pThis);
            break;
#endif
            //-----------------------------------------------------
            case KEY_ESC:
#ifdef _USE_ESC_SEQ
                pThis->escape = 1;
#endif
            break;
            //-----------------------------------------------------
            case KEY_NAK: // ^U
                    while (pThis->cursor > 0) {
                    microrl_backspace (pThis);
                }
                terminal_print_line (pThis, 0, pThis->cursor);
            break;
            //-----------------------------------------------------
            case KEY_VT:  // ^K
                pThis->print ("\033[K");
                pThis->cmdlen = pThis->cursor;
            break;
            //-----------------------------------------------------
            case KEY_ENQ: // ^E
                terminal_move_cursor (pThis, pThis->cmdlen-pThis->cursor);
                pThis->cursor = pThis->cmdlen;
            break;
            //-----------------------------------------------------
            case KEY_SOH: // ^A
                terminal_reset_cursor (pThis);
                pThis->cursor = 0;
            break;
            //-----------------------------------------------------
            case KEY_ACK: // ^F
            if (pThis->cursor < pThis->cmdlen) {
                terminal_move_cursor (pThis, 1);
                pThis->cursor++;
            }
            break;
            //-----------------------------------------------------
            case KEY_STX: // ^B
            if (pThis->cursor) {
                terminal_move_cursor (pThis, -1);
                pThis->cursor--;
            }
            break;
            //-----------------------------------------------------
            case KEY_DLE: //^P
#ifdef _USE_HISTORY
            hist_search (pThis, _HIST_UP);
#endif
            break;
            //-----------------------------------------------------
            case KEY_SO: //^N
#ifdef _USE_HISTORY
            hist_search (pThis, _HIST_DOWN);
#endif
            break;
            //-----------------------------------------------------
            case KEY_DEL: // Backspace
            case KEY_BS: // ^U
                microrl_backspace (pThis);
                terminal_print_line (pThis, pThis->cursor, pThis->cursor);
            break;
            //-----------------------------------------------------
            case KEY_DC2: // ^R
                terminal_newline (pThis);
                print_prompt (pThis);
                terminal_reset_cursor (pThis);
                terminal_print_line (pThis, 0, pThis->cursor);
            break;
            //-----------------------------------------------------
#ifdef _USE_CTLR_C
            case KEY_ETX:
            if (pThis->sigint != NULL)
                pThis->sigint();
            break;
#endif
            //-----------------------------------------------------
            default:
            if (((ch == ' ') && (pThis->cmdlen == 0)) || IS_CONTROL_CHAR(ch))
                break;
            if (microrl_insert_text (pThis, (char*)&ch, 1))
                terminal_print_line (pThis, pThis->cursor-1, pThis->cursor);

            break;
        }
#ifdef _USE_ESC_SEQ
    }
#endif
}
component/microrl/src/microrl.h
#ifndef _MICRORL_H_
#define _MICRORL_H_

#include "config.h"

#define true  1
#define false 0

 /* define the Key codes */
#define KEY_NUL 0 /**< ^@ Null character */
#define KEY_SOH 1 /**< ^A Start of heading, = console interrupt */
#define KEY_STX 2 /**< ^B Start of text, maintenance mode on HP console */
#define KEY_ETX 3 /**< ^C End of text */
#define KEY_EOT 4 /**< ^D End of transmission, not the same as ETB */
#define KEY_ENQ 5 /**< ^E Enquiry, goes with ACK; old HP flow control */
#define KEY_ACK 6 /**< ^F Acknowledge, clears ENQ logon hand */
#define KEY_BEL 7 /**< ^G Bell, rings the bell... */
#define KEY_BS  8 /**< ^H Backspace, works on HP terminals/computers */
#define KEY_HT  9 /**< ^I Horizontal tab, move to next tab stop */
#define KEY_LF  10  /**< ^J Line Feed */
#define KEY_VT  11  /**< ^K Vertical tab */
#define KEY_FF  12  /**< ^L Form Feed, page eject */
#define KEY_CR  13  /**< ^M Carriage Return*/
#define KEY_SO  14  /**< ^N Shift Out, alternate character set */
#define KEY_SI  15  /**< ^O Shift In, resume defaultn character set */
#define KEY_DLE 16  /**< ^P Data link escape */
#define KEY_DC1 17  /**< ^Q XON, with XOFF to pause listings; "okay to send". */
#define KEY_DC2 18  /**< ^R Device control 2, block-mode flow control */
#define KEY_DC3 19  /**< ^S XOFF, with XON is TERM=18 flow control */
#define KEY_DC4 20  /**< ^T Device control 4 */
#define KEY_NAK 21  /**< ^U Negative acknowledge */
#define KEY_SYN 22  /**< ^V Synchronous idle */
#define KEY_ETB 23  /**< ^W End transmission block, not the same as EOT */
#define KEY_CAN 24  /**< ^X Cancel line, MPE echoes !!! */
#define KEY_EM  25  /**< ^Y End of medium, Control-Y interrupt */
#define KEY_SUB 26  /**< ^Z Substitute */
#define KEY_ESC 27  /**< ^[ Escape, next character is not echoed */
#define KEY_FS  28  /**< ^\ File separator */
#define KEY_GS  29  /**< ^] Group separator */
#define KEY_RS  30  /**< ^^ Record separator, block-mode terminator */
#define KEY_US  31  /**< ^_ Unit separator */

#define KEY_DEL 127 /**< Delete (not a real control character...) */

#define IS_CONTROL_CHAR(x) ((x)<=31)

// direction of history navigation
#define _HIST_UP   0
#define _HIST_DOWN 1
// esc seq internal codes
#define _ESC_BRACKET  1
#define _ESC_HOME     2
#define _ESC_END      3

#ifdef _USE_HISTORY
// history struct, contain internal variable
// history store in static ring buffer for memory saving
typedef struct {
    char ring_buf [_RING_HISTORY_LEN];
    int begin;
    int end;
    int cur;
} ring_history_t;
#endif

// microrl struct, contain internal library data
typedef struct {
#ifdef _USE_ESC_SEQ
    char escape_seq;
    char escape;
#endif
#if (defined(_ENDL_CRLF) || defined(_ENDL_LFCR))
    char tmpch;
#endif
#ifdef _USE_HISTORY
    ring_history_t ring_hist;          // history object
#endif
    char * prompt_str;                 // pointer to prompt string
    char cmdline [_COMMAND_LINE_LEN];  // cmdline buffer
    int cmdlen;                        // last position in command line
    int cursor;                        // input cursor
    int (*execute) (int argc, const char * const * argv );            // ptr to 'execute' callback
    char ** (*get_completion) (int argc, const char * const * argv ); // ptr to 'completion' callback
    void (*print) (const char *);                                     // ptr to 'print' callback
#ifdef _USE_CTLR_C
    void (*sigint) (void);
#endif
} microrl_t;

// init internal data, calls once at start up
void microrl_init (microrl_t * pThis, void (*print)(const char*));

// set echo mode (true/false), using for disabling echo for password input
// echo mode will enabled after user press Enter.
void microrl_set_echo (int);

// set pointer to callback complition func, that called when user press 'Tab'
// callback func description:
//   param: argc - argument count, argv - pointer array to token string
//   must return NULL-terminated string, contain complite variant splitted by 'Whitespace'
//   If complite token found, it's must contain only one token to be complitted
//   Empty string if complite not found, and multiple string if there are some token
void microrl_set_complete_callback (microrl_t * pThis, char ** (*get_completion)(int, const char* const*));

// pointer to callback func, that called when user press 'Enter'
// execute func param: argc - argument count, argv - pointer array to token string
void microrl_set_execute_callback (microrl_t * pThis, int (*execute)(int, const char* const*));

// set callback for Ctrl+C terminal signal
#ifdef _USE_CTLR_C
void microrl_set_sigint_callback (microrl_t * pThis, void (*sigintf)(void));
#endif

// insert char to cmdline (for example call in usart RX interrupt)
void microrl_insert_char (microrl_t * pThis, int ch);

#endif