Statistics
| Branch: | Tag: | Revision:

amiro-os / core / src / aos_shell.c @ 27286ba5

History | View | Annotate | Download (47.146 KB)

1 e545e620 Thomas Schöpping
/*
2
AMiRo-OS is an operating system designed for the Autonomous Mini Robot (AMiRo) platform.
3 84f0ce9e Thomas Schöpping
Copyright (C) 2016..2019  Thomas Schöpping et al.
4 e545e620 Thomas Schöpping

5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation, either version 3 of the License, or
8
(at your option) any later version.
9

10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
GNU General Public License for more details.
14

15
You should have received a copy of the GNU General Public License
16
along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19 53710ca3 Marc Rothmann
/**
20
 * @file    aos_shell.c
21
 * @brief   Shell code.
22
 * @details Shell code as well as shell related channels and streams.
23
 *
24
 * @addtogroup aos_shell
25
 * @{
26
 */
27
28 e545e620 Thomas Schöpping
#include <aos_shell.h>
29
30 2dd2e257 Thomas Schöpping
#if (AMIROOS_CFG_SHELL_ENABLE == true) || (AMIROOS_CFG_TESTS_ENABLE == true)
31 e545e620 Thomas Schöpping
#include <aos_debug.h>
32
#include <aos_time.h>
33
#include <aos_system.h>
34
#include <string.h>
35 ba516b61 Thomas Schöpping
36
/**
37
 * @brief   Event mask to be set on OS related events.
38
 */
39
#define AOS_SHELL_EVENTMASK_OS                  EVENT_MASK(0)
40
41
/**
42
 * @brief   Event mask to be set on a input event.
43
 */
44
#define AOS_SHELL_EVENTMASK_INPUT               EVENT_MASK(1)
45
46
/**
47
 * @brief   Implementation of the BaseAsynchronous write() method (inherited from BaseSequentialStream).
48
 */
49
static size_t _channelwrite(void *instance, const uint8_t *bp, size_t n)
50
{
51
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
52 dd8738ea Thomas Schöpping
    return streamWrite(((AosShellChannel*)instance)->asyncchannel, bp, n);
53 ba516b61 Thomas Schöpping
  } else {
54
    return 0;
55
  }
56
}
57
58
/**
59
 * @brief   Implementation of the BaseAsynchronous read() method (inherited from BaseSequentialStream).
60
 */
61
static size_t _channelread(void *instance, uint8_t *bp, size_t n)
62
{
63 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
64
    return streamRead(((AosShellChannel*)instance)->asyncchannel, bp, n);
65
  } else {
66
    return 0;
67
  }
68 ba516b61 Thomas Schöpping
}
69
70
/**
71
 * @brief   Implementation of the BaseAsynchronous put() method (inherited from BaseSequentialStream).
72
 */
73
static msg_t _channelput(void *instance, uint8_t b)
74
{
75
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
76 dd8738ea Thomas Schöpping
    return streamPut(((AosShellChannel*)instance)->asyncchannel, b);
77 ba516b61 Thomas Schöpping
  } else {
78
    return MSG_RESET;
79
  }
80
}
81
82
/**
83
 * @brief   Implementation of the BaseAsynchronous get() method (inherited from BaseSequentialStream).
84
 */
85
static msg_t _channelget(void *instance)
86
{
87 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
88
    return streamGet(((AosShellChannel*)instance)->asyncchannel);
89
  } else {
90
    return MSG_RESET;
91
  }
92 ba516b61 Thomas Schöpping
}
93
94
/**
95
 * @brief   Implementation of the BaseAsynchronous putt() method.
96
 */
97 2c99037f Marc Rothmann
static msg_t _channelputt(void *instance, uint8_t b, sysinterval_t time)
98 ba516b61 Thomas Schöpping
{
99
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
100 dd8738ea Thomas Schöpping
    return chnPutTimeout(((AosShellChannel*)instance)->asyncchannel, b, time);
101 ba516b61 Thomas Schöpping
  } else {
102
    return MSG_RESET;
103
  }
104
}
105
106
/**
107
 * @brief   Implementation of the BaseAsynchronous gett() method.
108
 */
109 2c99037f Marc Rothmann
static msg_t _channelgett(void *instance, sysinterval_t time)
110 ba516b61 Thomas Schöpping
{
111 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
112
    return chnGetTimeout(((AosShellChannel*)instance)->asyncchannel, time);
113
  } else {
114
    return MSG_RESET;
115
  }
116 ba516b61 Thomas Schöpping
}
117
118
/**
119
 * @brief   Implementation of the BaseAsynchronous writet() method.
120
 */
121 2c99037f Marc Rothmann
static size_t _channelwritet(void *instance, const uint8_t *bp, size_t n, sysinterval_t time)
122 ba516b61 Thomas Schöpping
{
123
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
124 dd8738ea Thomas Schöpping
    return chnWriteTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
125 ba516b61 Thomas Schöpping
  } else {
126
    return 0;
127
  }
128
}
129
130
/**
131
 * @brief   Implementation of the BaseAsynchronous readt() method.
132
 */
133 2c99037f Marc Rothmann
static size_t _channelreadt(void *instance, uint8_t *bp, size_t n, sysinterval_t time)
134 ba516b61 Thomas Schöpping
{
135 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
136
    return chnReadTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
137
  } else {
138
    return 0;
139
  }
140 ba516b61 Thomas Schöpping
}
141
142 2c99037f Marc Rothmann
/**
143
 * @brief   Implementation of the BaseAsynchronousChannel ctl() method.
144
 */
145
static msg_t _channelctl(void *instance, unsigned int operation, void *arg) {
146
  (void) instance;
147
148
  switch (operation) {
149
  case CHN_CTL_NOP:
150
    osalDbgCheck(arg == NULL);
151
    break;
152
  case CHN_CTL_INVALID:
153
    osalDbgAssert(false, "invalid CTL operation");
154
    break;
155
  default:
156
    break;
157
  }
158
  return MSG_OK;
159
}
160
161 ba516b61 Thomas Schöpping
static const struct AosShellChannelVMT _channelvmt = {
162 0128be0f Marc Rothmann
  (size_t) 0,
163 ba516b61 Thomas Schöpping
  _channelwrite,
164
  _channelread,
165
  _channelput,
166
  _channelget,
167
  _channelputt,
168
  _channelgett,
169
  _channelwritet,
170
  _channelreadt,
171 2c99037f Marc Rothmann
  _channelctl,
172 ba516b61 Thomas Schöpping
};
173
174
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n)
175
{
176
  aosDbgCheck(instance != NULL);
177
178
  // local variables
179
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
180
  size_t bytes;
181
  size_t maxbytes = 0;
182
183
  // iterate through the list of channels
184
  while (channel != NULL) {
185
    bytes = streamWrite(channel, bp, n);
186
    maxbytes = (bytes > maxbytes) ? bytes : maxbytes;
187
    channel = channel->next;
188
  }
189
190
  return maxbytes;
191
}
192
193
static size_t _stremread(void *instance, uint8_t *bp, size_t n)
194
{
195
  (void)instance;
196
  (void)bp;
197
  (void)n;
198
199
  return 0;
200
}
201
202
static msg_t _streamput(void *instance, uint8_t b)
203
{
204
  aosDbgCheck(instance != NULL);
205
206
  // local variables
207
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
208 dd8738ea Thomas Schöpping
  msg_t ret = MSG_OK;
209 ba516b61 Thomas Schöpping
210
  // iterate through the list of channels
211
  while (channel != NULL) {
212 dd8738ea Thomas Schöpping
    msg_t ret_ = streamPut(channel, b);
213
    ret = (ret_ < ret) ? ret_ : ret;
214 ba516b61 Thomas Schöpping
    channel = channel->next;
215
  }
216
217 dd8738ea Thomas Schöpping
  return ret;
218 ba516b61 Thomas Schöpping
}
219
220
static msg_t _streamget(void *instance)
221
{
222
  (void)instance;
223
224
  return 0;
225
}
226
227
static const struct AosShellStreamVMT _streamvmt = {
228 0128be0f Marc Rothmann
  (size_t) 0,
229 ba516b61 Thomas Schöpping
  _streamwrite,
230
  _stremread,
231
  _streamput,
232
  _streamget,
233
};
234
235 e545e620 Thomas Schöpping
/**
236
 * @brief   Enumerator of special keyboard keys.
237
 */
238
typedef enum special_key {
239
  KEY_UNKNOWN,      /**< any/unknow key */
240
  KEY_AMBIGUOUS,    /**< key is ambiguous */
241
  KEY_TAB,          /**< tabulator key */
242
  KEY_ESCAPE,       /**< escape key */
243
  KEY_BACKSPACE,    /**< backspace key */
244
  KEY_INSERT,       /**< insert key */
245
  KEY_DELETE,       /**< delete key */
246
  KEY_HOME,         /**< home key */
247
  KEY_END,          /**< end key */
248
  KEY_PAGE_UP,      /**< page up key */
249
  KEY_PAGE_DOWN,    /**< page down key */
250
  KEY_ARROW_UP,     /**< arrow up key */
251
  KEY_ARROW_DOWN,   /**< arrow down key */
252
  KEY_ARROW_LEFT,   /**< arrow left key */
253
  KEY_ARROW_RIGHT,  /**< arrow right key */
254
} special_key_t;
255
256
/**
257
 * @brief   Enumerator for case (in)sensitive character matching.
258
 */
259
typedef enum charmatch {
260
  CHAR_MATCH_NOT    = 0,  /**< Characters do not match at all. */
261
  CHAR_MATCH_NCASE  = 1,  /**< Characters would match case insensitive. */
262
  CHAR_MATCH_CASE   = 2,  /**< Characters do match with case. */
263
} charmatch_t;
264
265
/**
266
 * @brief   Print the shell prompt
267
 * @details Depending on the configuration flags, the system uptime is printed before the prompt string.
268
 *
269
 * @param[in] shell   Pointer to the shell object.
270
 */
271
static void _printPrompt(aos_shell_t* shell)
272
{
273
  aosDbgCheck(shell != NULL);
274
275 8399aeae Thomas Schöpping
  // print some time informattion before prompt if configured
276
  if (shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) {
277
    // printf the system uptime
278
    if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_UPTIME) {
279
      // get current system uptime
280
      aos_timestamp_t uptime;
281
      aosSysGetUptime(&uptime);
282
283
      chprintf((BaseSequentialStream*)&shell->stream, "[%01u:%02u:%02u:%02u:%03u:%03u] ",
284
               (uint32_t)(uptime / MICROSECONDS_PER_DAY),
285
               (uint8_t)(uptime % MICROSECONDS_PER_DAY / MICROSECONDS_PER_HOUR),
286
               (uint8_t)(uptime % MICROSECONDS_PER_HOUR / MICROSECONDS_PER_MINUTE),
287
               (uint8_t)(uptime % MICROSECONDS_PER_MINUTE / MICROSECONDS_PER_SECOND),
288
               (uint16_t)(uptime % MICROSECONDS_PER_SECOND / MICROSECONDS_PER_MILLISECOND),
289
               (uint16_t)(uptime % MICROSECONDS_PER_MILLISECOND / MICROSECONDS_PER_MICROSECOND));
290
    }
291
    else if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_DATETIME) {
292
      // get current RTC time
293
      struct tm dt;
294
      aosSysGetDateTime(&dt);
295
      chprintf((BaseSequentialStream*)&shell->stream, "[%02u-%02u-%04u|%02u:%02u:%02u] ",
296
               dt.tm_mday,
297
               dt.tm_mon + 1,
298
               dt.tm_year + 1900,
299
               dt.tm_hour,
300
               dt.tm_min,
301
               dt.tm_sec);
302
    }
303
    else {
304
      aosDbgAssert(false);
305
    }
306 e545e620 Thomas Schöpping
  }
307
308
  // print the actual prompt string
309
  if (shell->prompt && !(shell->config & AOS_SHELL_CONFIG_PROMPT_MINIMAL)) {
310 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%s$ ", shell->prompt);
311 e545e620 Thomas Schöpping
  } else {
312 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%>$ ");
313 e545e620 Thomas Schöpping
  }
314
315
  return;
316
}
317
318
/**
319
 * @brief   Interprete a escape sequence
320
 *
321
 * @param[in] seq   Character sequence to interprete.
322
 *                  Must be terminated by NUL byte.
323
 *
324
 * @return          A @p special_key value.
325
 */
326
static special_key_t _interpreteEscapeSequence(const char seq[])
327
{
328
  // local variables
329
  bool ambiguous = false;
330
  int cmp = 0;
331
332
  // TAB
333
  /* not supported yet; use "\x09" instead */
334
335
  // BACKSPACE
336
  /* not supported yet; use "\x08" instead */
337
338
  // ESCAPE
339
  cmp = strcmp(seq, "\x1B");
340
  if (cmp == 0) {
341
    return KEY_ESCAPE;
342
  } else {
343
    ambiguous |= (cmp < 0);
344
  }
345
346
  // INSERT
347
  cmp = strcmp(seq, "\x1B\x5B\x32\x7E");
348
  if (cmp == 0) {
349
    return KEY_INSERT;
350
  } else {
351
    ambiguous |= (cmp < 0);
352
  }
353
354
  // DELETE
355
  cmp = strcmp(seq, "\x1B\x5B\x33\x7E");
356
  if (cmp == 0) {
357
    return KEY_DELETE;
358
  } else {
359
    ambiguous |= (cmp < 0);
360
  }
361
362
  // HOME
363
  cmp = strcmp(seq, "\x1B\x4F\x48");
364
  if (cmp == 0) {
365
    return KEY_HOME;
366
  } else {
367
    ambiguous |= (cmp < 0);
368
  }
369
370
  // END
371
  cmp = strcmp(seq, "\x1B\x4F\x46");
372
  if (cmp == 0) {
373
    return KEY_END;
374
  } else {
375
    ambiguous |= (cmp < 0);
376
  }
377
378
  // PAGE UP
379
  cmp = strcmp(seq, "\x1B\x5B\x35\x7E");
380
  if (cmp == 0) {
381
    return KEY_PAGE_UP;
382
  } else {
383
    ambiguous |= (cmp < 0);
384
  }
385
386
  // PAGE DOWN
387
  cmp = strcmp(seq, "\x1B\x5B\x36\x7E");
388
  if (cmp == 0) {
389
    return KEY_PAGE_DOWN;
390
  } else {
391
    ambiguous |= (cmp < 0);
392
  }
393
394
  // ARROW UP
395
  cmp = strcmp(seq, "\x1B\x5B\x41");
396
  if (cmp == 0) {
397
    return KEY_ARROW_UP;
398
  } else {
399
    ambiguous |= (cmp < 0);
400
  }
401
402
  // ARROW DOWN
403
  cmp = strcmp(seq, "\x1B\x5B\x42");
404
  if (cmp == 0) {
405
    return KEY_ARROW_DOWN;
406
  } else {
407
    ambiguous |= (cmp < 0);
408
  }
409
410
  // ARROW LEFT
411
  cmp = strcmp(seq, "\x1B\x5B\x44");
412
  if (cmp == 0) {
413
    return KEY_ARROW_LEFT;
414
  } else {
415
    ambiguous |= (cmp < 0);
416
  }
417
418
  // ARROW RIGHT
419
  cmp = strcmp(seq, "\x1B\x5B\x43");
420
  if (cmp == 0) {
421
    return KEY_ARROW_RIGHT;
422
  } else {
423
    ambiguous |= (cmp < 0);
424
  }
425
426
  return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
427
}
428
429
/**
430
 * @brief   Move the cursor in the terminal
431
 *
432
 * @param[in] shell   Pointer to the shell object.
433
 * @param[in] from    Starting position of the cursor.
434
 * @param[in] to      Target position to move the cursor to.
435
 *
436
 * @return            The number of positions moved.
437
 */
438
static int _moveCursor(aos_shell_t* shell, const size_t from, const size_t to)
439
{
440
  aosDbgCheck(shell != NULL);
441
442
  // local variables
443
  size_t pos = from;
444
445
  // move cursor left by printing backspaces
446
  while (pos > to) {
447 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, '\b');
448 e545e620 Thomas Schöpping
    --pos;
449
  }
450
451
  // move cursor right by printing line content
452
  while (pos < to) {
453 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, shell->line[pos]);
454 e545e620 Thomas Schöpping
    ++pos;
455
  }
456
457
  return (int)pos - (int)from;
458
}
459
460
/**
461
 * @brief   Print content of the shell line
462
 *
463
 * @param[in] shell   Pointer to the shell object.
464
 * @param[in] from    First position to start printing from.
465
 * @param[in] to      Position after the last character to print.
466
 *
467
 * @return            Number of characters printed.
468
 */
469
static inline size_t _printLine(aos_shell_t* shell, const size_t from, const size_t to)
470
{
471
  aosDbgCheck(shell != NULL);
472
473
  // local variables
474
  size_t cnt;
475
476
  for (cnt = 0; from + cnt < to; ++cnt) {
477 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, shell->line[from + cnt]);
478 e545e620 Thomas Schöpping
  }
479
480
  return cnt;
481
}
482
483
/**
484
 * @brief   Compare two characters.
485
 *
486
 * @param[in] lhs       First character to compare.
487
 * @param[in] rhs       Second character to compare.
488
 *
489
 * @return              How well the characters match.
490
 */
491
static inline charmatch_t _charcmp(char lhs, char rhs)
492
{
493
  // if lhs is a upper case letter and rhs is a lower case letter
494
  if (lhs >= 'A' && lhs <= 'Z' && rhs >= 'a' && rhs <= 'z') {
495
    return (lhs == (rhs - 'a' + 'A')) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
496
  }
497
  // if lhs is a lower case letter and rhs is a upper case letter
498
  else if (lhs >= 'a' && lhs <= 'z' && rhs >= 'A' && rhs <= 'Z') {
499
    return ((lhs - 'a' + 'A') == rhs) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
500
  }
501
  // default
502
  else {
503
    return (lhs == rhs) ? CHAR_MATCH_CASE : CHAR_MATCH_NOT;
504
  }
505
}
506
507
/**
508
 * @brief   Maps an character from ASCII to a modified custom encoding.
509
 * @details The custom character encoding is very similar to ASCII and has the following structure:
510
 *          0x00=NULL ... 0x40='@' (identically to ASCII)
511
 *          0x4A='a'; 0x4B='A'; 0x4C='b'; 0x4D='B' ... 0x73='z'; 0x74='Z' (custom letter order)
512
 *          0x75='[' ... 0x7A='`' (0x5B..0x60 is ASCII)
513
 *          0x7B='{' ... 0x7F=DEL (identically to ASCII)
514
 *
515
 * @param[in] c   Character to map to the custom encoding.
516
 *
517
 * @return    The customly encoded character.
518
 */
519
static inline char _mapAscii2Custom(const char c)
520
{
521
  if (c >= 'A' && c <= 'Z') {
522
    return ((c - 'A') * 2) + 'A' + 1;
523
  } else if (c > 'Z' && c < 'a') {
524
    return c + ('z' - 'a') + 1;
525
  } else if (c >= 'a' && c <= 'z') {
526
    return ((c - 'a') * 2) + 'A';
527
  } else {
528
    return c;
529
  }
530
}
531
532
/**
533
 * @brief   Compares two strings wrt letter case.
534
 * @details Comparisson uses a custom character encoding or mapping.
535
 *          See @p _mapAscii2Custom for details.
536
 *
537
 * @param[in] str1    First string to compare.
538
 * @param[in] str2    Second string to compare.
539
 * @param[in] cs      Flag indicating whether comparison shall be case sensitive.
540
 * @param[in,out] n   Maximum number of character to compare (in) and number of matching characters (out).
541
 *                    If a null pointer is specified, this parameter is ignored.
542
 *                    If the value pointed to is zero, comarison will not be limited.
543
 * @param[out] m      Optional indicator whether there was at least one case mismatch.
544
 *
545
 * @return      Integer value indicating the relationship between the strings.
546
 * @retval <0   The first character that does not match has a lower value in str1 than in str2.
547
 * @retval  0   The contents of both strings are equal.
548
 * @retval >0   The first character that does not match has a greater value in str1 than in str2.
549
 */
550
static int _strccmp(const char *str1, const char *str2, bool cs, size_t* n, charmatch_t* m)
551
{
552
  aosDbgCheck(str1 != NULL);
553
  aosDbgCheck(str2 != NULL);
554
555
  // initialize variables
556
  if (m) {
557
    *m = CHAR_MATCH_NOT;
558
  }
559
  size_t i = 0;
560
561
  // iterate through the strings
562
  while ((n == NULL) || (*n == 0) || (*n > 0 && i < *n)) {
563
    // break on NUL
564
    if (str1[i] == '\0' || str2[i] == '\0') {
565
      if (n) {
566
        *n = i;
567
      }
568
      break;
569
    }
570
    // compare character
571
    const charmatch_t match = _charcmp(str1[i], str2[i]);
572
    if ((match == CHAR_MATCH_CASE) || (!cs && match == CHAR_MATCH_NCASE)) {
573
      if (m != NULL && *m != CHAR_MATCH_NCASE) {
574
        *m = match;
575
      }
576
      ++i;
577
    } else {
578
      if (n) {
579
        *n = i;
580
      }
581
      break;
582
    }
583
  }
584
585
  return _mapAscii2Custom(str1[i]) - _mapAscii2Custom(str2[i]);
586
}
587
588 27286ba5 Thomas Schöpping
/**
589
 * @brief   Read input from a channel as long as there is data available.
590
 *
591
 * @param[in]   shell     Pointer to the shell object.
592
 * @param[in]   channel   The channel to read from.
593
 * @param[out]  n         Pointer to a variable to store the number of read characters to.
594
 *
595
 * @return
596
 */
597 ba516b61 Thomas Schöpping
static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, size_t* n)
598 e545e620 Thomas Schöpping
{
599
  aosDbgCheck(shell != NULL);
600 ba516b61 Thomas Schöpping
  aosDbgCheck(channel != NULL);
601
  aosDbgCheck(n != NULL);
602 e545e620 Thomas Schöpping
603
  // local variables
604 ba516b61 Thomas Schöpping
  aos_shellaction_t action = AOS_SHELL_ACTION_NONE;
605 e545e620 Thomas Schöpping
  char c;
606 dd8738ea Thomas Schöpping
  special_key_t key;
607 e545e620 Thomas Schöpping
608 ba516b61 Thomas Schöpping
  // initialize output variables
609
  *n = 0;
610
611
  // read character by character from the channel
612
  while (chnReadTimeout(channel, (uint8_t*)&c, 1, TIME_IMMEDIATE)) {
613 dd8738ea Thomas Schöpping
    key = KEY_UNKNOWN;
614 e545e620 Thomas Schöpping
615
    // parse escape sequence
616 ba516b61 Thomas Schöpping
    if (shell->inputdata.escp > 0) {
617
      shell->inputdata.escseq[shell->inputdata.escp] = c;
618
      ++shell->inputdata.escp;
619
      key = _interpreteEscapeSequence(shell->inputdata.escseq);
620 e545e620 Thomas Schöpping
      if (key == KEY_AMBIGUOUS) {
621
        // read next byte to resolve ambiguity
622
        continue;
623
      } else {
624 ba516b61 Thomas Schöpping
        /*
625
         * If the escape sequence could either be parsed sucessfully
626
         * or there is no match (KEY_UNKNOWN),
627
         * reset the sequence variable and interprete key/character
628
         */
629
        shell->inputdata.escp = 0;
630
        memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
631 e545e620 Thomas Schöpping
      }
632
    }
633
634 ba516b61 Thomas Schöpping
    /* interprete keys or character */
635 e545e620 Thomas Schöpping
    {
636 ba516b61 Thomas Schöpping
      // default
637
      action = AOS_SHELL_ACTION_NONE;
638
639
      // printable character
640
      if (key == KEY_UNKNOWN && c >= '\x20' && c <= '\x7E') {
641
        action = AOS_SHELL_ACTION_READCHAR;
642
      }
643
644
      // tab key or character
645
      else if (key == KEY_TAB || c == '\x09') {
646
        /*
647
         * pressing tab once applies auto fill
648
         * pressing tab a second time prints suggestions
649
         */
650
        if (shell->inputdata.lastaction == AOS_SHELL_ACTION_AUTOFILL || shell->inputdata.lastaction == AOS_SHELL_ACTION_SUGGEST) {
651
          action = AOS_SHELL_ACTION_SUGGEST;
652 e545e620 Thomas Schöpping
        } else {
653 ba516b61 Thomas Schöpping
          action = AOS_SHELL_ACTION_AUTOFILL;
654 e545e620 Thomas Schöpping
        }
655 ba516b61 Thomas Schöpping
      }
656
657
      // INS key
658
      else if (key == KEY_INSERT) {
659
        action = AOS_SHELL_ACTION_INSERTTOGGLE;
660
      }
661
662
      // DEL key or character
663
      else if (key == KEY_DELETE || c == '\x7F') {
664
        // ignore if cursor is at very right
665
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
666
          action = AOS_SHELL_ACTION_DELETEFORWARD;
667 e545e620 Thomas Schöpping
        }
668 ba516b61 Thomas Schöpping
      }
669
670
      // backspace key or character
671
      else if (key == KEY_BACKSPACE || c == '\x08') {
672
        // ignore if cursor is at very left
673
        if (shell->inputdata.cursorpos > 0) {
674
          action = AOS_SHELL_ACTION_DELETEBACKWARD;
675 e545e620 Thomas Schöpping
        }
676 ba516b61 Thomas Schöpping
      }
677
678
      // 'page up' of 'arrow up' key
679
      else if (key == KEY_PAGE_UP || key == KEY_ARROW_UP) {
680 e545e620 Thomas Schöpping
        // ignore if there was some input
681 ba516b61 Thomas Schöpping
        if (shell->inputdata.noinput) {
682
          action = AOS_SHELL_ACTION_RECALLLAST;
683 e545e620 Thomas Schöpping
        }
684 ba516b61 Thomas Schöpping
      }
685
686
      // 'page down' key, 'arrow done' key, 'end of test' character or 'end of transmission' character
687
      else if (key == KEY_PAGE_DOWN || key == KEY_ARROW_DOWN || c == '\x03' || c == '\x03') {
688 e545e620 Thomas Schöpping
        // ignore if line is empty
689 ba516b61 Thomas Schöpping
        if (shell->inputdata.lineend > 0) {
690
          action = AOS_SHELL_ACTION_CLEAR;
691 e545e620 Thomas Schöpping
        }
692 ba516b61 Thomas Schöpping
      }
693
694
      // 'home' key
695
      else if (key == KEY_HOME) {
696 e545e620 Thomas Schöpping
        // ignore if cursor is very left
697 ba516b61 Thomas Schöpping
        if (shell->inputdata.cursorpos > 0) {
698
          action = AOS_SHELL_ACTION_CURSOR2START;
699 e545e620 Thomas Schöpping
        }
700 ba516b61 Thomas Schöpping
      }
701
702
      // 'end' key
703
      else if (key == KEY_END) {
704
        // ignore if cursos is very right
705
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
706
          action = AOS_SHELL_ACTION_CURSOR2END;
707 e545e620 Thomas Schöpping
        }
708 ba516b61 Thomas Schöpping
      }
709
710
      // 'arrow left' key
711
      else if (key == KEY_ARROW_LEFT) {
712 e545e620 Thomas Schöpping
        // ignore if cursor is very left
713 ba516b61 Thomas Schöpping
        if (shell->inputdata.cursorpos > 0) {
714
          action = AOS_SHELL_ACTION_CURSORLEFT;
715 e545e620 Thomas Schöpping
        }
716 ba516b61 Thomas Schöpping
      }
717
718
      // 'arrow right' key
719
      else if (key == KEY_ARROW_RIGHT) {
720
        // irgnore if cursor is very right
721
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
722
          action = AOS_SHELL_ACTION_CURSORRIGHT;
723 e545e620 Thomas Schöpping
        }
724 ba516b61 Thomas Schöpping
      }
725
726
      // carriage return ('\r') or line feed ('\n') character
727
      else if (c == '\x0D' || c == '\x0A') {
728
        action = AOS_SHELL_ACTION_EXECUTE;
729
      }
730
731
      // ESC key or [ESCAPE] character
732
      else if (key == KEY_ESCAPE || c == '\x1B') {
733
        action = AOS_SHELL_ACTION_ESCSTART;
734 e545e620 Thomas Schöpping
      }
735
    }
736
737
    /* handle function */
738 ba516b61 Thomas Schöpping
    switch (action) {
739
      case AOS_SHELL_ACTION_READCHAR:
740
      {
741 e545e620 Thomas Schöpping
        // line is full
742 ba516b61 Thomas Schöpping
        if (shell->inputdata.lineend + 1 >= shell->linesize) {
743
          _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
744
          chprintf((BaseSequentialStream*)&shell->stream, "\n\tmaximum line width reached\n");
745 e545e620 Thomas Schöpping
          _printPrompt(shell);
746 ba516b61 Thomas Schöpping
          _printLine(shell, 0, shell->inputdata.lineend);
747
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
748 e545e620 Thomas Schöpping
        }
749
        // read character
750
        else {
751
          // clear old line content on first input
752 ba516b61 Thomas Schöpping
          if (shell->inputdata.noinput) {
753 e545e620 Thomas Schöpping
            memset(shell->line, '\0', shell->linesize);
754 ba516b61 Thomas Schöpping
            shell->inputdata.noinput = false;
755 e545e620 Thomas Schöpping
          }
756
          // overwrite content
757
          if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
758 ba516b61 Thomas Schöpping
            shell->line[shell->inputdata.cursorpos] = c;
759
            ++shell->inputdata.cursorpos;
760
            shell->inputdata.lineend = (shell->inputdata.cursorpos > shell->inputdata.lineend) ? shell->inputdata.cursorpos : shell->inputdata.lineend;
761
            streamPut(&shell->stream, (uint8_t)c);
762 e545e620 Thomas Schöpping
          }
763
          // insert character
764
          else {
765 ba516b61 Thomas Schöpping
            memmove(&(shell->line[shell->inputdata.cursorpos+1]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
766
            shell->line[shell->inputdata.cursorpos] = c;
767
            ++shell->inputdata.lineend;
768
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
769
            ++shell->inputdata.cursorpos;
770
            _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
771 e545e620 Thomas Schöpping
          }
772
        }
773
        break;
774 ba516b61 Thomas Schöpping
      }
775 e545e620 Thomas Schöpping
776 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_AUTOFILL:
777 e545e620 Thomas Schöpping
      {
778
        const char* fill = shell->line;
779 ba516b61 Thomas Schöpping
        size_t cmatch = shell->inputdata.cursorpos;
780 e545e620 Thomas Schöpping
        charmatch_t matchlevel = CHAR_MATCH_NOT;
781
        size_t n;
782
        // iterate through command list
783
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
784
          // compare current match with command
785
          n = cmatch;
786
          charmatch_t mlvl = CHAR_MATCH_NOT;
787
          _strccmp(fill, cmd->name, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, (n == 0) ? NULL : &n, &mlvl);
788
          const int cmp = (n < cmatch) ?
789
                            (n - cmatch) :
790
                            (cmd->name[n] != '\0') ?
791
                              strlen(cmd->name) - n :
792
                              0;
793
          // if an exact match was found
794 ba516b61 Thomas Schöpping
          if (cmatch + cmp == shell->inputdata.cursorpos) {
795
            cmatch = shell->inputdata.cursorpos;
796 e545e620 Thomas Schöpping
            fill = cmd->name;
797
            // break the loop only if there are no case mismatches with the input
798 ba516b61 Thomas Schöpping
            n = shell->inputdata.cursorpos;
799 e545e620 Thomas Schöpping
            _strccmp(fill, shell->line, false, &n, &mlvl);
800
            if (mlvl == CHAR_MATCH_CASE) {
801
              break;
802
            }
803
          }
804
          // if a not exact match was found
805 ba516b61 Thomas Schöpping
          else if (cmatch + cmp > shell->inputdata.cursorpos) {
806 e545e620 Thomas Schöpping
            // if this is the first one
807
            if (fill == shell->line) {
808
              cmatch += cmp;
809
              fill = cmd->name;
810
            }
811
            // if this is a worse one
812
            else if ((cmp < 0) || (cmp == 0 && mlvl == CHAR_MATCH_CASE)) {
813
              cmatch += cmp;
814
            }
815
          }
816
          // non matching commands are ignored
817
          else {}
818
        }
819
        // evaluate if there are case mismatches
820
        n = cmatch;
821
        _strccmp(shell->line, fill, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, &n, &matchlevel);
822
        // print the auto fill if any
823 ba516b61 Thomas Schöpping
        if (cmatch > shell->inputdata.cursorpos || (cmatch == shell->inputdata.cursorpos && matchlevel == CHAR_MATCH_NCASE)) {
824
          shell->inputdata.noinput = false;
825 e545e620 Thomas Schöpping
          // limit auto fill so it will not overflow the line width
826 ba516b61 Thomas Schöpping
          if (shell->inputdata.lineend + (cmatch - shell->inputdata.cursorpos) > shell->linesize) {
827
            cmatch = shell->linesize - shell->inputdata.lineend + shell->inputdata.cursorpos;
828 e545e620 Thomas Schöpping
          }
829
          // move trailing memory further in the line
830 ba516b61 Thomas Schöpping
          memmove(&(shell->line[cmatch]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
831
          shell->inputdata.lineend += cmatch - shell->inputdata.cursorpos;
832 e545e620 Thomas Schöpping
          // if there was no incorrect case when matching
833
          if (matchlevel == CHAR_MATCH_CASE) {
834
            // insert fill command name to line
835 ba516b61 Thomas Schöpping
            memcpy(&(shell->line[shell->inputdata.cursorpos]), &(fill[shell->inputdata.cursorpos]), cmatch - shell->inputdata.cursorpos);
836 e545e620 Thomas Schöpping
            // print the output
837 ba516b61 Thomas Schöpping
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
838 e545e620 Thomas Schöpping
          } else {
839
            // overwrite line with fill command name
840
            memcpy(shell->line, fill, cmatch);
841
            // reprint the whole line
842 ba516b61 Thomas Schöpping
            _moveCursor(shell, shell->inputdata.cursorpos, 0);
843
            _printLine(shell, 0, shell->inputdata.lineend);
844 e545e620 Thomas Schöpping
          }
845
          // move cursor to the end of the matching sequence
846 ba516b61 Thomas Schöpping
          shell->inputdata.cursorpos = cmatch;
847
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
848 e545e620 Thomas Schöpping
        }
849
        break;
850
      }
851
852 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_SUGGEST:
853 e545e620 Thomas Schöpping
      {
854
        unsigned int matches = 0;
855
        // iterate through command list
856
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
857
          // compare line content with command, excpet if cursorpos=0
858 ba516b61 Thomas Schöpping
          size_t i = shell->inputdata.cursorpos;
859
          if (shell->inputdata.cursorpos > 0) {
860 e545e620 Thomas Schöpping
            _strccmp(shell->line, cmd->name, true, &i, NULL);
861
          }
862 ba516b61 Thomas Schöpping
          const int cmp = (i < shell->inputdata.cursorpos) ?
863
                            (i - shell->inputdata.cursorpos) :
864 e545e620 Thomas Schöpping
                            (cmd->name[i] != '\0') ?
865
                              strlen(cmd->name) - i :
866
                              0;
867
          // if a match was found
868
          if (cmp > 0) {
869
            // if this is the first one
870
            if (matches == 0) {
871 ba516b61 Thomas Schöpping
              _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
872
              streamPut(&shell->stream, '\n');
873 e545e620 Thomas Schöpping
            }
874
            // print the command
875 ba516b61 Thomas Schöpping
            chprintf((BaseSequentialStream*)&shell->stream, "\t%s\n", cmd->name);
876 e545e620 Thomas Schöpping
            ++matches;
877
          }
878
        }
879
        // reprint the prompt and line if any matches have been found
880
        if (matches > 0) {
881
          _printPrompt(shell);
882 ba516b61 Thomas Schöpping
          _printLine(shell, 0, shell->inputdata.lineend);
883
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
884
          shell->inputdata.noinput = false;
885 e545e620 Thomas Schöpping
        }
886
        break;
887
      }
888
889 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_INSERTTOGGLE:
890
      {
891 e545e620 Thomas Schöpping
        if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
892
          shell->config &= ~AOS_SHELL_CONFIG_INPUT_OVERWRITE;
893
        } else {
894
          shell->config |= AOS_SHELL_CONFIG_INPUT_OVERWRITE;
895
        }
896
        break;
897 ba516b61 Thomas Schöpping
      }
898 e545e620 Thomas Schöpping
899 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_DELETEFORWARD:
900
      {
901
        --shell->inputdata.lineend;
902
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
903
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
904
        streamPut(&shell->stream, ' ');
905
        _moveCursor(shell, shell->inputdata.lineend + 1, shell->inputdata.cursorpos);
906 e545e620 Thomas Schöpping
        break;
907 ba516b61 Thomas Schöpping
      }
908 e545e620 Thomas Schöpping
909 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_DELETEBACKWARD:
910
      {
911
        --shell->inputdata.cursorpos;
912
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
913
        --shell->inputdata.lineend;
914
        shell->line[shell->inputdata.lineend] = '\0';
915
        _moveCursor(shell, shell->inputdata.cursorpos + 1, shell->inputdata.cursorpos);
916
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
917
        streamPut(&shell->stream, ' ');
918
        _moveCursor(shell, shell->inputdata.lineend+1, shell->inputdata.cursorpos);
919 e545e620 Thomas Schöpping
        break;
920 ba516b61 Thomas Schöpping
      }
921 e545e620 Thomas Schöpping
922 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_RECALLLAST:
923 e545e620 Thomas Schöpping
      {
924
        // replace any intermediate NUL bytes with spaces
925 ba516b61 Thomas Schöpping
        shell->inputdata.lineend = 0;
926 e545e620 Thomas Schöpping
        size_t nul_start = 0;
927
        size_t nul_end = 0;
928
        // search line for a NUL byte
929
        while (nul_start < shell->linesize) {
930
          if (shell->line[nul_start] == '\0') {
931
            nul_end = nul_start + 1;
932
            // keep searcjing for a byte that is not NUL
933
            while (nul_end < shell->linesize) {
934
              if (shell->line[nul_end] != '\0') {
935
                // an intermediate NUL sequence was found
936
                memset(&(shell->line[nul_start]), ' ', nul_end - nul_start);
937 ba516b61 Thomas Schöpping
                shell->inputdata.lineend = nul_end + 1;
938 e545e620 Thomas Schöpping
                break;
939
              } else {
940
                ++nul_end;
941
              }
942
            }
943
            nul_start = nul_end + 1;
944
          } else {
945 ba516b61 Thomas Schöpping
            ++shell->inputdata.lineend;
946 e545e620 Thomas Schöpping
            ++nul_start;
947
          }
948
        }
949 ba516b61 Thomas Schöpping
        shell->inputdata.cursorpos = shell->inputdata.lineend;
950 e545e620 Thomas Schöpping
        // print the line
951 ba516b61 Thomas Schöpping
        shell->inputdata.noinput = _printLine(shell, 0, shell->inputdata.lineend) == 0;
952 e545e620 Thomas Schöpping
        break;
953
      }
954
955 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CLEAR:
956
      {
957 e545e620 Thomas Schöpping
        // clear output
958 ba516b61 Thomas Schöpping
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
959
        for (shell->inputdata.cursorpos = 0; shell->inputdata.cursorpos < shell->inputdata.lineend; ++shell->inputdata.cursorpos) {
960
          streamPut(&shell->stream, ' ');
961 e545e620 Thomas Schöpping
        }
962 ba516b61 Thomas Schöpping
        _moveCursor(shell, shell->inputdata.lineend, 0);
963
        shell->inputdata.cursorpos = 0;
964
        shell->inputdata.lineend = 0;
965
        shell->inputdata.noinput = true;
966 e545e620 Thomas Schöpping
        break;
967 ba516b61 Thomas Schöpping
      }
968 e545e620 Thomas Schöpping
969 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSOR2START:
970
      {
971
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
972
        shell->inputdata.cursorpos = 0;
973 e545e620 Thomas Schöpping
        break;
974 ba516b61 Thomas Schöpping
      }
975 e545e620 Thomas Schöpping
976 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSOR2END:
977
      {
978
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
979
        shell->inputdata.cursorpos = shell->inputdata.lineend;
980 e545e620 Thomas Schöpping
        break;
981 ba516b61 Thomas Schöpping
      }
982 e545e620 Thomas Schöpping
983 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSORLEFT:
984
      {
985
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos-1);
986
        --shell->inputdata.cursorpos;
987 e545e620 Thomas Schöpping
        break;
988 ba516b61 Thomas Schöpping
      }
989 e545e620 Thomas Schöpping
990 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSORRIGHT:
991
      {
992
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos+1);
993
        ++shell->inputdata.cursorpos;
994 e545e620 Thomas Schöpping
        break;
995 ba516b61 Thomas Schöpping
      }
996 e545e620 Thomas Schöpping
997 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_EXECUTE:
998
      {
999
        streamPut(&shell->stream, '\n');
1000
        // set the number of read bytes and return
1001
        if (!shell->inputdata.noinput) {
1002
          *n = shell->linesize - shell->inputdata.lineend;
1003 e545e620 Thomas Schöpping
          // fill the remainder of the line with NUL bytes
1004 ba516b61 Thomas Schöpping
          memset(&(shell->line[shell->inputdata.lineend]), '\0', *n);
1005
          // reset static variables
1006
          shell->inputdata.noinput = true;
1007 e545e620 Thomas Schöpping
        }
1008 ba516b61 Thomas Schöpping
        return AOS_SUCCESS;
1009
      }
1010 e545e620 Thomas Schöpping
1011 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_ESCSTART:
1012
      {
1013
        shell->inputdata.escseq[0] = c;
1014
        ++shell->inputdata.escp;
1015 e545e620 Thomas Schöpping
        break;
1016 ba516b61 Thomas Schöpping
      }
1017 e545e620 Thomas Schöpping
1018 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_NONE:
1019 e545e620 Thomas Schöpping
      default:
1020 ba516b61 Thomas Schöpping
      {
1021 e545e620 Thomas Schöpping
        // do nothing (ignore input) and read next byte
1022
        continue;
1023 ba516b61 Thomas Schöpping
      }
1024
    } /* end of switch */
1025 e545e620 Thomas Schöpping
1026 ba516b61 Thomas Schöpping
    shell->inputdata.lastaction = action;
1027 e545e620 Thomas Schöpping
  } /* end of while */
1028
1029 ba516b61 Thomas Schöpping
  // no more data could be read from the channel
1030
  return AOS_WARNING;
1031 e545e620 Thomas Schöpping
}
1032
1033
/**
1034
 * @brief   Parses the content of the input buffer (line) to separate arguments.
1035
 *
1036
 * @param[in] shell   Pointer to the shell object.
1037
 *
1038
 * @return            Number of arguments found.
1039
 */
1040
static size_t _parseArguments(aos_shell_t* shell)
1041
{
1042
  aosDbgCheck(shell != NULL);
1043
1044
  /*
1045
   * States for a very small FSM.
1046
   */
1047
  typedef enum {
1048
    START,
1049
    SPACE,
1050
    TEXT,
1051
    END,
1052
  } state_t;
1053
1054
  // local variables
1055
  state_t state = START;
1056
  size_t arg = 0;
1057
1058
  // iterate through the line
1059
  for (char* c = shell->line; c < shell->line + shell->linesize; ++c) {
1060
    // terminate at first NUL byte
1061
    if (*c == '\0') {
1062
      state = END;
1063
      break;
1064
    }
1065
    // spaces become NUL bytes
1066
    else if (*c == ' ') {
1067
      *c = '\0';
1068
      state = SPACE;
1069
    }
1070
    // handle non-NUL bytes
1071
    else {
1072
      switch (state) {
1073
        case START:
1074
        case SPACE:
1075
          // ignore too many arguments
1076
          if (arg < shell->arglistsize) {
1077
            shell->arglist[arg] = c;
1078
          }
1079
          ++arg;
1080
          break;
1081
        case TEXT:
1082
        case END:
1083
        default:
1084
          break;
1085
      }
1086
      state = TEXT;
1087
    }
1088
  }
1089
1090
  // set all remaining argument pointers to NULL
1091
  for (size_t a = arg; a < shell->arglistsize; ++a) {
1092
    shell->arglist[a] = NULL;
1093
  }
1094
1095
  return arg;
1096
}
1097
1098
/**
1099
 * @brief   Initializes a shell object with the specified parameters.
1100
 *
1101
 * @param[in] shell         Pointer to the shell object.
1102
 * @param[in] stream        I/O stream to use.
1103
 * @param[in] prompt        Prompt line to print (NULL = use default prompt).
1104
 * @param[in] line          Pointer to the input buffer.
1105
 * @param[in] linesize      Size of the input buffer.
1106
 * @param[in] arglist       Pointer to the argument buffer.
1107
 * @param[in] arglistsize   Size of te argument buffer.
1108
 */
1109 ba516b61 Thomas Schöpping
void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char* prompt, char* line, size_t linesize, char** arglist, size_t arglistsize)
1110 e545e620 Thomas Schöpping
{
1111
  aosDbgCheck(shell != NULL);
1112 ba516b61 Thomas Schöpping
  aosDbgCheck(oseventsource != NULL);
1113 e545e620 Thomas Schöpping
  aosDbgCheck(line != NULL);
1114
  aosDbgCheck(arglist != NULL);
1115
1116
  // set parameters
1117
  shell->thread = NULL;
1118
  chEvtObjectInit(&shell->eventSource);
1119 ba516b61 Thomas Schöpping
  shell->os.eventSource = oseventsource;
1120
  aosShellStreamInit(&shell->stream);
1121 e545e620 Thomas Schöpping
  shell->prompt = prompt;
1122
  shell->commands = NULL;
1123
  shell->execstatus.command = NULL;
1124
  shell->execstatus.retval = 0;
1125
  shell->line = line;
1126
  shell->linesize = linesize;
1127 ba516b61 Thomas Schöpping
  shell->inputdata.lastaction = AOS_SHELL_ACTION_NONE;
1128
  shell->inputdata.escp = 0;
1129
  memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
1130
  shell->inputdata.cursorpos = 0;
1131
  shell->inputdata.lineend = 0;
1132
  shell->inputdata.noinput = true;
1133 e545e620 Thomas Schöpping
  shell->arglist = arglist;
1134
  shell->arglistsize = arglistsize;
1135
  shell->config = 0x00;
1136
1137
  // initialize arrays
1138
  memset(shell->line, '\0', shell->linesize);
1139
  for (size_t a = 0; a < shell->arglistsize; ++a) {
1140
    shell->arglist[a] = NULL;
1141
  }
1142
1143
  return;
1144
}
1145
1146
/**
1147 ba516b61 Thomas Schöpping
 * @brief   Initialize an AosShellStream object.
1148
 *
1149
 * @param[in] stream  The AosShellStrem to initialize.
1150
 */
1151
void aosShellStreamInit(AosShellStream* stream)
1152
{
1153
  aosDbgCheck(stream != NULL);
1154
1155
  stream->vmt = &_streamvmt;
1156
  stream->channel = NULL;
1157
1158
  return;
1159
}
1160
1161
/**
1162
 * @brief   Initialize an AosShellChannel object with the specified parameters.
1163
 *
1164 dd8738ea Thomas Schöpping
 * @param[in] channel       The AosShellChannel to initialize.
1165
 * @param[in] asyncchannel  An BaseAsynchronousChannel this AosShellChannel is associated with.
1166 ba516b61 Thomas Schöpping
 */
1167 dd8738ea Thomas Schöpping
void aosShellChannelInit(AosShellChannel* channel, BaseAsynchronousChannel* asyncchannel)
1168 ba516b61 Thomas Schöpping
{
1169
  aosDbgCheck(channel != NULL);
1170 dd8738ea Thomas Schöpping
  aosDbgCheck(asyncchannel != NULL);
1171 ba516b61 Thomas Schöpping
1172
  channel->vmt = &_channelvmt;
1173 dd8738ea Thomas Schöpping
  channel->asyncchannel = asyncchannel;
1174
  channel->listener.wflags = 0;
1175 ba516b61 Thomas Schöpping
  channel->next = NULL;
1176
  channel->flags = 0;
1177
1178
  return;
1179
}
1180
1181
/**
1182 e545e620 Thomas Schöpping
 * @brief   Inserts a command to the shells list of commands.
1183
 *
1184
 * @param[in] shell   Pointer to the shell object.
1185
 * @param[in] cmd     Pointer to the command to add.
1186
 *
1187
 * @return            A status value.
1188
 * @retval AOS_SUCCESS  The command was added successfully.
1189
 * @retval AOS_ERROR    Another command with identical name already exists.
1190
 */
1191
aos_status_t aosShellAddCommand(aos_shell_t *shell, aos_shellcommand_t *cmd)
1192
{
1193
  aosDbgCheck(shell != NULL);
1194
  aosDbgCheck(cmd != NULL);
1195
  aosDbgCheck(cmd->name != NULL && strlen(cmd->name) > 0 && strchr(cmd->name, ' ') == NULL && strchr(cmd->name, '\t') == NULL);
1196
  aosDbgCheck(cmd->callback != NULL);
1197
  aosDbgCheck(cmd->next == NULL);
1198
1199
  aos_shellcommand_t* prev = NULL;
1200
  aos_shellcommand_t** curr = &(shell->commands);
1201
1202
  // insert the command to the list wrt lexographical order (exception: lower case characters preceed upper their uppercase counterparts)
1203 ba516b61 Thomas Schöpping
  while (*curr != NULL) {
1204
    // iterate through the list as long as the command names are 'smaller'
1205
    const int cmp = _strccmp((*curr)->name, cmd->name, true, NULL, NULL);
1206
    if (cmp < 0) {
1207
      prev = *curr;
1208
      curr = &((*curr)->next);
1209
      continue;
1210
    }
1211
    // error if the command already exists
1212
    else if (cmp == 0) {
1213
      return AOS_ERROR;
1214
    }
1215
    // insert the command as soon as a 'larger' name was found
1216
    else /* if (cmpval > 0) */ {
1217
      cmd->next = *curr;
1218
      // special case: the first command is larger
1219
      if (prev == NULL) {
1220
        shell->commands = cmd;
1221
      } else {
1222
        prev->next = cmd;
1223 e545e620 Thomas Schöpping
      }
1224 ba516b61 Thomas Schöpping
      return AOS_SUCCESS;
1225 e545e620 Thomas Schöpping
    }
1226
  }
1227 ba516b61 Thomas Schöpping
  // the end of the list has been reached
1228
1229
  // append the command
1230
  *curr = cmd;
1231
  return AOS_SUCCESS;
1232 e545e620 Thomas Schöpping
}
1233
1234
/**
1235
 * @brief   Removes a command from the shells list of commands.
1236
 *
1237
 * @param[in] shell     Pointer to the shell object.
1238
 * @param[in] cmd       Name of the command to removde.
1239
 * @param[out] removed  Optional pointer to the command that was removed.
1240
 *
1241
 * @return              A status value.
1242
 * @retval AOS_SUCCESS  The command was removed successfully.
1243
 * @retval AOS_ERROR    The command name was not found.
1244
 */
1245
aos_status_t aosShellRemoveCommand(aos_shell_t *shell, char *cmd, aos_shellcommand_t **removed)
1246
{
1247
  aosDbgCheck(shell != NULL);
1248
  aosDbgCheck(cmd != NULL && strlen(cmd) > 0);
1249
1250
  aos_shellcommand_t* prev = NULL;
1251
  aos_shellcommand_t** curr = &(shell->commands);
1252
1253
  // iterate through the list and seach for the specified command name
1254
  while (curr != NULL) {
1255
    const int cmpval = strcmp((*curr)->name, cmd);
1256
    // iterate through the list as long as the command names are 'smaller'
1257
    if (cmpval < 0) {
1258
      prev = *curr;
1259
      curr = &((*curr)->next);
1260
      continue;
1261
    }
1262
    // remove the command when found
1263
    else if (cmpval == 0) {
1264
      // special case: the first command matches
1265
      if (prev == NULL) {
1266
        shell->commands = (*curr)->next;
1267
      } else {
1268
        prev->next = (*curr)->next;
1269
      }
1270
      (*curr)->next = NULL;
1271
      // set the optional output argument
1272
      if (removed != NULL) {
1273
        *removed = *curr;
1274
      }
1275
      return AOS_SUCCESS;
1276
    }
1277
    // break the loop if the command names are 'larger'
1278
    else /* if (cmpval > 0) */ {
1279
      break;
1280
    }
1281
  }
1282
1283
  // if the command was not found, return an error
1284
  return AOS_ERROR;
1285
}
1286
1287
/**
1288 aed3754b Thomas Schöpping
 * @brief   Count the number of commands assigned to the shell.
1289
 *
1290
 * @param[in] shell   The shell to count the commands for.
1291
 *
1292
 * @return  The number of commands associated to the shell.
1293
 */
1294
unsigned int aosShellCountCommands(aos_shell_t* shell)
1295
{
1296
  aosDbgCheck(shell != NULL);
1297
1298
  unsigned int count = 0;
1299
  aos_shellcommand_t* cmd = shell->commands;
1300
  while (cmd != NULL) {
1301
    ++count;
1302
    cmd = cmd->next;
1303
  }
1304
1305
  return count;
1306
}
1307
1308
/**
1309 ba516b61 Thomas Schöpping
 * @brief   Add a channel to a AosShellStream.
1310
 *
1311
 * @param[in] stream    The AosShellStream to extend.
1312
 * @param[in] channel   The channel to be added to the stream.
1313
 */
1314
void aosShellStreamAddChannel(AosShellStream* stream, AosShellChannel* channel)
1315
{
1316
  aosDbgCheck(stream != NULL);
1317 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->next == NULL && (channel->flags & AOS_SHELLCHANNEL_ATTACHED) == 0);
1318 ba516b61 Thomas Schöpping
1319
  // prepend the new channel
1320
  chSysLock();
1321
  channel->flags |= AOS_SHELLCHANNEL_ATTACHED;
1322
  channel->next = stream->channel;
1323
  stream->channel = channel;
1324
  chSysUnlock();
1325
1326
  return;
1327
}
1328
1329
/**
1330
 * @brief   Remove a channel from an AosShellStream.
1331
 *
1332
 * @param[in] stream    The AosShellStream to modify.
1333
 * @param[in] channel   The channel to remove.
1334 aed3754b Thomas Schöpping
 *
1335
 * @return              A status value.
1336
 * @retval AOS_SUCCESS  The channel was removed successfully.
1337
 * @retval AOS_ERROR    The specified channel was not found to be associated with the shell.
1338 ba516b61 Thomas Schöpping
 */
1339
aos_status_t aosShellStreamRemoveChannel(AosShellStream* stream, AosShellChannel* channel)
1340
{
1341
  aosDbgCheck(stream != NULL);
1342 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->flags & AOS_SHELLCHANNEL_ATTACHED);
1343 ba516b61 Thomas Schöpping
1344
  // local varibales
1345
  AosShellChannel* prev = NULL;
1346
  AosShellChannel* curr = stream->channel;
1347
1348
  // iterate through the list and search for the specified channel
1349
  while (curr != NULL) {
1350
    // if the channel was found
1351
    if (curr == channel) {
1352
      chSysLock();
1353
      // special case: the first channel matches (prev is NULL)
1354
      if (prev == NULL) {
1355
        stream->channel = curr->next;
1356
      } else {
1357
        prev->next = channel->next;
1358
      }
1359
      curr->next = NULL;
1360
      curr->flags &= ~AOS_SHELLCHANNEL_ATTACHED;
1361
      chSysUnlock();
1362
      return AOS_SUCCESS;
1363
    }
1364
  }
1365
1366
  // if the channel was not found, return an error
1367
  return AOS_ERROR;
1368
}
1369
1370
/**
1371
 * @brief   Enable a AosSheööChannel as input.
1372
 *
1373
 * @param[in] channel   The channel to enable as input.
1374
 */
1375
void aosShellChannelInputEnable(AosShellChannel* channel)
1376
{
1377 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1378 ba516b61 Thomas Schöpping
1379
  chSysLock();
1380
  channel->listener.wflags |= CHN_INPUT_AVAILABLE;
1381
  channel->flags |= AOS_SHELLCHANNEL_INPUT_ENABLED;
1382
  chSysUnlock();
1383
1384
  return;
1385
}
1386
1387
/**
1388
 * @brief   Disable a AosSheööChannel as input.
1389
 *
1390
 * @param[in] channel   The channel to disable as input.
1391
 */
1392
void aosShellChannelInputDisable( AosShellChannel* channel)
1393
{
1394 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1395 ba516b61 Thomas Schöpping
1396
  chSysLock();
1397
  channel->listener.wflags &= ~CHN_INPUT_AVAILABLE;
1398
  channel->flags &= ~AOS_SHELLCHANNEL_INPUT_ENABLED;
1399
  chSysUnlock();
1400
1401
  return;
1402
}
1403
1404
/**
1405
 * @brief   Enable a AosSheööChannel as output.
1406
 *
1407
 * @param[in] channel   The channel to enable as output.
1408
 */
1409
void aosShellChannelOutputEnable(AosShellChannel* channel)
1410
{
1411 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1412 ba516b61 Thomas Schöpping
1413
  channel->flags |= AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1414
1415
  return;
1416
}
1417
1418
/**
1419
 * @brief   Disable a AosSheööChannel as output.
1420
 *
1421
 * @param[in] channel   The channel to disable as output.
1422
 */
1423
void aosShellChannelOutputDisable(AosShellChannel* channel)
1424
{
1425 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1426 ba516b61 Thomas Schöpping
1427
  channel->flags &= ~AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1428
1429
  return;
1430
}
1431
1432
/**
1433 e545e620 Thomas Schöpping
 * @brief   Thread main function.
1434
 *
1435
 * @param[in] aosShellThread    Name of the function;
1436
 * @param[in] shell             Pointer to the shell object.
1437
 */
1438 af4fd4a2 Thomas Schöpping
void aosShellThread(void* shell)
1439 e545e620 Thomas Schöpping
{
1440
  aosDbgCheck(shell != NULL);
1441
1442
  // local variables
1443 ba516b61 Thomas Schöpping
  eventmask_t eventmask;
1444
  eventflags_t eventflags;
1445
  AosShellChannel* channel;
1446
  aos_status_t readeval;
1447
  size_t nchars = 0;
1448 e545e620 Thomas Schöpping
  size_t nargs = 0;
1449 ba516b61 Thomas Schöpping
  aos_shellcommand_t* cmd;
1450
1451
1452
  // register OS related events
1453
  chEvtRegisterMask(((aos_shell_t*)shell)->os.eventSource, &(((aos_shell_t*)shell)->os.eventListener), AOS_SHELL_EVENTMASK_OS);
1454
  // register events to all input channels
1455
  for (channel = ((aos_shell_t*)shell)->stream.channel; channel != NULL; channel = channel->next) {
1456 dd8738ea Thomas Schöpping
    chEvtRegisterMaskWithFlags(&(channel->asyncchannel->event), &(channel->listener), AOS_SHELL_EVENTMASK_INPUT, channel->listener.wflags);
1457 ba516b61 Thomas Schöpping
  }
1458 e545e620 Thomas Schöpping
1459
  // fire start event
1460
  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_START);
1461
1462 ba516b61 Thomas Schöpping
  // print the prompt for the first time
1463
  _printPrompt((aos_shell_t*)shell);
1464
1465 e545e620 Thomas Schöpping
  // enter thread loop
1466
  while (!chThdShouldTerminateX()) {
1467 ba516b61 Thomas Schöpping
    // wait for event and handle it accordingly
1468
    eventmask = chEvtWaitOne(ALL_EVENTS);
1469
1470
    // handle event
1471
    switch (eventmask) {
1472
1473
      // OS related events
1474
      case AOS_SHELL_EVENTMASK_OS:
1475
      {
1476
        eventflags = chEvtGetAndClearFlags(&((aos_shell_t*)shell)->os.eventListener);
1477
        // handle shutdown/restart events
1478
        if (eventflags & AOS_SYSTEM_EVENTFLAGS_SHUTDOWN) {
1479
          chThdTerminate(((aos_shell_t*)shell)->thread);
1480
        } else {
1481
          // print an error message
1482
          chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown OS event received (0x%08X)\n", eventflags);
1483
        }
1484 e545e620 Thomas Schöpping
        break;
1485
      }
1486
1487 ba516b61 Thomas Schöpping
      // input events
1488
      case AOS_SHELL_EVENTMASK_INPUT:
1489
      {
1490
        // check and handle all channels
1491
        channel = ((aos_shell_t*)shell)->stream.channel;
1492
        while (channel != NULL) {
1493
          eventflags = chEvtGetAndClearFlags(&channel->listener);
1494
          // if there is new input
1495
          if (eventflags & CHN_INPUT_AVAILABLE) {
1496 dd8738ea Thomas Schöpping
            // read input from channel
1497
            readeval = _readChannel((aos_shell_t*)shell, channel, &nchars);
1498
            // parse input line to argument list only if the input shall be executed
1499
            nargs = (readeval == AOS_SUCCESS && nchars > 0) ? _parseArguments((aos_shell_t*)shell) : 0;
1500
            // check number of arguments
1501
            if (nargs > ((aos_shell_t*)shell)->arglistsize) {
1502
              // error too many arguments
1503
              chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\ttoo many arguments\n");
1504
            } else if (nargs > 0) {
1505
              // search command list for arg[0] and execute callback
1506
              cmd = ((aos_shell_t*)shell)->commands;
1507
              while (cmd != NULL) {
1508
                if (strcmp(((aos_shell_t*)shell)->arglist[0], cmd->name) == 0) {
1509
                  ((aos_shell_t*)shell)->execstatus.command = cmd;
1510
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXEC);
1511
                  ((aos_shell_t*)shell)->execstatus.retval = cmd->callback((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, nargs, ((aos_shell_t*)shell)->arglist);
1512
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_DONE);
1513
                  // notify if the command was not successful
1514
                  if (((aos_shell_t*)shell)->execstatus.retval != 0) {
1515
                    chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "command returned exit status %d\n", ((aos_shell_t*)shell)->execstatus.retval);
1516 ba516b61 Thomas Schöpping
                  }
1517 dd8738ea Thomas Schöpping
                  break;
1518 ba516b61 Thomas Schöpping
                }
1519 dd8738ea Thomas Schöpping
                cmd = cmd->next;
1520
              } /* end of while */
1521 e545e620 Thomas Schöpping
1522 dd8738ea Thomas Schöpping
              // if no matching command was found, print an error
1523
              if (cmd == NULL) {
1524
                chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "%s: command not found\n", ((aos_shell_t*)shell)->arglist[0]);
1525 ba516b61 Thomas Schöpping
              }
1526
            }
1527 dd8738ea Thomas Schöpping
1528
            // reset some internal variables and eprint a new prompt
1529
            if (readeval == AOS_SUCCESS && !chThdShouldTerminateX()) {
1530
              ((aos_shell_t*)shell)->inputdata.cursorpos = 0;
1531
              ((aos_shell_t*)shell)->inputdata.lineend = 0;
1532
              _printPrompt((aos_shell_t*)shell);
1533 ba516b61 Thomas Schöpping
            }
1534 e545e620 Thomas Schöpping
          }
1535 ba516b61 Thomas Schöpping
1536
          // iterate to next channel
1537
          channel = channel->next;
1538 e545e620 Thomas Schöpping
        }
1539 ba516b61 Thomas Schöpping
        break;
1540 e545e620 Thomas Schöpping
      }
1541 ba516b61 Thomas Schöpping
1542
      // other events
1543
      default:
1544
      {
1545
        // print an error message
1546 1e5f7648 Thomas Schöpping
        chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nSHELL: ERROR: unknown event received (0x%08X)\n", eventmask);
1547 ba516b61 Thomas Schöpping
        break;
1548 e545e620 Thomas Schöpping
      }
1549
1550 ba516b61 Thomas Schöpping
    } /* end of switch */
1551
1552
  } /* end of while */
1553 e545e620 Thomas Schöpping
1554
  // fire event and exit the thread
1555
  chSysLock();
1556
  chEvtBroadcastFlagsI(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXIT);
1557
  chThdExitS(MSG_OK);
1558
  // no chSysUnlock() required since the thread has been terminated an all waiting threads have been woken up
1559
}
1560 ba516b61 Thomas Schöpping
1561 2dd2e257 Thomas Schöpping
#endif /* (AMIROOS_CFG_SHELL_ENABLE == true) || (AMIROOS_CFG_TESTS_ENABLE == true)*/
1562 53710ca3 Marc Rothmann
1563
/** @} */