Statistics
| Branch: | Tag: | Revision:

amiro-os / os / core / src / aos_shell.c @ 2c99037f

History | View | Annotate | Download (46 KB)

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

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
#include <aos_shell.h>
20

    
21
#if (AMIROOS_CFG_SHELL_ENABLE == true)
22
#include <aos_debug.h>
23
#include <aos_time.h>
24
#include <aos_system.h>
25
#include <string.h>
26

    
27
/**
28
 * @brief   Event mask to be set on OS related events.
29
 */
30
#define AOS_SHELL_EVENTMASK_OS                  EVENT_MASK(0)
31

    
32
/**
33
 * @brief   Event mask to be set on a input event.
34
 */
35
#define AOS_SHELL_EVENTMASK_INPUT               EVENT_MASK(1)
36

    
37
/**
38
 * @brief   Implementation of the BaseAsynchronous write() method (inherited from BaseSequentialStream).
39
 */
40
static size_t _channelwrite(void *instance, const uint8_t *bp, size_t n)
41
{
42
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
43
    return streamWrite(((AosShellChannel*)instance)->asyncchannel, bp, n);
44
  } else {
45
    return 0;
46
  }
47
}
48

    
49
/**
50
 * @brief   Implementation of the BaseAsynchronous read() method (inherited from BaseSequentialStream).
51
 */
52
static size_t _channelread(void *instance, uint8_t *bp, size_t n)
53
{
54
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
55
    return streamRead(((AosShellChannel*)instance)->asyncchannel, bp, n);
56
  } else {
57
    return 0;
58
  }
59
}
60

    
61
/**
62
 * @brief   Implementation of the BaseAsynchronous put() method (inherited from BaseSequentialStream).
63
 */
64
static msg_t _channelput(void *instance, uint8_t b)
65
{
66
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
67
    return streamPut(((AosShellChannel*)instance)->asyncchannel, b);
68
  } else {
69
    return MSG_RESET;
70
  }
71
}
72

    
73
/**
74
 * @brief   Implementation of the BaseAsynchronous get() method (inherited from BaseSequentialStream).
75
 */
76
static msg_t _channelget(void *instance)
77
{
78
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
79
    return streamGet(((AosShellChannel*)instance)->asyncchannel);
80
  } else {
81
    return MSG_RESET;
82
  }
83
}
84

    
85
/**
86
 * @brief   Implementation of the BaseAsynchronous putt() method.
87
 */
88
static msg_t _channelputt(void *instance, uint8_t b, sysinterval_t time)
89
{
90
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
91
    return chnPutTimeout(((AosShellChannel*)instance)->asyncchannel, b, time);
92
  } else {
93
    return MSG_RESET;
94
  }
95
}
96

    
97
/**
98
 * @brief   Implementation of the BaseAsynchronous gett() method.
99
 */
100
static msg_t _channelgett(void *instance, sysinterval_t time)
101
{
102
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
103
    return chnGetTimeout(((AosShellChannel*)instance)->asyncchannel, time);
104
  } else {
105
    return MSG_RESET;
106
  }
107
}
108

    
109
/**
110
 * @brief   Implementation of the BaseAsynchronous writet() method.
111
 */
112
static size_t _channelwritet(void *instance, const uint8_t *bp, size_t n, sysinterval_t time)
113
{
114
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_OUTPUT_ENABLED) {
115
    return chnWriteTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
116
  } else {
117
    return 0;
118
  }
119
}
120

    
121
/**
122
 * @brief   Implementation of the BaseAsynchronous readt() method.
123
 */
124
static size_t _channelreadt(void *instance, uint8_t *bp, size_t n, sysinterval_t time)
125
{
126
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
127
    return chnReadTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
128
  } else {
129
    return 0;
130
  }
131
}
132

    
133
/**
134
 * @brief   Implementation of the BaseAsynchronousChannel ctl() method.
135
 */
136
static msg_t _channelctl(void *instance, unsigned int operation, void *arg) {
137
  (void) instance;
138

    
139
  switch (operation) {
140
  case CHN_CTL_NOP:
141
    osalDbgCheck(arg == NULL);
142
    break;
143
  case CHN_CTL_INVALID:
144
    osalDbgAssert(false, "invalid CTL operation");
145
    break;
146
  default:
147
    break;
148
  }
149
  return MSG_OK;
150
}
151

    
152
static const struct AosShellChannelVMT _channelvmt = {
153
  (size_t) 0,
154
  _channelwrite,
155
  _channelread,
156
  _channelput,
157
  _channelget,
158
  _channelputt,
159
  _channelgett,
160
  _channelwritet,
161
  _channelreadt,
162
  _channelctl,
163
};
164

    
165
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n)
166
{
167
  aosDbgCheck(instance != NULL);
168

    
169
  // local variables
170
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
171
  size_t bytes;
172
  size_t maxbytes = 0;
173

    
174
  // iterate through the list of channels
175
  while (channel != NULL) {
176
    bytes = streamWrite(channel, bp, n);
177
    maxbytes = (bytes > maxbytes) ? bytes : maxbytes;
178
    channel = channel->next;
179
  }
180

    
181
  return maxbytes;
182
}
183

    
184
static size_t _stremread(void *instance, uint8_t *bp, size_t n)
185
{
186
  (void)instance;
187
  (void)bp;
188
  (void)n;
189

    
190
  return 0;
191
}
192

    
193
static msg_t _streamput(void *instance, uint8_t b)
194
{
195
  aosDbgCheck(instance != NULL);
196

    
197
  // local variables
198
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
199
  msg_t ret = MSG_OK;
200

    
201
  // iterate through the list of channels
202
  while (channel != NULL) {
203
    msg_t ret_ = streamPut(channel, b);
204
    ret = (ret_ < ret) ? ret_ : ret;
205
    channel = channel->next;
206
  }
207

    
208
  return ret;
209
}
210

    
211
static msg_t _streamget(void *instance)
212
{
213
  (void)instance;
214

    
215
  return 0;
216
}
217

    
218
static const struct AosShellStreamVMT _streamvmt = {
219
  (size_t) 0,
220
  _streamwrite,
221
  _stremread,
222
  _streamput,
223
  _streamget,
224
};
225

    
226
/**
227
 * @brief   Enumerator of special keyboard keys.
228
 */
229
typedef enum special_key {
230
  KEY_UNKNOWN,      /**< any/unknow key */
231
  KEY_AMBIGUOUS,    /**< key is ambiguous */
232
  KEY_TAB,          /**< tabulator key */
233
  KEY_ESCAPE,       /**< escape key */
234
  KEY_BACKSPACE,    /**< backspace key */
235
  KEY_INSERT,       /**< insert key */
236
  KEY_DELETE,       /**< delete key */
237
  KEY_HOME,         /**< home key */
238
  KEY_END,          /**< end key */
239
  KEY_PAGE_UP,      /**< page up key */
240
  KEY_PAGE_DOWN,    /**< page down key */
241
  KEY_ARROW_UP,     /**< arrow up key */
242
  KEY_ARROW_DOWN,   /**< arrow down key */
243
  KEY_ARROW_LEFT,   /**< arrow left key */
244
  KEY_ARROW_RIGHT,  /**< arrow right key */
245
} special_key_t;
246

    
247
/**
248
 * @brief   Enumerator for case (in)sensitive character matching.
249
 */
250
typedef enum charmatch {
251
  CHAR_MATCH_NOT    = 0,  /**< Characters do not match at all. */
252
  CHAR_MATCH_NCASE  = 1,  /**< Characters would match case insensitive. */
253
  CHAR_MATCH_CASE   = 2,  /**< Characters do match with case. */
254
} charmatch_t;
255

    
256
/**
257
 * @brief   Print the shell prompt
258
 * @details Depending on the configuration flags, the system uptime is printed before the prompt string.
259
 *
260
 * @param[in] shell   Pointer to the shell object.
261
 */
262
static void _printPrompt(aos_shell_t* shell)
263
{
264
  aosDbgCheck(shell != NULL);
265

    
266
  // print some time informattion before prompt if configured
267
  if (shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) {
268
    // printf the system uptime
269
    if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_UPTIME) {
270
      // get current system uptime
271
      aos_timestamp_t uptime;
272
      aosSysGetUptime(&uptime);
273

    
274
      chprintf((BaseSequentialStream*)&shell->stream, "[%01u:%02u:%02u:%02u:%03u:%03u] ",
275
               (uint32_t)(uptime / MICROSECONDS_PER_DAY),
276
               (uint8_t)(uptime % MICROSECONDS_PER_DAY / MICROSECONDS_PER_HOUR),
277
               (uint8_t)(uptime % MICROSECONDS_PER_HOUR / MICROSECONDS_PER_MINUTE),
278
               (uint8_t)(uptime % MICROSECONDS_PER_MINUTE / MICROSECONDS_PER_SECOND),
279
               (uint16_t)(uptime % MICROSECONDS_PER_SECOND / MICROSECONDS_PER_MILLISECOND),
280
               (uint16_t)(uptime % MICROSECONDS_PER_MILLISECOND / MICROSECONDS_PER_MICROSECOND));
281
    }
282
    else if ((shell->config & (AOS_SHELL_CONFIG_PROMPT_UPTIME | AOS_SHELL_CONFIG_PROMPT_DATETIME)) == AOS_SHELL_CONFIG_PROMPT_DATETIME) {
283
      // get current RTC time
284
      struct tm dt;
285
      aosSysGetDateTime(&dt);
286
      chprintf((BaseSequentialStream*)&shell->stream, "[%02u-%02u-%04u|%02u:%02u:%02u] ",
287
               dt.tm_mday,
288
               dt.tm_mon + 1,
289
               dt.tm_year + 1900,
290
               dt.tm_hour,
291
               dt.tm_min,
292
               dt.tm_sec);
293
    }
294
    else {
295
      aosDbgAssert(false);
296
    }
297
  }
298

    
299
  // print the actual prompt string
300
  if (shell->prompt && !(shell->config & AOS_SHELL_CONFIG_PROMPT_MINIMAL)) {
301
    chprintf((BaseSequentialStream*)&shell->stream, "%s$ ", shell->prompt);
302
  } else {
303
    chprintf((BaseSequentialStream*)&shell->stream, "%>$ ");
304
  }
305

    
306
  return;
307
}
308

    
309
/**
310
 * @brief   Interprete a escape sequence
311
 *
312
 * @param[in] seq   Character sequence to interprete.
313
 *                  Must be terminated by NUL byte.
314
 *
315
 * @return          A @p special_key value.
316
 */
317
static special_key_t _interpreteEscapeSequence(const char seq[])
318
{
319
  // local variables
320
  bool ambiguous = false;
321
  int cmp = 0;
322

    
323
  // TAB
324
  /* not supported yet; use "\x09" instead */
325

    
326
  // BACKSPACE
327
  /* not supported yet; use "\x08" instead */
328

    
329
  // ESCAPE
330
  cmp = strcmp(seq, "\x1B");
331
  if (cmp == 0) {
332
    return KEY_ESCAPE;
333
  } else {
334
    ambiguous |= (cmp < 0);
335
  }
336

    
337
  // INSERT
338
  cmp = strcmp(seq, "\x1B\x5B\x32\x7E");
339
  if (cmp == 0) {
340
    return KEY_INSERT;
341
  } else {
342
    ambiguous |= (cmp < 0);
343
  }
344

    
345
  // DELETE
346
  cmp = strcmp(seq, "\x1B\x5B\x33\x7E");
347
  if (cmp == 0) {
348
    return KEY_DELETE;
349
  } else {
350
    ambiguous |= (cmp < 0);
351
  }
352

    
353
  // HOME
354
  cmp = strcmp(seq, "\x1B\x4F\x48");
355
  if (cmp == 0) {
356
    return KEY_HOME;
357
  } else {
358
    ambiguous |= (cmp < 0);
359
  }
360

    
361
  // END
362
  cmp = strcmp(seq, "\x1B\x4F\x46");
363
  if (cmp == 0) {
364
    return KEY_END;
365
  } else {
366
    ambiguous |= (cmp < 0);
367
  }
368

    
369
  // PAGE UP
370
  cmp = strcmp(seq, "\x1B\x5B\x35\x7E");
371
  if (cmp == 0) {
372
    return KEY_PAGE_UP;
373
  } else {
374
    ambiguous |= (cmp < 0);
375
  }
376

    
377
  // PAGE DOWN
378
  cmp = strcmp(seq, "\x1B\x5B\x36\x7E");
379
  if (cmp == 0) {
380
    return KEY_PAGE_DOWN;
381
  } else {
382
    ambiguous |= (cmp < 0);
383
  }
384

    
385
  // ARROW UP
386
  cmp = strcmp(seq, "\x1B\x5B\x41");
387
  if (cmp == 0) {
388
    return KEY_ARROW_UP;
389
  } else {
390
    ambiguous |= (cmp < 0);
391
  }
392

    
393
  // ARROW DOWN
394
  cmp = strcmp(seq, "\x1B\x5B\x42");
395
  if (cmp == 0) {
396
    return KEY_ARROW_DOWN;
397
  } else {
398
    ambiguous |= (cmp < 0);
399
  }
400

    
401
  // ARROW LEFT
402
  cmp = strcmp(seq, "\x1B\x5B\x44");
403
  if (cmp == 0) {
404
    return KEY_ARROW_LEFT;
405
  } else {
406
    ambiguous |= (cmp < 0);
407
  }
408

    
409
  // ARROW RIGHT
410
  cmp = strcmp(seq, "\x1B\x5B\x43");
411
  if (cmp == 0) {
412
    return KEY_ARROW_RIGHT;
413
  } else {
414
    ambiguous |= (cmp < 0);
415
  }
416

    
417
  return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
418
}
419

    
420
/**
421
 * @brief   Move the cursor in the terminal
422
 *
423
 * @param[in] shell   Pointer to the shell object.
424
 * @param[in] from    Starting position of the cursor.
425
 * @param[in] to      Target position to move the cursor to.
426
 *
427
 * @return            The number of positions moved.
428
 */
429
static int _moveCursor(aos_shell_t* shell, const size_t from, const size_t to)
430
{
431
  aosDbgCheck(shell != NULL);
432

    
433
  // local variables
434
  size_t pos = from;
435

    
436
  // move cursor left by printing backspaces
437
  while (pos > to) {
438
    streamPut(&shell->stream, '\b');
439
    --pos;
440
  }
441

    
442
  // move cursor right by printing line content
443
  while (pos < to) {
444
    streamPut(&shell->stream, shell->line[pos]);
445
    ++pos;
446
  }
447

    
448
  return (int)pos - (int)from;
449
}
450

    
451
/**
452
 * @brief   Print content of the shell line
453
 *
454
 * @param[in] shell   Pointer to the shell object.
455
 * @param[in] from    First position to start printing from.
456
 * @param[in] to      Position after the last character to print.
457
 *
458
 * @return            Number of characters printed.
459
 */
460
static inline size_t _printLine(aos_shell_t* shell, const size_t from, const size_t to)
461
{
462
  aosDbgCheck(shell != NULL);
463

    
464
  // local variables
465
  size_t cnt;
466

    
467
  for (cnt = 0; from + cnt < to; ++cnt) {
468
    streamPut(&shell->stream, shell->line[from + cnt]);
469
  }
470

    
471
  return cnt;
472
}
473

    
474
/**
475
 * @brief   Compare two characters.
476
 *
477
 * @param[in] lhs       First character to compare.
478
 * @param[in] rhs       Second character to compare.
479
 *
480
 * @return              How well the characters match.
481
 */
482
static inline charmatch_t _charcmp(char lhs, char rhs)
483
{
484
  // if lhs is a upper case letter and rhs is a lower case letter
485
  if (lhs >= 'A' && lhs <= 'Z' && rhs >= 'a' && rhs <= 'z') {
486
    return (lhs == (rhs - 'a' + 'A')) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
487
  }
488
  // if lhs is a lower case letter and rhs is a upper case letter
489
  else if (lhs >= 'a' && lhs <= 'z' && rhs >= 'A' && rhs <= 'Z') {
490
    return ((lhs - 'a' + 'A') == rhs) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
491
  }
492
  // default
493
  else {
494
    return (lhs == rhs) ? CHAR_MATCH_CASE : CHAR_MATCH_NOT;
495
  }
496
}
497

    
498
/**
499
 * @brief   Maps an character from ASCII to a modified custom encoding.
500
 * @details The custom character encoding is very similar to ASCII and has the following structure:
501
 *          0x00=NULL ... 0x40='@' (identically to ASCII)
502
 *          0x4A='a'; 0x4B='A'; 0x4C='b'; 0x4D='B' ... 0x73='z'; 0x74='Z' (custom letter order)
503
 *          0x75='[' ... 0x7A='`' (0x5B..0x60 is ASCII)
504
 *          0x7B='{' ... 0x7F=DEL (identically to ASCII)
505
 *
506
 * @param[in] c   Character to map to the custom encoding.
507
 *
508
 * @return    The customly encoded character.
509
 */
510
static inline char _mapAscii2Custom(const char c)
511
{
512
  if (c >= 'A' && c <= 'Z') {
513
    return ((c - 'A') * 2) + 'A' + 1;
514
  } else if (c > 'Z' && c < 'a') {
515
    return c + ('z' - 'a') + 1;
516
  } else if (c >= 'a' && c <= 'z') {
517
    return ((c - 'a') * 2) + 'A';
518
  } else {
519
    return c;
520
  }
521
}
522

    
523
/**
524
 * @brief   Compares two strings wrt letter case.
525
 * @details Comparisson uses a custom character encoding or mapping.
526
 *          See @p _mapAscii2Custom for details.
527
 *
528
 * @param[in] str1    First string to compare.
529
 * @param[in] str2    Second string to compare.
530
 * @param[in] cs      Flag indicating whether comparison shall be case sensitive.
531
 * @param[in,out] n   Maximum number of character to compare (in) and number of matching characters (out).
532
 *                    If a null pointer is specified, this parameter is ignored.
533
 *                    If the value pointed to is zero, comarison will not be limited.
534
 * @param[out] m      Optional indicator whether there was at least one case mismatch.
535
 *
536
 * @return      Integer value indicating the relationship between the strings.
537
 * @retval <0   The first character that does not match has a lower value in str1 than in str2.
538
 * @retval  0   The contents of both strings are equal.
539
 * @retval >0   The first character that does not match has a greater value in str1 than in str2.
540
 */
541
static int _strccmp(const char *str1, const char *str2, bool cs, size_t* n, charmatch_t* m)
542
{
543
  aosDbgCheck(str1 != NULL);
544
  aosDbgCheck(str2 != NULL);
545

    
546
  // initialize variables
547
  if (m) {
548
    *m = CHAR_MATCH_NOT;
549
  }
550
  size_t i = 0;
551

    
552
  // iterate through the strings
553
  while ((n == NULL) || (*n == 0) || (*n > 0 && i < *n)) {
554
    // break on NUL
555
    if (str1[i] == '\0' || str2[i] == '\0') {
556
      if (n) {
557
        *n = i;
558
      }
559
      break;
560
    }
561
    // compare character
562
    const charmatch_t match = _charcmp(str1[i], str2[i]);
563
    if ((match == CHAR_MATCH_CASE) || (!cs && match == CHAR_MATCH_NCASE)) {
564
      if (m != NULL && *m != CHAR_MATCH_NCASE) {
565
        *m = match;
566
      }
567
      ++i;
568
    } else {
569
      if (n) {
570
        *n = i;
571
      }
572
      break;
573
    }
574
  }
575

    
576
  return _mapAscii2Custom(str1[i]) - _mapAscii2Custom(str2[i]);
577
}
578

    
579
static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, size_t* n)
580
{
581
  aosDbgCheck(shell != NULL);
582
  aosDbgCheck(channel != NULL);
583
  aosDbgCheck(n != NULL);
584

    
585
  // local variables
586
  aos_shellaction_t action = AOS_SHELL_ACTION_NONE;
587
  char c;
588
  special_key_t key;
589

    
590
  // initialize output variables
591
  *n = 0;
592

    
593
  // read character by character from the channel
594
  while (chnReadTimeout(channel, (uint8_t*)&c, 1, TIME_IMMEDIATE)) {
595
    key = KEY_UNKNOWN;
596

    
597
    // parse escape sequence
598
    if (shell->inputdata.escp > 0) {
599
      shell->inputdata.escseq[shell->inputdata.escp] = c;
600
      ++shell->inputdata.escp;
601
      key = _interpreteEscapeSequence(shell->inputdata.escseq);
602
      if (key == KEY_AMBIGUOUS) {
603
        // read next byte to resolve ambiguity
604
        continue;
605
      } else {
606
        /*
607
         * If the escape sequence could either be parsed sucessfully
608
         * or there is no match (KEY_UNKNOWN),
609
         * reset the sequence variable and interprete key/character
610
         */
611
        shell->inputdata.escp = 0;
612
        memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
613
      }
614
    }
615

    
616
    /* interprete keys or character */
617
    {
618
      // default
619
      action = AOS_SHELL_ACTION_NONE;
620

    
621
      // printable character
622
      if (key == KEY_UNKNOWN && c >= '\x20' && c <= '\x7E') {
623
        action = AOS_SHELL_ACTION_READCHAR;
624
      }
625

    
626
      // tab key or character
627
      else if (key == KEY_TAB || c == '\x09') {
628
        /*
629
         * pressing tab once applies auto fill
630
         * pressing tab a second time prints suggestions
631
         */
632
        if (shell->inputdata.lastaction == AOS_SHELL_ACTION_AUTOFILL || shell->inputdata.lastaction == AOS_SHELL_ACTION_SUGGEST) {
633
          action = AOS_SHELL_ACTION_SUGGEST;
634
        } else {
635
          action = AOS_SHELL_ACTION_AUTOFILL;
636
        }
637
      }
638

    
639
      // INS key
640
      else if (key == KEY_INSERT) {
641
        action = AOS_SHELL_ACTION_INSERTTOGGLE;
642
      }
643

    
644
      // DEL key or character
645
      else if (key == KEY_DELETE || c == '\x7F') {
646
        // ignore if cursor is at very right
647
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
648
          action = AOS_SHELL_ACTION_DELETEFORWARD;
649
        }
650
      }
651

    
652
      // backspace key or character
653
      else if (key == KEY_BACKSPACE || c == '\x08') {
654
        // ignore if cursor is at very left
655
        if (shell->inputdata.cursorpos > 0) {
656
          action = AOS_SHELL_ACTION_DELETEBACKWARD;
657
        }
658
      }
659

    
660
      // 'page up' of 'arrow up' key
661
      else if (key == KEY_PAGE_UP || key == KEY_ARROW_UP) {
662
        // ignore if there was some input
663
        if (shell->inputdata.noinput) {
664
          action = AOS_SHELL_ACTION_RECALLLAST;
665
        }
666
      }
667

    
668
      // 'page down' key, 'arrow done' key, 'end of test' character or 'end of transmission' character
669
      else if (key == KEY_PAGE_DOWN || key == KEY_ARROW_DOWN || c == '\x03' || c == '\x03') {
670
        // ignore if line is empty
671
        if (shell->inputdata.lineend > 0) {
672
          action = AOS_SHELL_ACTION_CLEAR;
673
        }
674
      }
675

    
676
      // 'home' key
677
      else if (key == KEY_HOME) {
678
        // ignore if cursor is very left
679
        if (shell->inputdata.cursorpos > 0) {
680
          action = AOS_SHELL_ACTION_CURSOR2START;
681
        }
682
      }
683

    
684
      // 'end' key
685
      else if (key == KEY_END) {
686
        // ignore if cursos is very right
687
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
688
          action = AOS_SHELL_ACTION_CURSOR2END;
689
        }
690
      }
691

    
692
      // 'arrow left' key
693
      else if (key == KEY_ARROW_LEFT) {
694
        // ignore if cursor is very left
695
        if (shell->inputdata.cursorpos > 0) {
696
          action = AOS_SHELL_ACTION_CURSORLEFT;
697
        }
698
      }
699

    
700
      // 'arrow right' key
701
      else if (key == KEY_ARROW_RIGHT) {
702
        // irgnore if cursor is very right
703
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
704
          action = AOS_SHELL_ACTION_CURSORRIGHT;
705
        }
706
      }
707

    
708
      // carriage return ('\r') or line feed ('\n') character
709
      else if (c == '\x0D' || c == '\x0A') {
710
        action = AOS_SHELL_ACTION_EXECUTE;
711
      }
712

    
713
      // ESC key or [ESCAPE] character
714
      else if (key == KEY_ESCAPE || c == '\x1B') {
715
        action = AOS_SHELL_ACTION_ESCSTART;
716
      }
717
    }
718

    
719
    /* handle function */
720
    switch (action) {
721
      case AOS_SHELL_ACTION_READCHAR:
722
      {
723
        // line is full
724
        if (shell->inputdata.lineend + 1 >= shell->linesize) {
725
          _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
726
          chprintf((BaseSequentialStream*)&shell->stream, "\n\tmaximum line width reached\n");
727
          _printPrompt(shell);
728
          _printLine(shell, 0, shell->inputdata.lineend);
729
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
730
        }
731
        // read character
732
        else {
733
          // clear old line content on first input
734
          if (shell->inputdata.noinput) {
735
            memset(shell->line, '\0', shell->linesize);
736
            shell->inputdata.noinput = false;
737
          }
738
          // overwrite content
739
          if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
740
            shell->line[shell->inputdata.cursorpos] = c;
741
            ++shell->inputdata.cursorpos;
742
            shell->inputdata.lineend = (shell->inputdata.cursorpos > shell->inputdata.lineend) ? shell->inputdata.cursorpos : shell->inputdata.lineend;
743
            streamPut(&shell->stream, (uint8_t)c);
744
          }
745
          // insert character
746
          else {
747
            memmove(&(shell->line[shell->inputdata.cursorpos+1]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
748
            shell->line[shell->inputdata.cursorpos] = c;
749
            ++shell->inputdata.lineend;
750
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
751
            ++shell->inputdata.cursorpos;
752
            _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
753
          }
754
        }
755
        break;
756
      }
757

    
758
      case AOS_SHELL_ACTION_AUTOFILL:
759
      {
760
        const char* fill = shell->line;
761
        size_t cmatch = shell->inputdata.cursorpos;
762
        charmatch_t matchlevel = CHAR_MATCH_NOT;
763
        size_t n;
764
        // iterate through command list
765
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
766
          // compare current match with command
767
          n = cmatch;
768
          charmatch_t mlvl = CHAR_MATCH_NOT;
769
          _strccmp(fill, cmd->name, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, (n == 0) ? NULL : &n, &mlvl);
770
          const int cmp = (n < cmatch) ?
771
                            (n - cmatch) :
772
                            (cmd->name[n] != '\0') ?
773
                              strlen(cmd->name) - n :
774
                              0;
775
          // if an exact match was found
776
          if (cmatch + cmp == shell->inputdata.cursorpos) {
777
            cmatch = shell->inputdata.cursorpos;
778
            fill = cmd->name;
779
            // break the loop only if there are no case mismatches with the input
780
            n = shell->inputdata.cursorpos;
781
            _strccmp(fill, shell->line, false, &n, &mlvl);
782
            if (mlvl == CHAR_MATCH_CASE) {
783
              break;
784
            }
785
          }
786
          // if a not exact match was found
787
          else if (cmatch + cmp > shell->inputdata.cursorpos) {
788
            // if this is the first one
789
            if (fill == shell->line) {
790
              cmatch += cmp;
791
              fill = cmd->name;
792
            }
793
            // if this is a worse one
794
            else if ((cmp < 0) || (cmp == 0 && mlvl == CHAR_MATCH_CASE)) {
795
              cmatch += cmp;
796
            }
797
          }
798
          // non matching commands are ignored
799
          else {}
800
        }
801
        // evaluate if there are case mismatches
802
        n = cmatch;
803
        _strccmp(shell->line, fill, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, &n, &matchlevel);
804
        // print the auto fill if any
805
        if (cmatch > shell->inputdata.cursorpos || (cmatch == shell->inputdata.cursorpos && matchlevel == CHAR_MATCH_NCASE)) {
806
          shell->inputdata.noinput = false;
807
          // limit auto fill so it will not overflow the line width
808
          if (shell->inputdata.lineend + (cmatch - shell->inputdata.cursorpos) > shell->linesize) {
809
            cmatch = shell->linesize - shell->inputdata.lineend + shell->inputdata.cursorpos;
810
          }
811
          // move trailing memory further in the line
812
          memmove(&(shell->line[cmatch]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
813
          shell->inputdata.lineend += cmatch - shell->inputdata.cursorpos;
814
          // if there was no incorrect case when matching
815
          if (matchlevel == CHAR_MATCH_CASE) {
816
            // insert fill command name to line
817
            memcpy(&(shell->line[shell->inputdata.cursorpos]), &(fill[shell->inputdata.cursorpos]), cmatch - shell->inputdata.cursorpos);
818
            // print the output
819
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
820
          } else {
821
            // overwrite line with fill command name
822
            memcpy(shell->line, fill, cmatch);
823
            // reprint the whole line
824
            _moveCursor(shell, shell->inputdata.cursorpos, 0);
825
            _printLine(shell, 0, shell->inputdata.lineend);
826
          }
827
          // move cursor to the end of the matching sequence
828
          shell->inputdata.cursorpos = cmatch;
829
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
830
        }
831
        break;
832
      }
833

    
834
      case AOS_SHELL_ACTION_SUGGEST:
835
      {
836
        unsigned int matches = 0;
837
        // iterate through command list
838
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
839
          // compare line content with command, excpet if cursorpos=0
840
          size_t i = shell->inputdata.cursorpos;
841
          if (shell->inputdata.cursorpos > 0) {
842
            _strccmp(shell->line, cmd->name, true, &i, NULL);
843
          }
844
          const int cmp = (i < shell->inputdata.cursorpos) ?
845
                            (i - shell->inputdata.cursorpos) :
846
                            (cmd->name[i] != '\0') ?
847
                              strlen(cmd->name) - i :
848
                              0;
849
          // if a match was found
850
          if (cmp > 0) {
851
            // if this is the first one
852
            if (matches == 0) {
853
              _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
854
              streamPut(&shell->stream, '\n');
855
            }
856
            // print the command
857
            chprintf((BaseSequentialStream*)&shell->stream, "\t%s\n", cmd->name);
858
            ++matches;
859
          }
860
        }
861
        // reprint the prompt and line if any matches have been found
862
        if (matches > 0) {
863
          _printPrompt(shell);
864
          _printLine(shell, 0, shell->inputdata.lineend);
865
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
866
          shell->inputdata.noinput = false;
867
        }
868
        break;
869
      }
870

    
871
      case AOS_SHELL_ACTION_INSERTTOGGLE:
872
      {
873
        if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
874
          shell->config &= ~AOS_SHELL_CONFIG_INPUT_OVERWRITE;
875
        } else {
876
          shell->config |= AOS_SHELL_CONFIG_INPUT_OVERWRITE;
877
        }
878
        break;
879
      }
880

    
881
      case AOS_SHELL_ACTION_DELETEFORWARD:
882
      {
883
        --shell->inputdata.lineend;
884
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
885
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
886
        streamPut(&shell->stream, ' ');
887
        _moveCursor(shell, shell->inputdata.lineend + 1, shell->inputdata.cursorpos);
888
        break;
889
      }
890

    
891
      case AOS_SHELL_ACTION_DELETEBACKWARD:
892
      {
893
        --shell->inputdata.cursorpos;
894
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
895
        --shell->inputdata.lineend;
896
        shell->line[shell->inputdata.lineend] = '\0';
897
        _moveCursor(shell, shell->inputdata.cursorpos + 1, shell->inputdata.cursorpos);
898
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
899
        streamPut(&shell->stream, ' ');
900
        _moveCursor(shell, shell->inputdata.lineend+1, shell->inputdata.cursorpos);
901
        break;
902
      }
903

    
904
      case AOS_SHELL_ACTION_RECALLLAST:
905
      {
906
        // replace any intermediate NUL bytes with spaces
907
        shell->inputdata.lineend = 0;
908
        size_t nul_start = 0;
909
        size_t nul_end = 0;
910
        // search line for a NUL byte
911
        while (nul_start < shell->linesize) {
912
          if (shell->line[nul_start] == '\0') {
913
            nul_end = nul_start + 1;
914
            // keep searcjing for a byte that is not NUL
915
            while (nul_end < shell->linesize) {
916
              if (shell->line[nul_end] != '\0') {
917
                // an intermediate NUL sequence was found
918
                memset(&(shell->line[nul_start]), ' ', nul_end - nul_start);
919
                shell->inputdata.lineend = nul_end + 1;
920
                break;
921
              } else {
922
                ++nul_end;
923
              }
924
            }
925
            nul_start = nul_end + 1;
926
          } else {
927
            ++shell->inputdata.lineend;
928
            ++nul_start;
929
          }
930
        }
931
        shell->inputdata.cursorpos = shell->inputdata.lineend;
932
        // print the line
933
        shell->inputdata.noinput = _printLine(shell, 0, shell->inputdata.lineend) == 0;
934
        break;
935
      }
936

    
937
      case AOS_SHELL_ACTION_CLEAR:
938
      {
939
        // clear output
940
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
941
        for (shell->inputdata.cursorpos = 0; shell->inputdata.cursorpos < shell->inputdata.lineend; ++shell->inputdata.cursorpos) {
942
          streamPut(&shell->stream, ' ');
943
        }
944
        _moveCursor(shell, shell->inputdata.lineend, 0);
945
        shell->inputdata.cursorpos = 0;
946
        shell->inputdata.lineend = 0;
947
        shell->inputdata.noinput = true;
948
        break;
949
      }
950

    
951
      case AOS_SHELL_ACTION_CURSOR2START:
952
      {
953
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
954
        shell->inputdata.cursorpos = 0;
955
        break;
956
      }
957

    
958
      case AOS_SHELL_ACTION_CURSOR2END:
959
      {
960
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
961
        shell->inputdata.cursorpos = shell->inputdata.lineend;
962
        break;
963
      }
964

    
965
      case AOS_SHELL_ACTION_CURSORLEFT:
966
      {
967
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos-1);
968
        --shell->inputdata.cursorpos;
969
        break;
970
      }
971

    
972
      case AOS_SHELL_ACTION_CURSORRIGHT:
973
      {
974
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos+1);
975
        ++shell->inputdata.cursorpos;
976
        break;
977
      }
978

    
979
      case AOS_SHELL_ACTION_EXECUTE:
980
      {
981
        streamPut(&shell->stream, '\n');
982
        // set the number of read bytes and return
983
        if (!shell->inputdata.noinput) {
984
          *n = shell->linesize - shell->inputdata.lineend;
985
          // fill the remainder of the line with NUL bytes
986
          memset(&(shell->line[shell->inputdata.lineend]), '\0', *n);
987
          // reset static variables
988
          shell->inputdata.noinput = true;
989
        }
990
        return AOS_SUCCESS;
991
        break;
992
      }
993

    
994
      case AOS_SHELL_ACTION_ESCSTART:
995
      {
996
        shell->inputdata.escseq[0] = c;
997
        ++shell->inputdata.escp;
998
        break;
999
      }
1000

    
1001
      case AOS_SHELL_ACTION_NONE:
1002
      default:
1003
      {
1004
        // do nothing (ignore input) and read next byte
1005
        continue;
1006
        break;
1007
      }
1008
    } /* end of switch */
1009

    
1010
    shell->inputdata.lastaction = action;
1011
  } /* end of while */
1012

    
1013
  // no more data could be read from the channel
1014
  return AOS_WARNING;
1015
}
1016

    
1017
/**
1018
 * @brief   Parses the content of the input buffer (line) to separate arguments.
1019
 *
1020
 * @param[in] shell   Pointer to the shell object.
1021
 *
1022
 * @return            Number of arguments found.
1023
 */
1024
static size_t _parseArguments(aos_shell_t* shell)
1025
{
1026
  aosDbgCheck(shell != NULL);
1027

    
1028
  /*
1029
   * States for a very small FSM.
1030
   */
1031
  typedef enum {
1032
    START,
1033
    SPACE,
1034
    TEXT,
1035
    END,
1036
  } state_t;
1037

    
1038
  // local variables
1039
  state_t state = START;
1040
  size_t arg = 0;
1041

    
1042
  // iterate through the line
1043
  for (char* c = shell->line; c < shell->line + shell->linesize; ++c) {
1044
    // terminate at first NUL byte
1045
    if (*c == '\0') {
1046
      state = END;
1047
      break;
1048
    }
1049
    // spaces become NUL bytes
1050
    else if (*c == ' ') {
1051
      *c = '\0';
1052
      state = SPACE;
1053
    }
1054
    // handle non-NUL bytes
1055
    else {
1056
      switch (state) {
1057
        case START:
1058
        case SPACE:
1059
          // ignore too many arguments
1060
          if (arg < shell->arglistsize) {
1061
            shell->arglist[arg] = c;
1062
          }
1063
          ++arg;
1064
          break;
1065
        case TEXT:
1066
        case END:
1067
        default:
1068
          break;
1069
      }
1070
      state = TEXT;
1071
    }
1072
  }
1073

    
1074
  // set all remaining argument pointers to NULL
1075
  for (size_t a = arg; a < shell->arglistsize; ++a) {
1076
    shell->arglist[a] = NULL;
1077
  }
1078

    
1079
  return arg;
1080
}
1081

    
1082
/**
1083
 * @brief   Initializes a shell object with the specified parameters.
1084
 *
1085
 * @param[in] shell         Pointer to the shell object.
1086
 * @param[in] stream        I/O stream to use.
1087
 * @param[in] prompt        Prompt line to print (NULL = use default prompt).
1088
 * @param[in] line          Pointer to the input buffer.
1089
 * @param[in] linesize      Size of the input buffer.
1090
 * @param[in] arglist       Pointer to the argument buffer.
1091
 * @param[in] arglistsize   Size of te argument buffer.
1092
 */
1093
void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char* prompt, char* line, size_t linesize, char** arglist, size_t arglistsize)
1094
{
1095
  aosDbgCheck(shell != NULL);
1096
  aosDbgCheck(oseventsource != NULL);
1097
  aosDbgCheck(line != NULL);
1098
  aosDbgCheck(arglist != NULL);
1099

    
1100
  // set parameters
1101
  shell->thread = NULL;
1102
  chEvtObjectInit(&shell->eventSource);
1103
  shell->os.eventSource = oseventsource;
1104
  aosShellStreamInit(&shell->stream);
1105
  shell->prompt = prompt;
1106
  shell->commands = NULL;
1107
  shell->execstatus.command = NULL;
1108
  shell->execstatus.retval = 0;
1109
  shell->line = line;
1110
  shell->linesize = linesize;
1111
  shell->inputdata.lastaction = AOS_SHELL_ACTION_NONE;
1112
  shell->inputdata.escp = 0;
1113
  memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
1114
  shell->inputdata.cursorpos = 0;
1115
  shell->inputdata.lineend = 0;
1116
  shell->inputdata.noinput = true;
1117
  shell->arglist = arglist;
1118
  shell->arglistsize = arglistsize;
1119
  shell->config = 0x00;
1120

    
1121
  // initialize arrays
1122
  memset(shell->line, '\0', shell->linesize);
1123
  for (size_t a = 0; a < shell->arglistsize; ++a) {
1124
    shell->arglist[a] = NULL;
1125
  }
1126

    
1127
  return;
1128
}
1129

    
1130
/**
1131
 * @brief   Initialize an AosShellStream object.
1132
 *
1133
 * @param[in] stream  The AosShellStrem to initialize.
1134
 */
1135
void aosShellStreamInit(AosShellStream* stream)
1136
{
1137
  aosDbgCheck(stream != NULL);
1138

    
1139
  stream->vmt = &_streamvmt;
1140
  stream->channel = NULL;
1141

    
1142
  return;
1143
}
1144

    
1145
/**
1146
 * @brief   Initialize an AosShellChannel object with the specified parameters.
1147
 *
1148
 * @param[in] channel       The AosShellChannel to initialize.
1149
 * @param[in] asyncchannel  An BaseAsynchronousChannel this AosShellChannel is associated with.
1150
 */
1151
void aosShellChannelInit(AosShellChannel* channel, BaseAsynchronousChannel* asyncchannel)
1152
{
1153
  aosDbgCheck(channel != NULL);
1154
  aosDbgCheck(asyncchannel != NULL);
1155

    
1156
  channel->vmt = &_channelvmt;
1157
  channel->asyncchannel = asyncchannel;
1158
  channel->listener.wflags = 0;
1159
  channel->next = NULL;
1160
  channel->flags = 0;
1161

    
1162
  return;
1163
}
1164

    
1165
/**
1166
 * @brief   Inserts a command to the shells list of commands.
1167
 *
1168
 * @param[in] shell   Pointer to the shell object.
1169
 * @param[in] cmd     Pointer to the command to add.
1170
 *
1171
 * @return            A status value.
1172
 * @retval AOS_SUCCESS  The command was added successfully.
1173
 * @retval AOS_ERROR    Another command with identical name already exists.
1174
 */
1175
aos_status_t aosShellAddCommand(aos_shell_t *shell, aos_shellcommand_t *cmd)
1176
{
1177
  aosDbgCheck(shell != NULL);
1178
  aosDbgCheck(cmd != NULL);
1179
  aosDbgCheck(cmd->name != NULL && strlen(cmd->name) > 0 && strchr(cmd->name, ' ') == NULL && strchr(cmd->name, '\t') == NULL);
1180
  aosDbgCheck(cmd->callback != NULL);
1181
  aosDbgCheck(cmd->next == NULL);
1182

    
1183
  aos_shellcommand_t* prev = NULL;
1184
  aos_shellcommand_t** curr = &(shell->commands);
1185

    
1186
  // insert the command to the list wrt lexographical order (exception: lower case characters preceed upper their uppercase counterparts)
1187
  while (*curr != NULL) {
1188
    // iterate through the list as long as the command names are 'smaller'
1189
    const int cmp = _strccmp((*curr)->name, cmd->name, true, NULL, NULL);
1190
    if (cmp < 0) {
1191
      prev = *curr;
1192
      curr = &((*curr)->next);
1193
      continue;
1194
    }
1195
    // error if the command already exists
1196
    else if (cmp == 0) {
1197
      return AOS_ERROR;
1198
    }
1199
    // insert the command as soon as a 'larger' name was found
1200
    else /* if (cmpval > 0) */ {
1201
      cmd->next = *curr;
1202
      // special case: the first command is larger
1203
      if (prev == NULL) {
1204
        shell->commands = cmd;
1205
      } else {
1206
        prev->next = cmd;
1207
      }
1208
      return AOS_SUCCESS;
1209
    }
1210
  }
1211
  // the end of the list has been reached
1212

    
1213
  // append the command
1214
  *curr = cmd;
1215
  return AOS_SUCCESS;
1216
}
1217

    
1218
/**
1219
 * @brief   Removes a command from the shells list of commands.
1220
 *
1221
 * @param[in] shell     Pointer to the shell object.
1222
 * @param[in] cmd       Name of the command to removde.
1223
 * @param[out] removed  Optional pointer to the command that was removed.
1224
 *
1225
 * @return              A status value.
1226
 * @retval AOS_SUCCESS  The command was removed successfully.
1227
 * @retval AOS_ERROR    The command name was not found.
1228
 */
1229
aos_status_t aosShellRemoveCommand(aos_shell_t *shell, char *cmd, aos_shellcommand_t **removed)
1230
{
1231
  aosDbgCheck(shell != NULL);
1232
  aosDbgCheck(cmd != NULL && strlen(cmd) > 0);
1233

    
1234
  aos_shellcommand_t* prev = NULL;
1235
  aos_shellcommand_t** curr = &(shell->commands);
1236

    
1237
  // iterate through the list and seach for the specified command name
1238
  while (curr != NULL) {
1239
    const int cmpval = strcmp((*curr)->name, cmd);
1240
    // iterate through the list as long as the command names are 'smaller'
1241
    if (cmpval < 0) {
1242
      prev = *curr;
1243
      curr = &((*curr)->next);
1244
      continue;
1245
    }
1246
    // remove the command when found
1247
    else if (cmpval == 0) {
1248
      // special case: the first command matches
1249
      if (prev == NULL) {
1250
        shell->commands = (*curr)->next;
1251
      } else {
1252
        prev->next = (*curr)->next;
1253
      }
1254
      (*curr)->next = NULL;
1255
      // set the optional output argument
1256
      if (removed != NULL) {
1257
        *removed = *curr;
1258
      }
1259
      return AOS_SUCCESS;
1260
    }
1261
    // break the loop if the command names are 'larger'
1262
    else /* if (cmpval > 0) */ {
1263
      break;
1264
    }
1265
  }
1266

    
1267
  // if the command was not found, return an error
1268
  return AOS_ERROR;
1269
}
1270

    
1271
/**
1272
 * @brief   Add a channel to a AosShellStream.
1273
 *
1274
 * @param[in] stream    The AosShellStream to extend.
1275
 * @param[in] channel   The channel to be added to the stream.
1276
 */
1277
void aosShellStreamAddChannel(AosShellStream* stream, AosShellChannel* channel)
1278
{
1279
  aosDbgCheck(stream != NULL);
1280
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->next == NULL && (channel->flags & AOS_SHELLCHANNEL_ATTACHED) == 0);
1281

    
1282
  // prepend the new channel
1283
  chSysLock();
1284
  channel->flags |= AOS_SHELLCHANNEL_ATTACHED;
1285
  channel->next = stream->channel;
1286
  stream->channel = channel;
1287
  chSysUnlock();
1288

    
1289
  return;
1290
}
1291

    
1292
/**
1293
 * @brief   Remove a channel from an AosShellStream.
1294
 *
1295
 * @param[in] stream    The AosShellStream to modify.
1296
 * @param[in] channel   The channel to remove.
1297
 * @return
1298
 */
1299
aos_status_t aosShellStreamRemoveChannel(AosShellStream* stream, AosShellChannel* channel)
1300
{
1301
  aosDbgCheck(stream != NULL);
1302
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->flags & AOS_SHELLCHANNEL_ATTACHED);
1303

    
1304
  // local varibales
1305
  AosShellChannel* prev = NULL;
1306
  AosShellChannel* curr = stream->channel;
1307

    
1308
  // iterate through the list and search for the specified channel
1309
  while (curr != NULL) {
1310
    // if the channel was found
1311
    if (curr == channel) {
1312
      chSysLock();
1313
      // special case: the first channel matches (prev is NULL)
1314
      if (prev == NULL) {
1315
        stream->channel = curr->next;
1316
      } else {
1317
        prev->next = channel->next;
1318
      }
1319
      curr->next = NULL;
1320
      curr->flags &= ~AOS_SHELLCHANNEL_ATTACHED;
1321
      chSysUnlock();
1322
      return AOS_SUCCESS;
1323
    }
1324
  }
1325

    
1326
  // if the channel was not found, return an error
1327
  return AOS_ERROR;
1328
}
1329

    
1330
/**
1331
 * @brief   Enable a AosSheööChannel as input.
1332
 *
1333
 * @param[in] channel   The channel to enable as input.
1334
 */
1335
void aosShellChannelInputEnable(AosShellChannel* channel)
1336
{
1337
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1338

    
1339
  chSysLock();
1340
  channel->listener.wflags |= CHN_INPUT_AVAILABLE;
1341
  channel->flags |= AOS_SHELLCHANNEL_INPUT_ENABLED;
1342
  chSysUnlock();
1343

    
1344
  return;
1345
}
1346

    
1347
/**
1348
 * @brief   Disable a AosSheööChannel as input.
1349
 *
1350
 * @param[in] channel   The channel to disable as input.
1351
 */
1352
void aosShellChannelInputDisable( AosShellChannel* channel)
1353
{
1354
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1355

    
1356
  chSysLock();
1357
  channel->listener.wflags &= ~CHN_INPUT_AVAILABLE;
1358
  channel->flags &= ~AOS_SHELLCHANNEL_INPUT_ENABLED;
1359
  chSysUnlock();
1360

    
1361
  return;
1362
}
1363

    
1364
/**
1365
 * @brief   Enable a AosSheööChannel as output.
1366
 *
1367
 * @param[in] channel   The channel to enable as output.
1368
 */
1369
void aosShellChannelOutputEnable(AosShellChannel* channel)
1370
{
1371
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1372

    
1373
  channel->flags |= AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1374

    
1375
  return;
1376
}
1377

    
1378
/**
1379
 * @brief   Disable a AosSheööChannel as output.
1380
 *
1381
 * @param[in] channel   The channel to disable as output.
1382
 */
1383
void aosShellChannelOutputDisable(AosShellChannel* channel)
1384
{
1385
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1386

    
1387
  channel->flags &= ~AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1388

    
1389
  return;
1390
}
1391

    
1392
/**
1393
 * @brief   Thread main function.
1394
 *
1395
 * @param[in] aosShellThread    Name of the function;
1396
 * @param[in] shell             Pointer to the shell object.
1397
 */
1398
THD_FUNCTION(aosShellThread, shell)
1399
{
1400
  aosDbgCheck(shell != NULL);
1401

    
1402
  // local variables
1403
  eventmask_t eventmask;
1404
  eventflags_t eventflags;
1405
  AosShellChannel* channel;
1406
  aos_status_t readeval;
1407
  size_t nchars = 0;
1408
  size_t nargs = 0;
1409
  aos_shellcommand_t* cmd;
1410

    
1411

    
1412
  // register OS related events
1413
  chEvtRegisterMask(((aos_shell_t*)shell)->os.eventSource, &(((aos_shell_t*)shell)->os.eventListener), AOS_SHELL_EVENTMASK_OS);
1414
  // register events to all input channels
1415
  for (channel = ((aos_shell_t*)shell)->stream.channel; channel != NULL; channel = channel->next) {
1416
    chEvtRegisterMaskWithFlags(&(channel->asyncchannel->event), &(channel->listener), AOS_SHELL_EVENTMASK_INPUT, channel->listener.wflags);
1417
  }
1418

    
1419
  // fire start event
1420
  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_START);
1421

    
1422
  // print the prompt for the first time
1423
  _printPrompt((aos_shell_t*)shell);
1424

    
1425
  // enter thread loop
1426
  while (!chThdShouldTerminateX()) {
1427
    // wait for event and handle it accordingly
1428
    eventmask = chEvtWaitOne(ALL_EVENTS);
1429

    
1430
    // handle event
1431
    switch (eventmask) {
1432

    
1433
      // OS related events
1434
      case AOS_SHELL_EVENTMASK_OS:
1435
      {
1436
        eventflags = chEvtGetAndClearFlags(&((aos_shell_t*)shell)->os.eventListener);
1437
        // handle shutdown/restart events
1438
        if (eventflags & AOS_SYSTEM_EVENTFLAGS_SHUTDOWN) {
1439
          chThdTerminate(((aos_shell_t*)shell)->thread);
1440
        } else {
1441
          // print an error message
1442
          chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown OS event received (0x%08X)\n", eventflags);
1443
        }
1444
        break;
1445
      }
1446

    
1447
      // input events
1448
      case AOS_SHELL_EVENTMASK_INPUT:
1449
      {
1450
        // check and handle all channels
1451
        channel = ((aos_shell_t*)shell)->stream.channel;
1452
        while (channel != NULL) {
1453
          eventflags = chEvtGetAndClearFlags(&channel->listener);
1454
          // if there is new input
1455
          if (eventflags & CHN_INPUT_AVAILABLE) {
1456
            // read input from channel
1457
            readeval = _readChannel((aos_shell_t*)shell, channel, &nchars);
1458
            // parse input line to argument list only if the input shall be executed
1459
            nargs = (readeval == AOS_SUCCESS && nchars > 0) ? _parseArguments((aos_shell_t*)shell) : 0;
1460
            // check number of arguments
1461
            if (nargs > ((aos_shell_t*)shell)->arglistsize) {
1462
              // error too many arguments
1463
              chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\ttoo many arguments\n");
1464
            } else if (nargs > 0) {
1465
              // search command list for arg[0] and execute callback
1466
              cmd = ((aos_shell_t*)shell)->commands;
1467
              while (cmd != NULL) {
1468
                if (strcmp(((aos_shell_t*)shell)->arglist[0], cmd->name) == 0) {
1469
                  ((aos_shell_t*)shell)->execstatus.command = cmd;
1470
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXEC);
1471
                  ((aos_shell_t*)shell)->execstatus.retval = cmd->callback((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, nargs, ((aos_shell_t*)shell)->arglist);
1472
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_DONE);
1473
                  // notify if the command was not successful
1474
                  if (((aos_shell_t*)shell)->execstatus.retval != 0) {
1475
                    chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "command returned exit status %d\n", ((aos_shell_t*)shell)->execstatus.retval);
1476
                  }
1477
                  break;
1478
                }
1479
                cmd = cmd->next;
1480
              } /* end of while */
1481

    
1482
              // if no matching command was found, print an error
1483
              if (cmd == NULL) {
1484
                chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "%s: command not found\n", ((aos_shell_t*)shell)->arglist[0]);
1485
              }
1486
            }
1487

    
1488
            // reset some internal variables and eprint a new prompt
1489
            if (readeval == AOS_SUCCESS && !chThdShouldTerminateX()) {
1490
              ((aos_shell_t*)shell)->inputdata.cursorpos = 0;
1491
              ((aos_shell_t*)shell)->inputdata.lineend = 0;
1492
              _printPrompt((aos_shell_t*)shell);
1493
            }
1494
          }
1495

    
1496
          // iterate to next channel
1497
          channel = channel->next;
1498
        }
1499
        break;
1500
      }
1501

    
1502
      // other events
1503
      default:
1504
      {
1505
        // print an error message
1506
        chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown event received (0x%08X)\n", eventmask);
1507
        break;
1508
      }
1509

    
1510
    } /* end of switch */
1511

    
1512
  } /* end of while */
1513

    
1514
  // fire event and exit the thread
1515
  chSysLock();
1516
  chEvtBroadcastFlagsI(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXIT);
1517
  chThdExitS(MSG_OK);
1518
  // no chSysUnlock() required since the thread has been terminated an all waiting threads have been woken up
1519
}
1520

    
1521
#endif /* AMIROOS_CFG_SHELL_ENABLE == true */