Statistics
| Branch: | Tag: | Revision:

amiro-os / os / core / src / aos_shell.c @ cb835a3e

History | View | Annotate | Download (44.843 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, systime_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, systime_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, systime_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, systime_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
static const struct AosShellChannelVMT _channelvmt = {
134
  _channelwrite,
135
  _channelread,
136
  _channelput,
137
  _channelget,
138
  _channelputt,
139
  _channelgett,
140
  _channelwritet,
141
  _channelreadt,
142
};
143

    
144
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n)
145
{
146
  aosDbgCheck(instance != NULL);
147

    
148
  // local variables
149
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
150
  size_t bytes;
151
  size_t maxbytes = 0;
152

    
153
  // iterate through the list of channels
154
  while (channel != NULL) {
155
    bytes = streamWrite(channel, bp, n);
156
    maxbytes = (bytes > maxbytes) ? bytes : maxbytes;
157
    channel = channel->next;
158
  }
159

    
160
  return maxbytes;
161
}
162

    
163
static size_t _stremread(void *instance, uint8_t *bp, size_t n)
164
{
165
  (void)instance;
166
  (void)bp;
167
  (void)n;
168

    
169
  return 0;
170
}
171

    
172
static msg_t _streamput(void *instance, uint8_t b)
173
{
174
  aosDbgCheck(instance != NULL);
175

    
176
  // local variables
177
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
178
  msg_t ret = MSG_OK;
179

    
180
  // iterate through the list of channels
181
  while (channel != NULL) {
182
    msg_t ret_ = streamPut(channel, b);
183
    ret = (ret_ < ret) ? ret_ : ret;
184
    channel = channel->next;
185
  }
186

    
187
  return ret;
188
}
189

    
190
static msg_t _streamget(void *instance)
191
{
192
  (void)instance;
193

    
194
  return 0;
195
}
196

    
197
static const struct AosShellStreamVMT _streamvmt = {
198
  _streamwrite,
199
  _stremread,
200
  _streamput,
201
  _streamget,
202
};
203

    
204
/**
205
 * @brief   Enumerator of special keyboard keys.
206
 */
207
typedef enum special_key {
208
  KEY_UNKNOWN,      /**< any/unknow key */
209
  KEY_AMBIGUOUS,    /**< key is ambiguous */
210
  KEY_TAB,          /**< tabulator key */
211
  KEY_ESCAPE,       /**< escape key */
212
  KEY_BACKSPACE,    /**< backspace key */
213
  KEY_INSERT,       /**< insert key */
214
  KEY_DELETE,       /**< delete key */
215
  KEY_HOME,         /**< home key */
216
  KEY_END,          /**< end key */
217
  KEY_PAGE_UP,      /**< page up key */
218
  KEY_PAGE_DOWN,    /**< page down key */
219
  KEY_ARROW_UP,     /**< arrow up key */
220
  KEY_ARROW_DOWN,   /**< arrow down key */
221
  KEY_ARROW_LEFT,   /**< arrow left key */
222
  KEY_ARROW_RIGHT,  /**< arrow right key */
223
} special_key_t;
224

    
225
/**
226
 * @brief   Enumerator for case (in)sensitive character matching.
227
 */
228
typedef enum charmatch {
229
  CHAR_MATCH_NOT    = 0,  /**< Characters do not match at all. */
230
  CHAR_MATCH_NCASE  = 1,  /**< Characters would match case insensitive. */
231
  CHAR_MATCH_CASE   = 2,  /**< Characters do match with case. */
232
} charmatch_t;
233

    
234
/**
235
 * @brief   Print the shell prompt
236
 * @details Depending on the configuration flags, the system uptime is printed before the prompt string.
237
 *
238
 * @param[in] shell   Pointer to the shell object.
239
 */
240
static void _printPrompt(aos_shell_t* shell)
241
{
242
  aosDbgCheck(shell != NULL);
243

    
244
  // print the system uptime before prompt is configured
245
  if (shell->config & AOS_SHELL_CONFIG_PROMPT_UPTIME) {
246
    // get current system uptime
247
    aos_timestamp_t uptime;
248
    aosSysGetUptime(&uptime);
249

    
250
    chprintf((BaseSequentialStream*)&shell->stream, "[%01u:%02u:%02u:%02u:%03u:%03u] ",
251
             (uint32_t)(uptime / MICROSECONDS_PER_DAY),
252
             (uint8_t)(uptime % MICROSECONDS_PER_DAY / MICROSECONDS_PER_HOUR),
253
             (uint8_t)(uptime % MICROSECONDS_PER_HOUR / MICROSECONDS_PER_MINUTE),
254
             (uint8_t)(uptime % MICROSECONDS_PER_MINUTE / MICROSECONDS_PER_SECOND),
255
             (uint16_t)(uptime % MICROSECONDS_PER_SECOND / MICROSECONDS_PER_MILLISECOND),
256
             (uint16_t)(uptime % MICROSECONDS_PER_MILLISECOND / MICROSECONDS_PER_MICROSECOND));
257
  }
258

    
259
  // print the actual prompt string
260
  if (shell->prompt && !(shell->config & AOS_SHELL_CONFIG_PROMPT_MINIMAL)) {
261
    chprintf((BaseSequentialStream*)&shell->stream, "%s$ ", shell->prompt);
262
  } else {
263
    chprintf((BaseSequentialStream*)&shell->stream, "%>$ ");
264
  }
265

    
266
  return;
267
}
268

    
269
/**
270
 * @brief   Interprete a escape sequence
271
 *
272
 * @param[in] seq   Character sequence to interprete.
273
 *                  Must be terminated by NUL byte.
274
 *
275
 * @return          A @p special_key value.
276
 */
277
static special_key_t _interpreteEscapeSequence(const char seq[])
278
{
279
  // local variables
280
  bool ambiguous = false;
281
  int cmp = 0;
282

    
283
  // TAB
284
  /* not supported yet; use "\x09" instead */
285

    
286
  // BACKSPACE
287
  /* not supported yet; use "\x08" instead */
288

    
289
  // ESCAPE
290
  cmp = strcmp(seq, "\x1B");
291
  if (cmp == 0) {
292
    return KEY_ESCAPE;
293
  } else {
294
    ambiguous |= (cmp < 0);
295
  }
296

    
297
  // INSERT
298
  cmp = strcmp(seq, "\x1B\x5B\x32\x7E");
299
  if (cmp == 0) {
300
    return KEY_INSERT;
301
  } else {
302
    ambiguous |= (cmp < 0);
303
  }
304

    
305
  // DELETE
306
  cmp = strcmp(seq, "\x1B\x5B\x33\x7E");
307
  if (cmp == 0) {
308
    return KEY_DELETE;
309
  } else {
310
    ambiguous |= (cmp < 0);
311
  }
312

    
313
  // HOME
314
  cmp = strcmp(seq, "\x1B\x4F\x48");
315
  if (cmp == 0) {
316
    return KEY_HOME;
317
  } else {
318
    ambiguous |= (cmp < 0);
319
  }
320

    
321
  // END
322
  cmp = strcmp(seq, "\x1B\x4F\x46");
323
  if (cmp == 0) {
324
    return KEY_END;
325
  } else {
326
    ambiguous |= (cmp < 0);
327
  }
328

    
329
  // PAGE UP
330
  cmp = strcmp(seq, "\x1B\x5B\x35\x7E");
331
  if (cmp == 0) {
332
    return KEY_PAGE_UP;
333
  } else {
334
    ambiguous |= (cmp < 0);
335
  }
336

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

    
345
  // ARROW UP
346
  cmp = strcmp(seq, "\x1B\x5B\x41");
347
  if (cmp == 0) {
348
    return KEY_ARROW_UP;
349
  } else {
350
    ambiguous |= (cmp < 0);
351
  }
352

    
353
  // ARROW DOWN
354
  cmp = strcmp(seq, "\x1B\x5B\x42");
355
  if (cmp == 0) {
356
    return KEY_ARROW_DOWN;
357
  } else {
358
    ambiguous |= (cmp < 0);
359
  }
360

    
361
  // ARROW LEFT
362
  cmp = strcmp(seq, "\x1B\x5B\x44");
363
  if (cmp == 0) {
364
    return KEY_ARROW_LEFT;
365
  } else {
366
    ambiguous |= (cmp < 0);
367
  }
368

    
369
  // ARROW RIGHT
370
  cmp = strcmp(seq, "\x1B\x5B\x43");
371
  if (cmp == 0) {
372
    return KEY_ARROW_RIGHT;
373
  } else {
374
    ambiguous |= (cmp < 0);
375
  }
376

    
377
  return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
378
}
379

    
380
/**
381
 * @brief   Move the cursor in the terminal
382
 *
383
 * @param[in] shell   Pointer to the shell object.
384
 * @param[in] from    Starting position of the cursor.
385
 * @param[in] to      Target position to move the cursor to.
386
 *
387
 * @return            The number of positions moved.
388
 */
389
static int _moveCursor(aos_shell_t* shell, const size_t from, const size_t to)
390
{
391
  aosDbgCheck(shell != NULL);
392

    
393
  // local variables
394
  size_t pos = from;
395

    
396
  // move cursor left by printing backspaces
397
  while (pos > to) {
398
    streamPut(&shell->stream, '\b');
399
    --pos;
400
  }
401

    
402
  // move cursor right by printing line content
403
  while (pos < to) {
404
    streamPut(&shell->stream, shell->line[pos]);
405
    ++pos;
406
  }
407

    
408
  return (int)pos - (int)from;
409
}
410

    
411
/**
412
 * @brief   Print content of the shell line
413
 *
414
 * @param[in] shell   Pointer to the shell object.
415
 * @param[in] from    First position to start printing from.
416
 * @param[in] to      Position after the last character to print.
417
 *
418
 * @return            Number of characters printed.
419
 */
420
static inline size_t _printLine(aos_shell_t* shell, const size_t from, const size_t to)
421
{
422
  aosDbgCheck(shell != NULL);
423

    
424
  // local variables
425
  size_t cnt;
426

    
427
  for (cnt = 0; from + cnt < to; ++cnt) {
428
    streamPut(&shell->stream, shell->line[from + cnt]);
429
  }
430

    
431
  return cnt;
432
}
433

    
434
/**
435
 * @brief   Compare two characters.
436
 *
437
 * @param[in] lhs       First character to compare.
438
 * @param[in] rhs       Second character to compare.
439
 *
440
 * @return              How well the characters match.
441
 */
442
static inline charmatch_t _charcmp(char lhs, char rhs)
443
{
444
  // if lhs is a upper case letter and rhs is a lower case letter
445
  if (lhs >= 'A' && lhs <= 'Z' && rhs >= 'a' && rhs <= 'z') {
446
    return (lhs == (rhs - 'a' + 'A')) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
447
  }
448
  // if lhs is a lower case letter and rhs is a upper case letter
449
  else if (lhs >= 'a' && lhs <= 'z' && rhs >= 'A' && rhs <= 'Z') {
450
    return ((lhs - 'a' + 'A') == rhs) ? CHAR_MATCH_NCASE : CHAR_MATCH_NOT;
451
  }
452
  // default
453
  else {
454
    return (lhs == rhs) ? CHAR_MATCH_CASE : CHAR_MATCH_NOT;
455
  }
456
}
457

    
458
/**
459
 * @brief   Maps an character from ASCII to a modified custom encoding.
460
 * @details The custom character encoding is very similar to ASCII and has the following structure:
461
 *          0x00=NULL ... 0x40='@' (identically to ASCII)
462
 *          0x4A='a'; 0x4B='A'; 0x4C='b'; 0x4D='B' ... 0x73='z'; 0x74='Z' (custom letter order)
463
 *          0x75='[' ... 0x7A='`' (0x5B..0x60 is ASCII)
464
 *          0x7B='{' ... 0x7F=DEL (identically to ASCII)
465
 *
466
 * @param[in] c   Character to map to the custom encoding.
467
 *
468
 * @return    The customly encoded character.
469
 */
470
static inline char _mapAscii2Custom(const char c)
471
{
472
  if (c >= 'A' && c <= 'Z') {
473
    return ((c - 'A') * 2) + 'A' + 1;
474
  } else if (c > 'Z' && c < 'a') {
475
    return c + ('z' - 'a') + 1;
476
  } else if (c >= 'a' && c <= 'z') {
477
    return ((c - 'a') * 2) + 'A';
478
  } else {
479
    return c;
480
  }
481
}
482

    
483
/**
484
 * @brief   Compares two strings wrt letter case.
485
 * @details Comparisson uses a custom character encoding or mapping.
486
 *          See @p _mapAscii2Custom for details.
487
 *
488
 * @param[in] str1    First string to compare.
489
 * @param[in] str2    Second string to compare.
490
 * @param[in] cs      Flag indicating whether comparison shall be case sensitive.
491
 * @param[in,out] n   Maximum number of character to compare (in) and number of matching characters (out).
492
 *                    If a null pointer is specified, this parameter is ignored.
493
 *                    If the value pointed to is zero, comarison will not be limited.
494
 * @param[out] m      Optional indicator whether there was at least one case mismatch.
495
 *
496
 * @return      Integer value indicating the relationship between the strings.
497
 * @retval <0   The first character that does not match has a lower value in str1 than in str2.
498
 * @retval  0   The contents of both strings are equal.
499
 * @retval >0   The first character that does not match has a greater value in str1 than in str2.
500
 */
501
static int _strccmp(const char *str1, const char *str2, bool cs, size_t* n, charmatch_t* m)
502
{
503
  aosDbgCheck(str1 != NULL);
504
  aosDbgCheck(str2 != NULL);
505

    
506
  // initialize variables
507
  if (m) {
508
    *m = CHAR_MATCH_NOT;
509
  }
510
  size_t i = 0;
511

    
512
  // iterate through the strings
513
  while ((n == NULL) || (*n == 0) || (*n > 0 && i < *n)) {
514
    // break on NUL
515
    if (str1[i] == '\0' || str2[i] == '\0') {
516
      if (n) {
517
        *n = i;
518
      }
519
      break;
520
    }
521
    // compare character
522
    const charmatch_t match = _charcmp(str1[i], str2[i]);
523
    if ((match == CHAR_MATCH_CASE) || (!cs && match == CHAR_MATCH_NCASE)) {
524
      if (m != NULL && *m != CHAR_MATCH_NCASE) {
525
        *m = match;
526
      }
527
      ++i;
528
    } else {
529
      if (n) {
530
        *n = i;
531
      }
532
      break;
533
    }
534
  }
535

    
536
  return _mapAscii2Custom(str1[i]) - _mapAscii2Custom(str2[i]);
537
}
538

    
539
static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, size_t* n)
540
{
541
  aosDbgCheck(shell != NULL);
542
  aosDbgCheck(channel != NULL);
543
  aosDbgCheck(n != NULL);
544

    
545
  // local variables
546
  aos_shellaction_t action = AOS_SHELL_ACTION_NONE;
547
  char c;
548
  special_key_t key;
549

    
550
  // initialize output variables
551
  *n = 0;
552

    
553
  // read character by character from the channel
554
  while (chnReadTimeout(channel, (uint8_t*)&c, 1, TIME_IMMEDIATE)) {
555
    key = KEY_UNKNOWN;
556

    
557
    // parse escape sequence
558
    if (shell->inputdata.escp > 0) {
559
      shell->inputdata.escseq[shell->inputdata.escp] = c;
560
      ++shell->inputdata.escp;
561
      key = _interpreteEscapeSequence(shell->inputdata.escseq);
562
      if (key == KEY_AMBIGUOUS) {
563
        // read next byte to resolve ambiguity
564
        continue;
565
      } else {
566
        /*
567
         * If the escape sequence could either be parsed sucessfully
568
         * or there is no match (KEY_UNKNOWN),
569
         * reset the sequence variable and interprete key/character
570
         */
571
        shell->inputdata.escp = 0;
572
        memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
573
      }
574
    }
575

    
576
    /* interprete keys or character */
577
    {
578
      // default
579
      action = AOS_SHELL_ACTION_NONE;
580

    
581
      // printable character
582
      if (key == KEY_UNKNOWN && c >= '\x20' && c <= '\x7E') {
583
        action = AOS_SHELL_ACTION_READCHAR;
584
      }
585

    
586
      // tab key or character
587
      else if (key == KEY_TAB || c == '\x09') {
588
        /*
589
         * pressing tab once applies auto fill
590
         * pressing tab a second time prints suggestions
591
         */
592
        if (shell->inputdata.lastaction == AOS_SHELL_ACTION_AUTOFILL || shell->inputdata.lastaction == AOS_SHELL_ACTION_SUGGEST) {
593
          action = AOS_SHELL_ACTION_SUGGEST;
594
        } else {
595
          action = AOS_SHELL_ACTION_AUTOFILL;
596
        }
597
      }
598

    
599
      // INS key
600
      else if (key == KEY_INSERT) {
601
        action = AOS_SHELL_ACTION_INSERTTOGGLE;
602
      }
603

    
604
      // DEL key or character
605
      else if (key == KEY_DELETE || c == '\x7F') {
606
        // ignore if cursor is at very right
607
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
608
          action = AOS_SHELL_ACTION_DELETEFORWARD;
609
        }
610
      }
611

    
612
      // backspace key or character
613
      else if (key == KEY_BACKSPACE || c == '\x08') {
614
        // ignore if cursor is at very left
615
        if (shell->inputdata.cursorpos > 0) {
616
          action = AOS_SHELL_ACTION_DELETEBACKWARD;
617
        }
618
      }
619

    
620
      // 'page up' of 'arrow up' key
621
      else if (key == KEY_PAGE_UP || key == KEY_ARROW_UP) {
622
        // ignore if there was some input
623
        if (shell->inputdata.noinput) {
624
          action = AOS_SHELL_ACTION_RECALLLAST;
625
        }
626
      }
627

    
628
      // 'page down' key, 'arrow done' key, 'end of test' character or 'end of transmission' character
629
      else if (key == KEY_PAGE_DOWN || key == KEY_ARROW_DOWN || c == '\x03' || c == '\x03') {
630
        // ignore if line is empty
631
        if (shell->inputdata.lineend > 0) {
632
          action = AOS_SHELL_ACTION_CLEAR;
633
        }
634
      }
635

    
636
      // 'home' key
637
      else if (key == KEY_HOME) {
638
        // ignore if cursor is very left
639
        if (shell->inputdata.cursorpos > 0) {
640
          action = AOS_SHELL_ACTION_CURSOR2START;
641
        }
642
      }
643

    
644
      // 'end' key
645
      else if (key == KEY_END) {
646
        // ignore if cursos is very right
647
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
648
          action = AOS_SHELL_ACTION_CURSOR2END;
649
        }
650
      }
651

    
652
      // 'arrow left' key
653
      else if (key == KEY_ARROW_LEFT) {
654
        // ignore if cursor is very left
655
        if (shell->inputdata.cursorpos > 0) {
656
          action = AOS_SHELL_ACTION_CURSORLEFT;
657
        }
658
      }
659

    
660
      // 'arrow right' key
661
      else if (key == KEY_ARROW_RIGHT) {
662
        // irgnore if cursor is very right
663
        if (shell->inputdata.cursorpos < shell->inputdata.lineend) {
664
          action = AOS_SHELL_ACTION_CURSORRIGHT;
665
        }
666
      }
667

    
668
      // carriage return ('\r') or line feed ('\n') character
669
      else if (c == '\x0D' || c == '\x0A') {
670
        action = AOS_SHELL_ACTION_EXECUTE;
671
      }
672

    
673
      // ESC key or [ESCAPE] character
674
      else if (key == KEY_ESCAPE || c == '\x1B') {
675
        action = AOS_SHELL_ACTION_ESCSTART;
676
      }
677
    }
678

    
679
    /* handle function */
680
    switch (action) {
681
      case AOS_SHELL_ACTION_READCHAR:
682
      {
683
        // line is full
684
        if (shell->inputdata.lineend + 1 >= shell->linesize) {
685
          _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
686
          chprintf((BaseSequentialStream*)&shell->stream, "\n\tmaximum line width reached\n");
687
          _printPrompt(shell);
688
          _printLine(shell, 0, shell->inputdata.lineend);
689
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
690
        }
691
        // read character
692
        else {
693
          // clear old line content on first input
694
          if (shell->inputdata.noinput) {
695
            memset(shell->line, '\0', shell->linesize);
696
            shell->inputdata.noinput = false;
697
          }
698
          // overwrite content
699
          if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
700
            shell->line[shell->inputdata.cursorpos] = c;
701
            ++shell->inputdata.cursorpos;
702
            shell->inputdata.lineend = (shell->inputdata.cursorpos > shell->inputdata.lineend) ? shell->inputdata.cursorpos : shell->inputdata.lineend;
703
            streamPut(&shell->stream, (uint8_t)c);
704
          }
705
          // insert character
706
          else {
707
            memmove(&(shell->line[shell->inputdata.cursorpos+1]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
708
            shell->line[shell->inputdata.cursorpos] = c;
709
            ++shell->inputdata.lineend;
710
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
711
            ++shell->inputdata.cursorpos;
712
            _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
713
          }
714
        }
715
        break;
716
      }
717

    
718
      case AOS_SHELL_ACTION_AUTOFILL:
719
      {
720
        const char* fill = shell->line;
721
        size_t cmatch = shell->inputdata.cursorpos;
722
        charmatch_t matchlevel = CHAR_MATCH_NOT;
723
        size_t n;
724
        // iterate through command list
725
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
726
          // compare current match with command
727
          n = cmatch;
728
          charmatch_t mlvl = CHAR_MATCH_NOT;
729
          _strccmp(fill, cmd->name, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, (n == 0) ? NULL : &n, &mlvl);
730
          const int cmp = (n < cmatch) ?
731
                            (n - cmatch) :
732
                            (cmd->name[n] != '\0') ?
733
                              strlen(cmd->name) - n :
734
                              0;
735
          // if an exact match was found
736
          if (cmatch + cmp == shell->inputdata.cursorpos) {
737
            cmatch = shell->inputdata.cursorpos;
738
            fill = cmd->name;
739
            // break the loop only if there are no case mismatches with the input
740
            n = shell->inputdata.cursorpos;
741
            _strccmp(fill, shell->line, false, &n, &mlvl);
742
            if (mlvl == CHAR_MATCH_CASE) {
743
              break;
744
            }
745
          }
746
          // if a not exact match was found
747
          else if (cmatch + cmp > shell->inputdata.cursorpos) {
748
            // if this is the first one
749
            if (fill == shell->line) {
750
              cmatch += cmp;
751
              fill = cmd->name;
752
            }
753
            // if this is a worse one
754
            else if ((cmp < 0) || (cmp == 0 && mlvl == CHAR_MATCH_CASE)) {
755
              cmatch += cmp;
756
            }
757
          }
758
          // non matching commands are ignored
759
          else {}
760
        }
761
        // evaluate if there are case mismatches
762
        n = cmatch;
763
        _strccmp(shell->line, fill, shell->config & AOS_SHELL_CONFIG_MATCH_CASE, &n, &matchlevel);
764
        // print the auto fill if any
765
        if (cmatch > shell->inputdata.cursorpos || (cmatch == shell->inputdata.cursorpos && matchlevel == CHAR_MATCH_NCASE)) {
766
          shell->inputdata.noinput = false;
767
          // limit auto fill so it will not overflow the line width
768
          if (shell->inputdata.lineend + (cmatch - shell->inputdata.cursorpos) > shell->linesize) {
769
            cmatch = shell->linesize - shell->inputdata.lineend + shell->inputdata.cursorpos;
770
          }
771
          // move trailing memory further in the line
772
          memmove(&(shell->line[cmatch]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
773
          shell->inputdata.lineend += cmatch - shell->inputdata.cursorpos;
774
          // if there was no incorrect case when matching
775
          if (matchlevel == CHAR_MATCH_CASE) {
776
            // insert fill command name to line
777
            memcpy(&(shell->line[shell->inputdata.cursorpos]), &(fill[shell->inputdata.cursorpos]), cmatch - shell->inputdata.cursorpos);
778
            // print the output
779
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
780
          } else {
781
            // overwrite line with fill command name
782
            memcpy(shell->line, fill, cmatch);
783
            // reprint the whole line
784
            _moveCursor(shell, shell->inputdata.cursorpos, 0);
785
            _printLine(shell, 0, shell->inputdata.lineend);
786
          }
787
          // move cursor to the end of the matching sequence
788
          shell->inputdata.cursorpos = cmatch;
789
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
790
        }
791
        break;
792
      }
793

    
794
      case AOS_SHELL_ACTION_SUGGEST:
795
      {
796
        unsigned int matches = 0;
797
        // iterate through command list
798
        for (aos_shellcommand_t* cmd = shell->commands; cmd != NULL; cmd = cmd->next) {
799
          // compare line content with command, excpet if cursorpos=0
800
          size_t i = shell->inputdata.cursorpos;
801
          if (shell->inputdata.cursorpos > 0) {
802
            _strccmp(shell->line, cmd->name, true, &i, NULL);
803
          }
804
          const int cmp = (i < shell->inputdata.cursorpos) ?
805
                            (i - shell->inputdata.cursorpos) :
806
                            (cmd->name[i] != '\0') ?
807
                              strlen(cmd->name) - i :
808
                              0;
809
          // if a match was found
810
          if (cmp > 0) {
811
            // if this is the first one
812
            if (matches == 0) {
813
              _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
814
              streamPut(&shell->stream, '\n');
815
            }
816
            // print the command
817
            chprintf((BaseSequentialStream*)&shell->stream, "\t%s\n", cmd->name);
818
            ++matches;
819
          }
820
        }
821
        // reprint the prompt and line if any matches have been found
822
        if (matches > 0) {
823
          _printPrompt(shell);
824
          _printLine(shell, 0, shell->inputdata.lineend);
825
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
826
          shell->inputdata.noinput = false;
827
        }
828
        break;
829
      }
830

    
831
      case AOS_SHELL_ACTION_INSERTTOGGLE:
832
      {
833
        if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
834
          shell->config &= ~AOS_SHELL_CONFIG_INPUT_OVERWRITE;
835
        } else {
836
          shell->config |= AOS_SHELL_CONFIG_INPUT_OVERWRITE;
837
        }
838
        break;
839
      }
840

    
841
      case AOS_SHELL_ACTION_DELETEFORWARD:
842
      {
843
        --shell->inputdata.lineend;
844
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
845
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
846
        streamPut(&shell->stream, ' ');
847
        _moveCursor(shell, shell->inputdata.lineend + 1, shell->inputdata.cursorpos);
848
        break;
849
      }
850

    
851
      case AOS_SHELL_ACTION_DELETEBACKWARD:
852
      {
853
        --shell->inputdata.cursorpos;
854
        memmove(&(shell->line[shell->inputdata.cursorpos]), &(shell->line[shell->inputdata.cursorpos+1]), shell->inputdata.lineend - shell->inputdata.cursorpos);
855
        --shell->inputdata.lineend;
856
        shell->line[shell->inputdata.lineend] = '\0';
857
        _moveCursor(shell, shell->inputdata.cursorpos + 1, shell->inputdata.cursorpos);
858
        _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
859
        streamPut(&shell->stream, ' ');
860
        _moveCursor(shell, shell->inputdata.lineend+1, shell->inputdata.cursorpos);
861
        break;
862
      }
863

    
864
      case AOS_SHELL_ACTION_RECALLLAST:
865
      {
866
        // replace any intermediate NUL bytes with spaces
867
        shell->inputdata.lineend = 0;
868
        size_t nul_start = 0;
869
        size_t nul_end = 0;
870
        // search line for a NUL byte
871
        while (nul_start < shell->linesize) {
872
          if (shell->line[nul_start] == '\0') {
873
            nul_end = nul_start + 1;
874
            // keep searcjing for a byte that is not NUL
875
            while (nul_end < shell->linesize) {
876
              if (shell->line[nul_end] != '\0') {
877
                // an intermediate NUL sequence was found
878
                memset(&(shell->line[nul_start]), ' ', nul_end - nul_start);
879
                shell->inputdata.lineend = nul_end + 1;
880
                break;
881
              } else {
882
                ++nul_end;
883
              }
884
            }
885
            nul_start = nul_end + 1;
886
          } else {
887
            ++shell->inputdata.lineend;
888
            ++nul_start;
889
          }
890
        }
891
        shell->inputdata.cursorpos = shell->inputdata.lineend;
892
        // print the line
893
        shell->inputdata.noinput = _printLine(shell, 0, shell->inputdata.lineend) == 0;
894
        break;
895
      }
896

    
897
      case AOS_SHELL_ACTION_CLEAR:
898
      {
899
        // clear output
900
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
901
        for (shell->inputdata.cursorpos = 0; shell->inputdata.cursorpos < shell->inputdata.lineend; ++shell->inputdata.cursorpos) {
902
          streamPut(&shell->stream, ' ');
903
        }
904
        _moveCursor(shell, shell->inputdata.lineend, 0);
905
        shell->inputdata.cursorpos = 0;
906
        shell->inputdata.lineend = 0;
907
        shell->inputdata.noinput = true;
908
        break;
909
      }
910

    
911
      case AOS_SHELL_ACTION_CURSOR2START:
912
      {
913
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
914
        shell->inputdata.cursorpos = 0;
915
        break;
916
      }
917

    
918
      case AOS_SHELL_ACTION_CURSOR2END:
919
      {
920
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
921
        shell->inputdata.cursorpos = shell->inputdata.lineend;
922
        break;
923
      }
924

    
925
      case AOS_SHELL_ACTION_CURSORLEFT:
926
      {
927
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos-1);
928
        --shell->inputdata.cursorpos;
929
        break;
930
      }
931

    
932
      case AOS_SHELL_ACTION_CURSORRIGHT:
933
      {
934
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos+1);
935
        ++shell->inputdata.cursorpos;
936
        break;
937
      }
938

    
939
      case AOS_SHELL_ACTION_EXECUTE:
940
      {
941
        streamPut(&shell->stream, '\n');
942
        // set the number of read bytes and return
943
        if (!shell->inputdata.noinput) {
944
          *n = shell->linesize - shell->inputdata.lineend;
945
          // fill the remainder of the line with NUL bytes
946
          memset(&(shell->line[shell->inputdata.lineend]), '\0', *n);
947
          // reset static variables
948
          shell->inputdata.noinput = true;
949
        }
950
        return AOS_SUCCESS;
951
        break;
952
      }
953

    
954
      case AOS_SHELL_ACTION_ESCSTART:
955
      {
956
        shell->inputdata.escseq[0] = c;
957
        ++shell->inputdata.escp;
958
        break;
959
      }
960

    
961
      case AOS_SHELL_ACTION_NONE:
962
      default:
963
      {
964
        // do nothing (ignore input) and read next byte
965
        continue;
966
        break;
967
      }
968
    } /* end of switch */
969

    
970
    shell->inputdata.lastaction = action;
971
  } /* end of while */
972

    
973
  // no more data could be read from the channel
974
  return AOS_WARNING;
975
}
976

    
977
/**
978
 * @brief   Parses the content of the input buffer (line) to separate arguments.
979
 *
980
 * @param[in] shell   Pointer to the shell object.
981
 *
982
 * @return            Number of arguments found.
983
 */
984
static size_t _parseArguments(aos_shell_t* shell)
985
{
986
  aosDbgCheck(shell != NULL);
987

    
988
  /*
989
   * States for a very small FSM.
990
   */
991
  typedef enum {
992
    START,
993
    SPACE,
994
    TEXT,
995
    END,
996
  } state_t;
997

    
998
  // local variables
999
  state_t state = START;
1000
  size_t arg = 0;
1001

    
1002
  // iterate through the line
1003
  for (char* c = shell->line; c < shell->line + shell->linesize; ++c) {
1004
    // terminate at first NUL byte
1005
    if (*c == '\0') {
1006
      state = END;
1007
      break;
1008
    }
1009
    // spaces become NUL bytes
1010
    else if (*c == ' ') {
1011
      *c = '\0';
1012
      state = SPACE;
1013
    }
1014
    // handle non-NUL bytes
1015
    else {
1016
      switch (state) {
1017
        case START:
1018
        case SPACE:
1019
          // ignore too many arguments
1020
          if (arg < shell->arglistsize) {
1021
            shell->arglist[arg] = c;
1022
          }
1023
          ++arg;
1024
          break;
1025
        case TEXT:
1026
        case END:
1027
        default:
1028
          break;
1029
      }
1030
      state = TEXT;
1031
    }
1032
  }
1033

    
1034
  // set all remaining argument pointers to NULL
1035
  for (size_t a = arg; a < shell->arglistsize; ++a) {
1036
    shell->arglist[a] = NULL;
1037
  }
1038

    
1039
  return arg;
1040
}
1041

    
1042
/**
1043
 * @brief   Initializes a shell object with the specified parameters.
1044
 *
1045
 * @param[in] shell         Pointer to the shell object.
1046
 * @param[in] stream        I/O stream to use.
1047
 * @param[in] prompt        Prompt line to print (NULL = use default prompt).
1048
 * @param[in] line          Pointer to the input buffer.
1049
 * @param[in] linesize      Size of the input buffer.
1050
 * @param[in] arglist       Pointer to the argument buffer.
1051
 * @param[in] arglistsize   Size of te argument buffer.
1052
 */
1053
void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char* prompt, char* line, size_t linesize, char** arglist, size_t arglistsize)
1054
{
1055
  aosDbgCheck(shell != NULL);
1056
  aosDbgCheck(oseventsource != NULL);
1057
  aosDbgCheck(line != NULL);
1058
  aosDbgCheck(arglist != NULL);
1059

    
1060
  // set parameters
1061
  shell->thread = NULL;
1062
  chEvtObjectInit(&shell->eventSource);
1063
  shell->os.eventSource = oseventsource;
1064
  aosShellStreamInit(&shell->stream);
1065
  shell->prompt = prompt;
1066
  shell->commands = NULL;
1067
  shell->execstatus.command = NULL;
1068
  shell->execstatus.retval = 0;
1069
  shell->line = line;
1070
  shell->linesize = linesize;
1071
  shell->inputdata.lastaction = AOS_SHELL_ACTION_NONE;
1072
  shell->inputdata.escp = 0;
1073
  memset(shell->inputdata.escseq, '\0', sizeof(shell->inputdata.escseq)*sizeof(shell->inputdata.escseq[0]));
1074
  shell->inputdata.cursorpos = 0;
1075
  shell->inputdata.lineend = 0;
1076
  shell->inputdata.noinput = true;
1077
  shell->arglist = arglist;
1078
  shell->arglistsize = arglistsize;
1079
  shell->config = 0x00;
1080

    
1081
  // initialize arrays
1082
  memset(shell->line, '\0', shell->linesize);
1083
  for (size_t a = 0; a < shell->arglistsize; ++a) {
1084
    shell->arglist[a] = NULL;
1085
  }
1086

    
1087
  return;
1088
}
1089

    
1090
/**
1091
 * @brief   Initialize an AosShellStream object.
1092
 *
1093
 * @param[in] stream  The AosShellStrem to initialize.
1094
 */
1095
void aosShellStreamInit(AosShellStream* stream)
1096
{
1097
  aosDbgCheck(stream != NULL);
1098

    
1099
  stream->vmt = &_streamvmt;
1100
  stream->channel = NULL;
1101

    
1102
  return;
1103
}
1104

    
1105
/**
1106
 * @brief   Initialize an AosShellChannel object with the specified parameters.
1107
 *
1108
 * @param[in] channel       The AosShellChannel to initialize.
1109
 * @param[in] asyncchannel  An BaseAsynchronousChannel this AosShellChannel is associated with.
1110
 */
1111
void aosShellChannelInit(AosShellChannel* channel, BaseAsynchronousChannel* asyncchannel)
1112
{
1113
  aosDbgCheck(channel != NULL);
1114
  aosDbgCheck(asyncchannel != NULL);
1115

    
1116
  channel->vmt = &_channelvmt;
1117
  channel->asyncchannel = asyncchannel;
1118
  channel->listener.wflags = 0;
1119
  channel->next = NULL;
1120
  channel->flags = 0;
1121

    
1122
  return;
1123
}
1124

    
1125
/**
1126
 * @brief   Inserts a command to the shells list of commands.
1127
 *
1128
 * @param[in] shell   Pointer to the shell object.
1129
 * @param[in] cmd     Pointer to the command to add.
1130
 *
1131
 * @return            A status value.
1132
 * @retval AOS_SUCCESS  The command was added successfully.
1133
 * @retval AOS_ERROR    Another command with identical name already exists.
1134
 */
1135
aos_status_t aosShellAddCommand(aos_shell_t *shell, aos_shellcommand_t *cmd)
1136
{
1137
  aosDbgCheck(shell != NULL);
1138
  aosDbgCheck(cmd != NULL);
1139
  aosDbgCheck(cmd->name != NULL && strlen(cmd->name) > 0 && strchr(cmd->name, ' ') == NULL && strchr(cmd->name, '\t') == NULL);
1140
  aosDbgCheck(cmd->callback != NULL);
1141
  aosDbgCheck(cmd->next == NULL);
1142

    
1143
  aos_shellcommand_t* prev = NULL;
1144
  aos_shellcommand_t** curr = &(shell->commands);
1145

    
1146
  // insert the command to the list wrt lexographical order (exception: lower case characters preceed upper their uppercase counterparts)
1147
  while (*curr != NULL) {
1148
    // iterate through the list as long as the command names are 'smaller'
1149
    const int cmp = _strccmp((*curr)->name, cmd->name, true, NULL, NULL);
1150
    if (cmp < 0) {
1151
      prev = *curr;
1152
      curr = &((*curr)->next);
1153
      continue;
1154
    }
1155
    // error if the command already exists
1156
    else if (cmp == 0) {
1157
      return AOS_ERROR;
1158
    }
1159
    // insert the command as soon as a 'larger' name was found
1160
    else /* if (cmpval > 0) */ {
1161
      cmd->next = *curr;
1162
      // special case: the first command is larger
1163
      if (prev == NULL) {
1164
        shell->commands = cmd;
1165
      } else {
1166
        prev->next = cmd;
1167
      }
1168
      return AOS_SUCCESS;
1169
    }
1170
  }
1171
  // the end of the list has been reached
1172

    
1173
  // append the command
1174
  *curr = cmd;
1175
  return AOS_SUCCESS;
1176
}
1177

    
1178
/**
1179
 * @brief   Removes a command from the shells list of commands.
1180
 *
1181
 * @param[in] shell     Pointer to the shell object.
1182
 * @param[in] cmd       Name of the command to removde.
1183
 * @param[out] removed  Optional pointer to the command that was removed.
1184
 *
1185
 * @return              A status value.
1186
 * @retval AOS_SUCCESS  The command was removed successfully.
1187
 * @retval AOS_ERROR    The command name was not found.
1188
 */
1189
aos_status_t aosShellRemoveCommand(aos_shell_t *shell, char *cmd, aos_shellcommand_t **removed)
1190
{
1191
  aosDbgCheck(shell != NULL);
1192
  aosDbgCheck(cmd != NULL && strlen(cmd) > 0);
1193

    
1194
  aos_shellcommand_t* prev = NULL;
1195
  aos_shellcommand_t** curr = &(shell->commands);
1196

    
1197
  // iterate through the list and seach for the specified command name
1198
  while (curr != NULL) {
1199
    const int cmpval = strcmp((*curr)->name, cmd);
1200
    // iterate through the list as long as the command names are 'smaller'
1201
    if (cmpval < 0) {
1202
      prev = *curr;
1203
      curr = &((*curr)->next);
1204
      continue;
1205
    }
1206
    // remove the command when found
1207
    else if (cmpval == 0) {
1208
      // special case: the first command matches
1209
      if (prev == NULL) {
1210
        shell->commands = (*curr)->next;
1211
      } else {
1212
        prev->next = (*curr)->next;
1213
      }
1214
      (*curr)->next = NULL;
1215
      // set the optional output argument
1216
      if (removed != NULL) {
1217
        *removed = *curr;
1218
      }
1219
      return AOS_SUCCESS;
1220
    }
1221
    // break the loop if the command names are 'larger'
1222
    else /* if (cmpval > 0) */ {
1223
      break;
1224
    }
1225
  }
1226

    
1227
  // if the command was not found, return an error
1228
  return AOS_ERROR;
1229
}
1230

    
1231
/**
1232
 * @brief   Add a channel to a AosShellStream.
1233
 *
1234
 * @param[in] stream    The AosShellStream to extend.
1235
 * @param[in] channel   The channel to be added to the stream.
1236
 */
1237
void aosShellStreamAddChannel(AosShellStream* stream, AosShellChannel* channel)
1238
{
1239
  aosDbgCheck(stream != NULL);
1240
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->next == NULL && (channel->flags & AOS_SHELLCHANNEL_ATTACHED) == 0);
1241

    
1242
  // prepend the new channel
1243
  chSysLock();
1244
  channel->flags |= AOS_SHELLCHANNEL_ATTACHED;
1245
  channel->next = stream->channel;
1246
  stream->channel = channel;
1247
  chSysUnlock();
1248

    
1249
  return;
1250
}
1251

    
1252
/**
1253
 * @brief   Remove a channel from an AosShellStream.
1254
 *
1255
 * @param[in] stream    The AosShellStream to modify.
1256
 * @param[in] channel   The channel to remove.
1257
 * @return
1258
 */
1259
aos_status_t aosShellStreamRemoveChannel(AosShellStream* stream, AosShellChannel* channel)
1260
{
1261
  aosDbgCheck(stream != NULL);
1262
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->flags & AOS_SHELLCHANNEL_ATTACHED);
1263

    
1264
  // local varibales
1265
  AosShellChannel* prev = NULL;
1266
  AosShellChannel* curr = stream->channel;
1267

    
1268
  // iterate through the list and search for the specified channel
1269
  while (curr != NULL) {
1270
    // if the channel was found
1271
    if (curr == channel) {
1272
      chSysLock();
1273
      // special case: the first channel matches (prev is NULL)
1274
      if (prev == NULL) {
1275
        stream->channel = curr->next;
1276
      } else {
1277
        prev->next = channel->next;
1278
      }
1279
      curr->next = NULL;
1280
      curr->flags &= ~AOS_SHELLCHANNEL_ATTACHED;
1281
      chSysUnlock();
1282
      return AOS_SUCCESS;
1283
    }
1284
  }
1285

    
1286
  // if the channel was not found, return an error
1287
  return AOS_ERROR;
1288
}
1289

    
1290
/**
1291
 * @brief   Enable a AosSheööChannel as input.
1292
 *
1293
 * @param[in] channel   The channel to enable as input.
1294
 */
1295
void aosShellChannelInputEnable(AosShellChannel* channel)
1296
{
1297
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1298

    
1299
  chSysLock();
1300
  channel->listener.wflags |= CHN_INPUT_AVAILABLE;
1301
  channel->flags |= AOS_SHELLCHANNEL_INPUT_ENABLED;
1302
  chSysUnlock();
1303

    
1304
  return;
1305
}
1306

    
1307
/**
1308
 * @brief   Disable a AosSheööChannel as input.
1309
 *
1310
 * @param[in] channel   The channel to disable as input.
1311
 */
1312
void aosShellChannelInputDisable( AosShellChannel* channel)
1313
{
1314
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1315

    
1316
  chSysLock();
1317
  channel->listener.wflags &= ~CHN_INPUT_AVAILABLE;
1318
  channel->flags &= ~AOS_SHELLCHANNEL_INPUT_ENABLED;
1319
  chSysUnlock();
1320

    
1321
  return;
1322
}
1323

    
1324
/**
1325
 * @brief   Enable a AosSheööChannel as output.
1326
 *
1327
 * @param[in] channel   The channel to enable as output.
1328
 */
1329
void aosShellChannelOutputEnable(AosShellChannel* channel)
1330
{
1331
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1332

    
1333
  channel->flags |= AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1334

    
1335
  return;
1336
}
1337

    
1338
/**
1339
 * @brief   Disable a AosSheööChannel as output.
1340
 *
1341
 * @param[in] channel   The channel to disable as output.
1342
 */
1343
void aosShellChannelOutputDisable(AosShellChannel* channel)
1344
{
1345
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1346

    
1347
  channel->flags &= ~AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1348

    
1349
  return;
1350
}
1351

    
1352
/**
1353
 * @brief   Thread main function.
1354
 *
1355
 * @param[in] aosShellThread    Name of the function;
1356
 * @param[in] shell             Pointer to the shell object.
1357
 */
1358
THD_FUNCTION(aosShellThread, shell)
1359
{
1360
  aosDbgCheck(shell != NULL);
1361

    
1362
  // local variables
1363
  eventmask_t eventmask;
1364
  eventflags_t eventflags;
1365
  AosShellChannel* channel;
1366
  aos_status_t readeval;
1367
  size_t nchars = 0;
1368
  size_t nargs = 0;
1369
  aos_shellcommand_t* cmd;
1370

    
1371

    
1372
  // register OS related events
1373
  chEvtRegisterMask(((aos_shell_t*)shell)->os.eventSource, &(((aos_shell_t*)shell)->os.eventListener), AOS_SHELL_EVENTMASK_OS);
1374
  // register events to all input channels
1375
  for (channel = ((aos_shell_t*)shell)->stream.channel; channel != NULL; channel = channel->next) {
1376
    chEvtRegisterMaskWithFlags(&(channel->asyncchannel->event), &(channel->listener), AOS_SHELL_EVENTMASK_INPUT, channel->listener.wflags);
1377
  }
1378

    
1379
  // fire start event
1380
  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_START);
1381

    
1382
  // print the prompt for the first time
1383
  _printPrompt((aos_shell_t*)shell);
1384

    
1385
  // enter thread loop
1386
  while (!chThdShouldTerminateX()) {
1387
    // wait for event and handle it accordingly
1388
    eventmask = chEvtWaitOne(ALL_EVENTS);
1389

    
1390
    // handle event
1391
    switch (eventmask) {
1392

    
1393
      // OS related events
1394
      case AOS_SHELL_EVENTMASK_OS:
1395
      {
1396
        eventflags = chEvtGetAndClearFlags(&((aos_shell_t*)shell)->os.eventListener);
1397
        // handle shutdown/restart events
1398
        if (eventflags & AOS_SYSTEM_EVENTFLAGS_SHUTDOWN) {
1399
          chThdTerminate(((aos_shell_t*)shell)->thread);
1400
        } else {
1401
          // print an error message
1402
          chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown OS event received (0x%08X)\n", eventflags);
1403
        }
1404
        break;
1405
      }
1406

    
1407
      // input events
1408
      case AOS_SHELL_EVENTMASK_INPUT:
1409
      {
1410
        // check and handle all channels
1411
        channel = ((aos_shell_t*)shell)->stream.channel;
1412
        while (channel != NULL) {
1413
          eventflags = chEvtGetAndClearFlags(&channel->listener);
1414
          // if there is new input
1415
          if (eventflags & CHN_INPUT_AVAILABLE) {
1416
            // read input from channel
1417
            readeval = _readChannel((aos_shell_t*)shell, channel, &nchars);
1418
            // parse input line to argument list only if the input shall be executed
1419
            nargs = (readeval == AOS_SUCCESS && nchars > 0) ? _parseArguments((aos_shell_t*)shell) : 0;
1420
            // check number of arguments
1421
            if (nargs > ((aos_shell_t*)shell)->arglistsize) {
1422
              // error too many arguments
1423
              chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\ttoo many arguments\n");
1424
            } else if (nargs > 0) {
1425
              // search command list for arg[0] and execute callback
1426
              cmd = ((aos_shell_t*)shell)->commands;
1427
              while (cmd != NULL) {
1428
                if (strcmp(((aos_shell_t*)shell)->arglist[0], cmd->name) == 0) {
1429
                  ((aos_shell_t*)shell)->execstatus.command = cmd;
1430
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXEC);
1431
                  ((aos_shell_t*)shell)->execstatus.retval = cmd->callback((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, nargs, ((aos_shell_t*)shell)->arglist);
1432
                  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_DONE);
1433
                  // notify if the command was not successful
1434
                  if (((aos_shell_t*)shell)->execstatus.retval != 0) {
1435
                    chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "command returned exit status %d\n", ((aos_shell_t*)shell)->execstatus.retval);
1436
                  }
1437
                  break;
1438
                }
1439
                cmd = cmd->next;
1440
              } /* end of while */
1441

    
1442
              // if no matching command was found, print an error
1443
              if (cmd == NULL) {
1444
                chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "%s: command not found\n", ((aos_shell_t*)shell)->arglist[0]);
1445
              }
1446
            }
1447

    
1448
            // reset some internal variables and eprint a new prompt
1449
            if (readeval == AOS_SUCCESS && !chThdShouldTerminateX()) {
1450
              ((aos_shell_t*)shell)->inputdata.cursorpos = 0;
1451
              ((aos_shell_t*)shell)->inputdata.lineend = 0;
1452
              _printPrompt((aos_shell_t*)shell);
1453
            }
1454
          }
1455

    
1456
          // iterate to next channel
1457
          channel = channel->next;
1458
        }
1459
        break;
1460
      }
1461

    
1462
      // other events
1463
      default:
1464
      {
1465
        // print an error message
1466
        chprintf((BaseSequentialStream*)&((aos_shell_t*)shell)->stream, "\nERROR: unknown event received (0x%08X)\n", eventmask);
1467
        break;
1468
      }
1469

    
1470
    } /* end of switch */
1471

    
1472
  } /* end of while */
1473

    
1474
  // fire event and exit the thread
1475
  chSysLock();
1476
  chEvtBroadcastFlagsI(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_EXIT);
1477
  chThdExitS(MSG_OK);
1478
  // no chSysUnlock() required since the thread has been terminated an all waiting threads have been woken up
1479
}
1480

    
1481
#endif /* AMIROOS_CFG_SHELL_ENABLE == true */