Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (44.843 KB)

1 e545e620 Thomas Schöpping
/*
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 ba516b61 Thomas Schöpping
#if (AMIROOS_CFG_SHELL_ENABLE == true)
22 e545e620 Thomas Schöpping
#include <aos_debug.h>
23
#include <aos_time.h>
24
#include <aos_system.h>
25
#include <string.h>
26 ba516b61 Thomas Schöpping
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 dd8738ea Thomas Schöpping
    return streamWrite(((AosShellChannel*)instance)->asyncchannel, bp, n);
44 ba516b61 Thomas Schöpping
  } 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 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
55
    return streamRead(((AosShellChannel*)instance)->asyncchannel, bp, n);
56
  } else {
57
    return 0;
58
  }
59 ba516b61 Thomas Schöpping
}
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 dd8738ea Thomas Schöpping
    return streamPut(((AosShellChannel*)instance)->asyncchannel, b);
68 ba516b61 Thomas Schöpping
  } 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 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
79
    return streamGet(((AosShellChannel*)instance)->asyncchannel);
80
  } else {
81
    return MSG_RESET;
82
  }
83 ba516b61 Thomas Schöpping
}
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 dd8738ea Thomas Schöpping
    return chnPutTimeout(((AosShellChannel*)instance)->asyncchannel, b, time);
92 ba516b61 Thomas Schöpping
  } 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 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
103
    return chnGetTimeout(((AosShellChannel*)instance)->asyncchannel, time);
104
  } else {
105
    return MSG_RESET;
106
  }
107 ba516b61 Thomas Schöpping
}
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 dd8738ea Thomas Schöpping
    return chnWriteTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
116 ba516b61 Thomas Schöpping
  } 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 dd8738ea Thomas Schöpping
  if (((AosShellChannel*)instance)->flags & AOS_SHELLCHANNEL_INPUT_ENABLED) {
127
    return chnReadTimeout(((AosShellChannel*)instance)->asyncchannel, bp, n, time);
128
  } else {
129
    return 0;
130
  }
131 ba516b61 Thomas Schöpping
}
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 dd8738ea Thomas Schöpping
  msg_t ret = MSG_OK;
179 ba516b61 Thomas Schöpping
180
  // iterate through the list of channels
181
  while (channel != NULL) {
182 dd8738ea Thomas Schöpping
    msg_t ret_ = streamPut(channel, b);
183
    ret = (ret_ < ret) ? ret_ : ret;
184 ba516b61 Thomas Schöpping
    channel = channel->next;
185
  }
186
187 dd8738ea Thomas Schöpping
  return ret;
188 ba516b61 Thomas Schöpping
}
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 e545e620 Thomas Schöpping
/**
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 ba516b61 Thomas Schöpping
    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 e545e620 Thomas Schöpping
  }
258
259
  // print the actual prompt string
260
  if (shell->prompt && !(shell->config & AOS_SHELL_CONFIG_PROMPT_MINIMAL)) {
261 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%s$ ", shell->prompt);
262 e545e620 Thomas Schöpping
  } else {
263 ba516b61 Thomas Schöpping
    chprintf((BaseSequentialStream*)&shell->stream, "%>$ ");
264 e545e620 Thomas Schöpping
  }
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 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, '\b');
399 e545e620 Thomas Schöpping
    --pos;
400
  }
401
402
  // move cursor right by printing line content
403
  while (pos < to) {
404 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, shell->line[pos]);
405 e545e620 Thomas Schöpping
    ++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 ba516b61 Thomas Schöpping
    streamPut(&shell->stream, shell->line[from + cnt]);
429 e545e620 Thomas Schöpping
  }
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 ba516b61 Thomas Schöpping
static aos_status_t _readChannel(aos_shell_t* shell, AosShellChannel* channel, size_t* n)
540 e545e620 Thomas Schöpping
{
541
  aosDbgCheck(shell != NULL);
542 ba516b61 Thomas Schöpping
  aosDbgCheck(channel != NULL);
543
  aosDbgCheck(n != NULL);
544 e545e620 Thomas Schöpping
545
  // local variables
546 ba516b61 Thomas Schöpping
  aos_shellaction_t action = AOS_SHELL_ACTION_NONE;
547 e545e620 Thomas Schöpping
  char c;
548 dd8738ea Thomas Schöpping
  special_key_t key;
549 e545e620 Thomas Schöpping
550 ba516b61 Thomas Schöpping
  // 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 dd8738ea Thomas Schöpping
    key = KEY_UNKNOWN;
556 e545e620 Thomas Schöpping
557
    // parse escape sequence
558 ba516b61 Thomas Schöpping
    if (shell->inputdata.escp > 0) {
559
      shell->inputdata.escseq[shell->inputdata.escp] = c;
560
      ++shell->inputdata.escp;
561
      key = _interpreteEscapeSequence(shell->inputdata.escseq);
562 e545e620 Thomas Schöpping
      if (key == KEY_AMBIGUOUS) {
563
        // read next byte to resolve ambiguity
564
        continue;
565
      } else {
566 ba516b61 Thomas Schöpping
        /*
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 e545e620 Thomas Schöpping
      }
574
    }
575
576 ba516b61 Thomas Schöpping
    /* interprete keys or character */
577 e545e620 Thomas Schöpping
    {
578 ba516b61 Thomas Schöpping
      // 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 e545e620 Thomas Schöpping
        } else {
595 ba516b61 Thomas Schöpping
          action = AOS_SHELL_ACTION_AUTOFILL;
596 e545e620 Thomas Schöpping
        }
597 ba516b61 Thomas Schöpping
      }
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 e545e620 Thomas Schöpping
        }
610 ba516b61 Thomas Schöpping
      }
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 e545e620 Thomas Schöpping
        }
618 ba516b61 Thomas Schöpping
      }
619
620
      // 'page up' of 'arrow up' key
621
      else if (key == KEY_PAGE_UP || key == KEY_ARROW_UP) {
622 e545e620 Thomas Schöpping
        // ignore if there was some input
623 ba516b61 Thomas Schöpping
        if (shell->inputdata.noinput) {
624
          action = AOS_SHELL_ACTION_RECALLLAST;
625 e545e620 Thomas Schöpping
        }
626 ba516b61 Thomas Schöpping
      }
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 e545e620 Thomas Schöpping
        // ignore if line is empty
631 ba516b61 Thomas Schöpping
        if (shell->inputdata.lineend > 0) {
632
          action = AOS_SHELL_ACTION_CLEAR;
633 e545e620 Thomas Schöpping
        }
634 ba516b61 Thomas Schöpping
      }
635
636
      // 'home' key
637
      else if (key == KEY_HOME) {
638 e545e620 Thomas Schöpping
        // ignore if cursor is very left
639 ba516b61 Thomas Schöpping
        if (shell->inputdata.cursorpos > 0) {
640
          action = AOS_SHELL_ACTION_CURSOR2START;
641 e545e620 Thomas Schöpping
        }
642 ba516b61 Thomas Schöpping
      }
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 e545e620 Thomas Schöpping
        }
650 ba516b61 Thomas Schöpping
      }
651
652
      // 'arrow left' key
653
      else if (key == KEY_ARROW_LEFT) {
654 e545e620 Thomas Schöpping
        // ignore if cursor is very left
655 ba516b61 Thomas Schöpping
        if (shell->inputdata.cursorpos > 0) {
656
          action = AOS_SHELL_ACTION_CURSORLEFT;
657 e545e620 Thomas Schöpping
        }
658 ba516b61 Thomas Schöpping
      }
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 e545e620 Thomas Schöpping
        }
666 ba516b61 Thomas Schöpping
      }
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 e545e620 Thomas Schöpping
      }
677
    }
678
679
    /* handle function */
680 ba516b61 Thomas Schöpping
    switch (action) {
681
      case AOS_SHELL_ACTION_READCHAR:
682
      {
683 e545e620 Thomas Schöpping
        // line is full
684 ba516b61 Thomas Schöpping
        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 e545e620 Thomas Schöpping
          _printPrompt(shell);
688 ba516b61 Thomas Schöpping
          _printLine(shell, 0, shell->inputdata.lineend);
689
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
690 e545e620 Thomas Schöpping
        }
691
        // read character
692
        else {
693
          // clear old line content on first input
694 ba516b61 Thomas Schöpping
          if (shell->inputdata.noinput) {
695 e545e620 Thomas Schöpping
            memset(shell->line, '\0', shell->linesize);
696 ba516b61 Thomas Schöpping
            shell->inputdata.noinput = false;
697 e545e620 Thomas Schöpping
          }
698
          // overwrite content
699
          if (shell->config & AOS_SHELL_CONFIG_INPUT_OVERWRITE) {
700 ba516b61 Thomas Schöpping
            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 e545e620 Thomas Schöpping
          }
705
          // insert character
706
          else {
707 ba516b61 Thomas Schöpping
            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 e545e620 Thomas Schöpping
          }
714
        }
715
        break;
716 ba516b61 Thomas Schöpping
      }
717 e545e620 Thomas Schöpping
718 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_AUTOFILL:
719 e545e620 Thomas Schöpping
      {
720
        const char* fill = shell->line;
721 ba516b61 Thomas Schöpping
        size_t cmatch = shell->inputdata.cursorpos;
722 e545e620 Thomas Schöpping
        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 ba516b61 Thomas Schöpping
          if (cmatch + cmp == shell->inputdata.cursorpos) {
737
            cmatch = shell->inputdata.cursorpos;
738 e545e620 Thomas Schöpping
            fill = cmd->name;
739
            // break the loop only if there are no case mismatches with the input
740 ba516b61 Thomas Schöpping
            n = shell->inputdata.cursorpos;
741 e545e620 Thomas Schöpping
            _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 ba516b61 Thomas Schöpping
          else if (cmatch + cmp > shell->inputdata.cursorpos) {
748 e545e620 Thomas Schöpping
            // 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 ba516b61 Thomas Schöpping
        if (cmatch > shell->inputdata.cursorpos || (cmatch == shell->inputdata.cursorpos && matchlevel == CHAR_MATCH_NCASE)) {
766
          shell->inputdata.noinput = false;
767 e545e620 Thomas Schöpping
          // limit auto fill so it will not overflow the line width
768 ba516b61 Thomas Schöpping
          if (shell->inputdata.lineend + (cmatch - shell->inputdata.cursorpos) > shell->linesize) {
769
            cmatch = shell->linesize - shell->inputdata.lineend + shell->inputdata.cursorpos;
770 e545e620 Thomas Schöpping
          }
771
          // move trailing memory further in the line
772 ba516b61 Thomas Schöpping
          memmove(&(shell->line[cmatch]), &(shell->line[shell->inputdata.cursorpos]), shell->inputdata.lineend - shell->inputdata.cursorpos);
773
          shell->inputdata.lineend += cmatch - shell->inputdata.cursorpos;
774 e545e620 Thomas Schöpping
          // if there was no incorrect case when matching
775
          if (matchlevel == CHAR_MATCH_CASE) {
776
            // insert fill command name to line
777 ba516b61 Thomas Schöpping
            memcpy(&(shell->line[shell->inputdata.cursorpos]), &(fill[shell->inputdata.cursorpos]), cmatch - shell->inputdata.cursorpos);
778 e545e620 Thomas Schöpping
            // print the output
779 ba516b61 Thomas Schöpping
            _printLine(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
780 e545e620 Thomas Schöpping
          } else {
781
            // overwrite line with fill command name
782
            memcpy(shell->line, fill, cmatch);
783
            // reprint the whole line
784 ba516b61 Thomas Schöpping
            _moveCursor(shell, shell->inputdata.cursorpos, 0);
785
            _printLine(shell, 0, shell->inputdata.lineend);
786 e545e620 Thomas Schöpping
          }
787
          // move cursor to the end of the matching sequence
788 ba516b61 Thomas Schöpping
          shell->inputdata.cursorpos = cmatch;
789
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
790 e545e620 Thomas Schöpping
        }
791
        break;
792
      }
793
794 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_SUGGEST:
795 e545e620 Thomas Schöpping
      {
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 ba516b61 Thomas Schöpping
          size_t i = shell->inputdata.cursorpos;
801
          if (shell->inputdata.cursorpos > 0) {
802 e545e620 Thomas Schöpping
            _strccmp(shell->line, cmd->name, true, &i, NULL);
803
          }
804 ba516b61 Thomas Schöpping
          const int cmp = (i < shell->inputdata.cursorpos) ?
805
                            (i - shell->inputdata.cursorpos) :
806 e545e620 Thomas Schöpping
                            (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 ba516b61 Thomas Schöpping
              _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
814
              streamPut(&shell->stream, '\n');
815 e545e620 Thomas Schöpping
            }
816
            // print the command
817 ba516b61 Thomas Schöpping
            chprintf((BaseSequentialStream*)&shell->stream, "\t%s\n", cmd->name);
818 e545e620 Thomas Schöpping
            ++matches;
819
          }
820
        }
821
        // reprint the prompt and line if any matches have been found
822
        if (matches > 0) {
823
          _printPrompt(shell);
824 ba516b61 Thomas Schöpping
          _printLine(shell, 0, shell->inputdata.lineend);
825
          _moveCursor(shell, shell->inputdata.lineend, shell->inputdata.cursorpos);
826
          shell->inputdata.noinput = false;
827 e545e620 Thomas Schöpping
        }
828
        break;
829
      }
830
831 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_INSERTTOGGLE:
832
      {
833 e545e620 Thomas Schöpping
        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 ba516b61 Thomas Schöpping
      }
840 e545e620 Thomas Schöpping
841 ba516b61 Thomas Schöpping
      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 e545e620 Thomas Schöpping
        break;
849 ba516b61 Thomas Schöpping
      }
850 e545e620 Thomas Schöpping
851 ba516b61 Thomas Schöpping
      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 e545e620 Thomas Schöpping
        break;
862 ba516b61 Thomas Schöpping
      }
863 e545e620 Thomas Schöpping
864 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_RECALLLAST:
865 e545e620 Thomas Schöpping
      {
866
        // replace any intermediate NUL bytes with spaces
867 ba516b61 Thomas Schöpping
        shell->inputdata.lineend = 0;
868 e545e620 Thomas Schöpping
        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 ba516b61 Thomas Schöpping
                shell->inputdata.lineend = nul_end + 1;
880 e545e620 Thomas Schöpping
                break;
881
              } else {
882
                ++nul_end;
883
              }
884
            }
885
            nul_start = nul_end + 1;
886
          } else {
887 ba516b61 Thomas Schöpping
            ++shell->inputdata.lineend;
888 e545e620 Thomas Schöpping
            ++nul_start;
889
          }
890
        }
891 ba516b61 Thomas Schöpping
        shell->inputdata.cursorpos = shell->inputdata.lineend;
892 e545e620 Thomas Schöpping
        // print the line
893 ba516b61 Thomas Schöpping
        shell->inputdata.noinput = _printLine(shell, 0, shell->inputdata.lineend) == 0;
894 e545e620 Thomas Schöpping
        break;
895
      }
896
897 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CLEAR:
898
      {
899 e545e620 Thomas Schöpping
        // clear output
900 ba516b61 Thomas Schöpping
        _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 e545e620 Thomas Schöpping
        }
904 ba516b61 Thomas Schöpping
        _moveCursor(shell, shell->inputdata.lineend, 0);
905
        shell->inputdata.cursorpos = 0;
906
        shell->inputdata.lineend = 0;
907
        shell->inputdata.noinput = true;
908 e545e620 Thomas Schöpping
        break;
909 ba516b61 Thomas Schöpping
      }
910 e545e620 Thomas Schöpping
911 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSOR2START:
912
      {
913
        _moveCursor(shell, shell->inputdata.cursorpos, 0);
914
        shell->inputdata.cursorpos = 0;
915 e545e620 Thomas Schöpping
        break;
916 ba516b61 Thomas Schöpping
      }
917 e545e620 Thomas Schöpping
918 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSOR2END:
919
      {
920
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.lineend);
921
        shell->inputdata.cursorpos = shell->inputdata.lineend;
922 e545e620 Thomas Schöpping
        break;
923 ba516b61 Thomas Schöpping
      }
924 e545e620 Thomas Schöpping
925 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSORLEFT:
926
      {
927
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos-1);
928
        --shell->inputdata.cursorpos;
929 e545e620 Thomas Schöpping
        break;
930 ba516b61 Thomas Schöpping
      }
931 e545e620 Thomas Schöpping
932 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_CURSORRIGHT:
933
      {
934
        _moveCursor(shell, shell->inputdata.cursorpos, shell->inputdata.cursorpos+1);
935
        ++shell->inputdata.cursorpos;
936 e545e620 Thomas Schöpping
        break;
937 ba516b61 Thomas Schöpping
      }
938 e545e620 Thomas Schöpping
939 ba516b61 Thomas Schöpping
      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 e545e620 Thomas Schöpping
          // fill the remainder of the line with NUL bytes
946 ba516b61 Thomas Schöpping
          memset(&(shell->line[shell->inputdata.lineend]), '\0', *n);
947
          // reset static variables
948
          shell->inputdata.noinput = true;
949 e545e620 Thomas Schöpping
        }
950 ba516b61 Thomas Schöpping
        return AOS_SUCCESS;
951 e545e620 Thomas Schöpping
        break;
952 ba516b61 Thomas Schöpping
      }
953 e545e620 Thomas Schöpping
954 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_ESCSTART:
955
      {
956
        shell->inputdata.escseq[0] = c;
957
        ++shell->inputdata.escp;
958 e545e620 Thomas Schöpping
        break;
959 ba516b61 Thomas Schöpping
      }
960 e545e620 Thomas Schöpping
961 ba516b61 Thomas Schöpping
      case AOS_SHELL_ACTION_NONE:
962 e545e620 Thomas Schöpping
      default:
963 ba516b61 Thomas Schöpping
      {
964 e545e620 Thomas Schöpping
        // do nothing (ignore input) and read next byte
965
        continue;
966
        break;
967 ba516b61 Thomas Schöpping
      }
968
    } /* end of switch */
969 e545e620 Thomas Schöpping
970 ba516b61 Thomas Schöpping
    shell->inputdata.lastaction = action;
971 e545e620 Thomas Schöpping
  } /* end of while */
972
973 ba516b61 Thomas Schöpping
  // no more data could be read from the channel
974
  return AOS_WARNING;
975 e545e620 Thomas Schöpping
}
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 ba516b61 Thomas Schöpping
void aosShellInit(aos_shell_t* shell, event_source_t* oseventsource,  const char* prompt, char* line, size_t linesize, char** arglist, size_t arglistsize)
1054 e545e620 Thomas Schöpping
{
1055
  aosDbgCheck(shell != NULL);
1056 ba516b61 Thomas Schöpping
  aosDbgCheck(oseventsource != NULL);
1057 e545e620 Thomas Schöpping
  aosDbgCheck(line != NULL);
1058
  aosDbgCheck(arglist != NULL);
1059
1060
  // set parameters
1061
  shell->thread = NULL;
1062
  chEvtObjectInit(&shell->eventSource);
1063 ba516b61 Thomas Schöpping
  shell->os.eventSource = oseventsource;
1064
  aosShellStreamInit(&shell->stream);
1065 e545e620 Thomas Schöpping
  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 ba516b61 Thomas Schöpping
  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 e545e620 Thomas Schöpping
  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 ba516b61 Thomas Schöpping
 * @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 dd8738ea Thomas Schöpping
 * @param[in] channel       The AosShellChannel to initialize.
1109
 * @param[in] asyncchannel  An BaseAsynchronousChannel this AosShellChannel is associated with.
1110 ba516b61 Thomas Schöpping
 */
1111 dd8738ea Thomas Schöpping
void aosShellChannelInit(AosShellChannel* channel, BaseAsynchronousChannel* asyncchannel)
1112 ba516b61 Thomas Schöpping
{
1113
  aosDbgCheck(channel != NULL);
1114 dd8738ea Thomas Schöpping
  aosDbgCheck(asyncchannel != NULL);
1115 ba516b61 Thomas Schöpping
1116
  channel->vmt = &_channelvmt;
1117 dd8738ea Thomas Schöpping
  channel->asyncchannel = asyncchannel;
1118
  channel->listener.wflags = 0;
1119 ba516b61 Thomas Schöpping
  channel->next = NULL;
1120
  channel->flags = 0;
1121
1122
  return;
1123
}
1124
1125
/**
1126 e545e620 Thomas Schöpping
 * @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 ba516b61 Thomas Schöpping
  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 e545e620 Thomas Schöpping
      }
1168 ba516b61 Thomas Schöpping
      return AOS_SUCCESS;
1169 e545e620 Thomas Schöpping
    }
1170
  }
1171 ba516b61 Thomas Schöpping
  // the end of the list has been reached
1172
1173
  // append the command
1174
  *curr = cmd;
1175
  return AOS_SUCCESS;
1176 e545e620 Thomas Schöpping
}
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 ba516b61 Thomas Schöpping
 * @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 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->next == NULL && (channel->flags & AOS_SHELLCHANNEL_ATTACHED) == 0);
1241 ba516b61 Thomas Schöpping
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 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL && channel->flags & AOS_SHELLCHANNEL_ATTACHED);
1263 ba516b61 Thomas Schöpping
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 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1298 ba516b61 Thomas Schöpping
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 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1315 ba516b61 Thomas Schöpping
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 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1332 ba516b61 Thomas Schöpping
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 dd8738ea Thomas Schöpping
  aosDbgCheck(channel != NULL && channel->asyncchannel != NULL);
1346 ba516b61 Thomas Schöpping
1347
  channel->flags &= ~AOS_SHELLCHANNEL_OUTPUT_ENABLED;
1348
1349
  return;
1350
}
1351
1352
/**
1353 e545e620 Thomas Schöpping
 * @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 ba516b61 Thomas Schöpping
  eventmask_t eventmask;
1364
  eventflags_t eventflags;
1365
  AosShellChannel* channel;
1366
  aos_status_t readeval;
1367
  size_t nchars = 0;
1368 e545e620 Thomas Schöpping
  size_t nargs = 0;
1369 ba516b61 Thomas Schöpping
  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 dd8738ea Thomas Schöpping
    chEvtRegisterMaskWithFlags(&(channel->asyncchannel->event), &(channel->listener), AOS_SHELL_EVENTMASK_INPUT, channel->listener.wflags);
1377 ba516b61 Thomas Schöpping
  }
1378 e545e620 Thomas Schöpping
1379
  // fire start event
1380
  chEvtBroadcastFlags(&(((aos_shell_t*)shell)->eventSource), AOS_SHELL_EVTFLAG_START);
1381
1382 ba516b61 Thomas Schöpping
  // print the prompt for the first time
1383
  _printPrompt((aos_shell_t*)shell);
1384
1385 e545e620 Thomas Schöpping
  // enter thread loop
1386
  while (!chThdShouldTerminateX()) {
1387 ba516b61 Thomas Schöpping
    // 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 e545e620 Thomas Schöpping
        break;
1405
      }
1406
1407 ba516b61 Thomas Schöpping
      // 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 dd8738ea Thomas Schöpping
            // 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 ba516b61 Thomas Schöpping
                  }
1437 dd8738ea Thomas Schöpping
                  break;
1438 ba516b61 Thomas Schöpping
                }
1439 dd8738ea Thomas Schöpping
                cmd = cmd->next;
1440
              } /* end of while */
1441 e545e620 Thomas Schöpping
1442 dd8738ea Thomas Schöpping
              // 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 ba516b61 Thomas Schöpping
              }
1446
            }
1447 dd8738ea Thomas Schöpping
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 ba516b61 Thomas Schöpping
            }
1454 e545e620 Thomas Schöpping
          }
1455 ba516b61 Thomas Schöpping
1456
          // iterate to next channel
1457
          channel = channel->next;
1458 e545e620 Thomas Schöpping
        }
1459 ba516b61 Thomas Schöpping
        break;
1460 e545e620 Thomas Schöpping
      }
1461 ba516b61 Thomas Schöpping
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 e545e620 Thomas Schöpping
      }
1469
1470 ba516b61 Thomas Schöpping
    } /* end of switch */
1471
1472
  } /* end of while */
1473 e545e620 Thomas Schöpping
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 ba516b61 Thomas Schöpping
1481
#endif /* AMIROOS_CFG_SHELL_ENABLE == true */