Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (44.89 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 <chprintf.h>
26
#include <string.h>
27
#include <aos_thread.h>
28

    
29

    
30

    
31
/**
32
 * @brief   Event mask to be set on OS related events.
33
 */
34
#define AOS_SHELL_EVENTMASK_OS                  EVENT_MASK(0)
35

    
36
/**
37
 * @brief   Event mask to be set on a input event.
38
 */
39
#define AOS_SHELL_EVENTMASK_INPUT               EVENT_MASK(1)
40

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

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

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

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

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

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

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

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

    
137
static const struct AosShellChannelVMT _channelvmt = {
138
  _channelwrite,
139
  _channelread,
140
  _channelput,
141
  _channelget,
142
  _channelputt,
143
  _channelgett,
144
  _channelwritet,
145
  _channelreadt,
146
};
147

    
148
static size_t _streamwrite(void *instance, const uint8_t *bp, size_t n)
149
{
150
  aosDbgCheck(instance != NULL);
151

    
152
  // local variables
153
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
154
  size_t bytes;
155
  size_t maxbytes = 0;
156

    
157
  // iterate through the list of channels
158
  while (channel != NULL) {
159
    bytes = streamWrite(channel, bp, n);
160
    maxbytes = (bytes > maxbytes) ? bytes : maxbytes;
161
    channel = channel->next;
162
  }
163

    
164
  return maxbytes;
165
}
166

    
167
static size_t _stremread(void *instance, uint8_t *bp, size_t n)
168
{
169
  (void)instance;
170
  (void)bp;
171
  (void)n;
172

    
173
  return 0;
174
}
175

    
176
static msg_t _streamput(void *instance, uint8_t b)
177
{
178
  aosDbgCheck(instance != NULL);
179

    
180
  // local variables
181
  AosShellChannel* channel = ((AosShellStream*)instance)->channel;
182
  msg_t ret = MSG_OK;
183

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

    
191
  return ret;
192
}
193

    
194
static msg_t _streamget(void *instance)
195
{
196
  (void)instance;
197

    
198
  return 0;
199
}
200

    
201
static const struct AosShellStreamVMT _streamvmt = {
202
  _streamwrite,
203
  _stremread,
204
  _streamput,
205
  _streamget,
206
};
207

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

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

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

    
248
  // print the system uptime before prompt is configured
249
  if (shell->config & AOS_SHELL_CONFIG_PROMPT_UPTIME) {
250
    // get current system uptime
251
    aos_timestamp_t uptime;
252
    aosSysGetUptime(&uptime);
253

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

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

    
270
  return;
271
}
272

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

    
287
  // TAB
288
  /* not supported yet; use "\x09" instead */
289

    
290
  // BACKSPACE
291
  /* not supported yet; use "\x08" instead */
292

    
293
  // ESCAPE
294
  cmp = strcmp(seq, "\x1B");
295
  if (cmp == 0) {
296
    return KEY_ESCAPE;
297
  } else {
298
    ambiguous |= (cmp < 0);
299
  }
300

    
301
  // INSERT
302
  cmp = strcmp(seq, "\x1B\x5B\x32\x7E");
303
  if (cmp == 0) {
304
    return KEY_INSERT;
305
  } else {
306
    ambiguous |= (cmp < 0);
307
  }
308

    
309
  // DELETE
310
  cmp = strcmp(seq, "\x1B\x5B\x33\x7E");
311
  if (cmp == 0) {
312
    return KEY_DELETE;
313
  } else {
314
    ambiguous |= (cmp < 0);
315
  }
316

    
317
  // HOME
318
  cmp = strcmp(seq, "\x1B\x4F\x48");
319
  if (cmp == 0) {
320
    return KEY_HOME;
321
  } else {
322
    ambiguous |= (cmp < 0);
323
  }
324

    
325
  // END
326
  cmp = strcmp(seq, "\x1B\x4F\x46");
327
  if (cmp == 0) {
328
    return KEY_END;
329
  } else {
330
    ambiguous |= (cmp < 0);
331
  }
332

    
333
  // PAGE UP
334
  cmp = strcmp(seq, "\x1B\x5B\x35\x7E");
335
  if (cmp == 0) {
336
    return KEY_PAGE_UP;
337
  } else {
338
    ambiguous |= (cmp < 0);
339
  }
340

    
341
  // PAGE DOWN
342
  cmp = strcmp(seq, "\x1B\x5B\x36\x7E");
343
  if (cmp == 0) {
344
    return KEY_PAGE_DOWN;
345
  } else {
346
    ambiguous |= (cmp < 0);
347
  }
348

    
349
  // ARROW UP
350
  cmp = strcmp(seq, "\x1B\x5B\x41");
351
  if (cmp == 0) {
352
    return KEY_ARROW_UP;
353
  } else {
354
    ambiguous |= (cmp < 0);
355
  }
356

    
357
  // ARROW DOWN
358
  cmp = strcmp(seq, "\x1B\x5B\x42");
359
  if (cmp == 0) {
360
    return KEY_ARROW_DOWN;
361
  } else {
362
    ambiguous |= (cmp < 0);
363
  }
364

    
365
  // ARROW LEFT
366
  cmp = strcmp(seq, "\x1B\x5B\x44");
367
  if (cmp == 0) {
368
    return KEY_ARROW_LEFT;
369
  } else {
370
    ambiguous |= (cmp < 0);
371
  }
372

    
373
  // ARROW RIGHT
374
  cmp = strcmp(seq, "\x1B\x5B\x43");
375
  if (cmp == 0) {
376
    return KEY_ARROW_RIGHT;
377
  } else {
378
    ambiguous |= (cmp < 0);
379
  }
380

    
381
  return ambiguous ? KEY_AMBIGUOUS : KEY_UNKNOWN;
382
}
383

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

    
397
  // local variables
398
  size_t pos = from;
399

    
400
  // move cursor left by printing backspaces
401
  while (pos > to) {
402
    streamPut(&shell->stream, '\b');
403
    --pos;
404
  }
405

    
406
  // move cursor right by printing line content
407
  while (pos < to) {
408
    streamPut(&shell->stream, shell->line[pos]);
409
    ++pos;
410
  }
411

    
412
  return (int)pos - (int)from;
413
}
414

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

    
428
  // local variables
429
  size_t cnt;
430

    
431
  for (cnt = 0; from + cnt < to; ++cnt) {
432
    streamPut(&shell->stream, shell->line[from + cnt]);
433
  }
434

    
435
  return cnt;
436
}
437

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

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

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

    
510
  // initialize variables
511
  if (m) {
512
    *m = CHAR_MATCH_NOT;
513
  }
514
  size_t i = 0;
515

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

    
540
  return _mapAscii2Custom(str1[i]) - _mapAscii2Custom(str2[i]);
541
}
542

    
543
static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, size_t* n)
544
{
545
  aosDbgCheck(shell != NULL);
546
  aosDbgCheck(channel != NULL);
547
  aosDbgCheck(n != NULL);
548

    
549
  // local variables
550
  aos_shellaction_t action = AOS_SHELL_ACTION_NONE;
551
  char c;
552
  special_key_t key;
553

    
554
  // initialize output variables
555
  *n = 0;
556

    
557
  // read character by character from the channel
558
  while (chnReadTimeout(channel, (uint8_t*)&c, 1, TIME_IMMEDIATE)) {
559
    key = KEY_UNKNOWN;
560

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

    
580
    /* interprete keys or character */
581
    {
582
      // default
583
      action = AOS_SHELL_ACTION_NONE;
584

    
585
      // printable character
586
      if (key == KEY_UNKNOWN && c >= '\x20' && c <= '\x7E') {
587
        action = AOS_SHELL_ACTION_READCHAR;
588
      }
589

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

    
603
      // INS key
604
      else if (key == KEY_INSERT) {
605
        action = AOS_SHELL_ACTION_INSERTTOGGLE;
606
      }
607

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

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

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

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

    
640
      // 'home' key
641
      else if (key == KEY_HOME) {
642
        // ignore if cursor is very left
643
        if (shell->inputdata.cursorpos > 0) {
644
          action = AOS_SHELL_ACTION_CURSOR2START;
645
        }
646
      }
647

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

    
656
      // 'arrow left' key
657
      else if (key == KEY_ARROW_LEFT) {
658
        // ignore if cursor is very left
659
        if (shell->inputdata.cursorpos > 0) {
660
          action = AOS_SHELL_ACTION_CURSORLEFT;
661
        }
662
      }
663

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

    
672
      // carriage return ('\r') or line feed ('\n') character
673
      else if (c == '\x0D' || c == '\x0A') {
674
        action = AOS_SHELL_ACTION_EXECUTE;
675
      }
676

    
677
      // ESC key or [ESCAPE] character
678
      else if (key == KEY_ESCAPE || c == '\x1B') {
679
        action = AOS_SHELL_ACTION_ESCSTART;
680
      }
681
    }
682

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

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

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

    
835
      case AOS_SHELL_ACTION_INSERTTOGGLE:
836
      {
837
        if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
838
          shell->config &= ~AOS_SHELL_CONFIG_INPUT_OVERWRITE;
839
        } else {
840
          shell->config |= AOS_SHELL_CONFIG_INPUT_OVERWRITE;
841
        }
842
        break;
843
      }
844

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

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

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

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

    
915
      case AOS_SHELL_ACTION_CURSOR2START:
916
      {
917
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
918
        shell->inputdata.cursorpos = 0;
919
        break;
920
      }
921

    
922
      case AOS_SHELL_ACTION_CURSOR2END:
923
      {
924
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
925
        shell->inputdata.cursorpos = shell->inputdata.lineend;
926
        break;
927
      }
928

    
929
      case AOS_SHELL_ACTION_CURSORLEFT:
930
      {
931
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos-1);
932
        --shell->inputdata.cursorpos;
933
        break;
934
      }
935

    
936
      case AOS_SHELL_ACTION_CURSORRIGHT:
937
      {
938
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos+1);
939
        ++shell->inputdata.cursorpos;
940
        break;
941
      }
942

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

    
958
      case AOS_SHELL_ACTION_ESCSTART:
959
      {
960
        shell->inputdata.escseq[0] = c;
961
        ++shell->inputdata.escp;
962
        break;
963
      }
964

    
965
      case AOS_SHELL_ACTION_NONE:
966
      default:
967
      {
968
        // do nothing (ignore input) and read next byte
969
        continue;
970
        break;
971
      }
972
    } /* end of switch */
973

    
974
    shell->inputdata.lastaction = action;
975
  } /* end of while */
976

    
977
  // no more data could be read from the channel
978
  return AOS_WARNING;
979
}
980

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

    
992
  /*
993
   * States for a very small FSM.
994
   */
995
  typedef enum {
996
    START,
997
    SPACE,
998
    TEXT,
999
    END,
1000
  } state_t;
1001

    
1002
  // local variables
1003
  state_t state = START;
1004
  size_t arg = 0;
1005

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

    
1038
  // set all remaining argument pointers to NULL
1039
  for (size_t a = arg; a < shell->arglistsize; ++a) {
1040
    shell->arglist[a] = NULL;
1041
  }
1042

    
1043
  return arg;
1044
}
1045

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

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

    
1085
  // initialize arrays
1086
  memset(shell->line, '\0', shell->linesize);
1087
  for (size_t a = 0; a < shell->arglistsize; ++a) {
1088
    shell->arglist[a] = NULL;
1089
  }
1090

    
1091
  return;
1092
}
1093

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

    
1103
  stream->vmt = &_streamvmt;
1104
  stream->channel = NULL;
1105

    
1106
  return;
1107
}
1108

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

    
1120
  channel->vmt = &_channelvmt;
1121
  channel->asyncchannel = asyncchannel;
1122
  channel->listener.wflags = 0;
1123
  channel->next = NULL;
1124
  channel->flags = 0;
1125

    
1126
  return;
1127
}
1128

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

    
1147
  aos_shellcommand_t* prev = NULL;
1148
  aos_shellcommand_t** curr = &(shell->commands);
1149

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

    
1177
  // append the command
1178
  *curr = cmd;
1179
  return AOS_SUCCESS;
1180
}
1181

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

    
1198
  aos_shellcommand_t* prev = NULL;
1199
  aos_shellcommand_t** curr = &(shell->commands);
1200

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

    
1231
  // if the command was not found, return an error
1232
  return AOS_ERROR;
1233
}
1234

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

    
1246
  // prepend the new channel
1247
  chSysLock();
1248
  channel->flags |= AOS_SHELLCHANNEL_ATTACHED;
1249
  channel->next = stream->channel;
1250
  stream->channel = channel;
1251
  chSysUnlock();
1252

    
1253
  return;
1254
}
1255

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

    
1268
  // local varibales
1269
  AosShellChannel* prev = NULL;
1270
  AosShellChannel* curr = stream->channel;
1271

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

    
1290
  // if the channel was not found, return an error
1291
  return AOS_ERROR;
1292
}
1293

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

    
1303
  chSysLock();
1304
  channel->listener.wflags |= CHN_INPUT_AVAILABLE;
1305
  channel->flags |= AOS_SHELLCHANNEL_INPUT_ENABLED;
1306
  chSysUnlock();
1307

    
1308
  return;
1309
}
1310

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

    
1320
  chSysLock();
1321
  channel->listener.wflags &= ~CHN_INPUT_AVAILABLE;
1322
  channel->flags &= ~AOS_SHELLCHANNEL_INPUT_ENABLED;
1323
  chSysUnlock();
1324

    
1325
  return;
1326
}
1327

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

    
1337
  channel->flags |= AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1338

    
1339
  return;
1340
}
1341

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

    
1351
  channel->flags &= ~AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1352

    
1353
  return;
1354
}
1355

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

    
1366
  // local variables
1367
  eventmask_t eventmask;
1368
  eventflags_t eventflags;
1369
  AosShellChannel* channel;
1370
  aos_status_t readeval;
1371
  size_t nchars = 0;
1372
  size_t nargs = 0;
1373
  aos_shellcommand_t* cmd;
1374

    
1375

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

    
1383
  // fire start event
1384
  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_START);
1385

    
1386
  // print the prompt for the first time
1387
  _printPrompt((aos_shell_t*)shell);
1388

    
1389
  // enter thread loop
1390
  while (!chThdShouldTerminateX()) {
1391
    // wait for event and handle it accordingly
1392
    eventmask = chEvtWaitOne(ALL_EVENTS);
1393

    
1394
    // handle event
1395
    switch (eventmask) {
1396

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

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

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

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

    
1460
          // iterate to next channel
1461
          channel = channel->next;
1462
        }
1463
        break;
1464
      }
1465

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

    
1474
    } /* end of switch */
1475

    
1476
  } /* end of while */
1477

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

    
1485
#endif /* AMIROOS_CFG_SHELL_ENABLE == true */