Shellminator  V1.1.1
Simple Terminal
Loading...
Searching...
No Matches
Shellminator.cpp
Go to the documentation of this file.
1/*
2 * Created on Aug 10 2020
3 *
4 * Copyright (c) 2020 - Daniel Hajnal
5 * hajnal.daniel96@gmail.com
6 * This file is part of the Shellminator project.
7 * Modified 2022.05.08
8*/
9
10/*
11MIT License
12
13Copyright (c) 2020 Daniel Hajnal
14
15Permission is hereby granted, free of charge, to any person obtaining a copy
16of this software and associated documentation files (the "Software"), to deal
17in the Software without restriction, including without limitation the rights
18to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19copies of the Software, and to permit persons to whom the Software is
20furnished to do so, subject to the following conditions:
21
22The above copyright notice and this permission notice shall be included in all
23copies or substantial portions of the Software.
24
25THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31SOFTWARE.
32*/
33
34#include "Shellminator.hpp"
35
36#ifdef __has_include
37 #if __has_include ("Commander-API.hpp")
38 #include "Commander-API.hpp"
39 #endif
40#endif
41
43
44#ifdef SHELLMINATOR_USE_ARDUINO_SERIAL
45Shellminator::Shellminator( HardwareSerial *serialPort_p ) {
46
47 // Initialise the arduinoSerialChannel as communication channel.
48 arduinoSerialChannel.select( serialPort_p );
49 channel = &arduinoSerialChannel;
50
51 // It has to be zero. We dont want to process any garbage.
52 cmd_buff_cntr = 0;
53
54 // This has to be 1 minimum, because the 0th element is used for the incoming data.
55 // The maximum value has to be ( SHELLMINATOR_BUFF_DIM - 1 )
56 cmd_buff_dim = 1;
57
58 // Just in case terminate the begining of the buffer
59 cmd_buff[ 0 ][ 0 ] = '\0';
60
61 // Because we did not specified the execution function, we have to make it a NULL
62 // pointer to make it detectable.
63 execution_fn = NULL;
64
65}
66
67Shellminator::Shellminator( HardwareSerial *serialPort_p, void( *execution_fn_p )( char* ) ) {
68
69 // Initialise the arduinoSerialChannel as communication channel.
70 arduinoSerialChannel.select( serialPort_p );
71 channel = &arduinoSerialChannel;
72
73 // It has to be zero. We dont want to process any garbage.
74 cmd_buff_cntr = 0;
75
76 // This has to be 1 minimum, because the 0th element is used for the incoming data.
77 // The maximum value has to be ( SHELLMINATOR_BUFF_DIM - 1 )
78 cmd_buff_dim = 1;
79
80 // Just in case terminate the begining of the buffer
81 cmd_buff[ 0 ][ 0 ] = '\0';
82
83 // passing execution_fn_p to execution_fn
84 execution_fn = execution_fn_p;
85
86}
87
88#endif
89
90#ifdef SHELLMINATOR_USE_ARDUINO_32U4_SERIAL
91Shellminator::Shellminator( Serial_ *serialPort_p ) {
92
93 // Initialise the arduinoSerialChannel as communication channel.
94 arduino32U4SerialChannel.select( serialPort_p );
95 channel = &arduino32U4SerialChannel;
96
97 // It has to be zero. We dont want to process any garbage.
98 cmd_buff_cntr = 0;
99
100 // This has to be 1 minimum, because the 0th element is used for the incoming data.
101 // The maximum value has to be ( SHELLMINATOR_BUFF_DIM - 1 )
102 cmd_buff_dim = 1;
103
104 // Just in case terminate the begining of the buffer
105 cmd_buff[ 0 ][ 0 ] = '\0';
106
107 // Because we did not specified the execution function, we have to make it a NULL
108 // pointer to make it detectable.
109 execution_fn = NULL;
110
111}
112
113Shellminator::Shellminator( Serial_ *serialPort_p, void( *execution_fn_p )( char* ) ) {
114
115 // Initialise the arduinoSerialChannel as communication channel.
116 arduino32U4SerialChannel.select( serialPort_p );
117 channel = &arduino32U4SerialChannel;
118
119 // It has to be zero. We dont want to process any garbage.
120 cmd_buff_cntr = 0;
121
122 // This has to be 1 minimum, because the 0th element is used for the incoming data.
123 // The maximum value has to be ( SHELLMINATOR_BUFF_DIM - 1 )
124 cmd_buff_dim = 1;
125
126 // Just in case terminate the begining of the buffer
127 cmd_buff[ 0 ][ 0 ] = '\0';
128
129 // passing execution_fn_p to execution_fn
130 execution_fn = execution_fn_p;
131
132}
133
134#endif
135
136#ifdef SHELLMINATOR_USE_WIFI_CLIENT
137Shellminator::Shellminator( WiFiClient *resp ) {
138
139 // Initialise the wifiChannel as communication channel.
140 wifiChannel.select( resp );
141 channel = &wifiChannel;
142
143 // It has to be zero. We dont want to process any garbage.
144 cmd_buff_cntr = 0;
145
146 // This has to be 1 minimum, because the 0th element is used for the incoming data.
147 // The maximum value has to be ( SHELLMINATOR_BUFF_DIM - 1 )
148 cmd_buff_dim = 1;
149
150 // Just in case terminate the begining of the buffer
151 cmd_buff[ 0 ][ 0 ] = '\0';
152
153 // Because we did not specified the execution function, we have to make it a NULL
154 // pointer to make it detectable.
155 execution_fn = NULL;
156
157}
158
159Shellminator::Shellminator( WiFiClient *resp, void( *execution_fn_p )( char* ) ) {
160
161 // Initialise the wifiChannel as communication channel.
162 wifiChannel.select( resp );
163 channel = &wifiChannel;
164
165 // It has to be zero. We dont want to process any garbage.
166 cmd_buff_cntr = 0;
167
168 // This has to be 1 minimum, because the 0th element is used for the incoming data.
169 // The maximum value has to be ( SHELLMINATOR_BUFF_DIM - 1 )
170 cmd_buff_dim = 1;
171
172 // Just in case terminate the begining of the buffer
173 cmd_buff[ 0 ][ 0 ] = '\0';
174
175 // passing execution_fn_p to execution_fn
176 execution_fn = execution_fn_p;
177
178}
179
180#endif
181
182
183void Shellminator::attachLogo( char* logo_p ){
184
185 logo = logo_p;
186
187}
188
189void Shellminator::attachLogo( const char* logo_p ){
190
191 logo = (char*)logo_p;
192
193}
194
195void Shellminator::addExecFunc( void( *execution_fn_p )( char* ) ){
196
197 // passing execution_fn_p to execution_fn
198 execution_fn = execution_fn_p;
199
200}
201
203
204 // explanation can be found here: http://braun-home.net/michael/info/misc/VT100_commands.htm
205 channel -> write( 27 ); // ESC character( decimal 27 )
206 channel -> print( (const char*)"[H" ); // VT100 Home command
207 channel -> write( 27 ); // ESC character( decimal 27 )
208 channel -> print( (const char*)"[J" ); // VT100 screen erase command
209
210}
211
212void Shellminator::printBanner() {
213
214 // Sets the terminal style to bold and the color to green.
215 // You can change it if you like. In my opinion the most
216 // useful is the invisible one :)
218
219 // Print the banner text.
220 channel -> print( banner );
221
222 // Sets the terminal style to regular and the color to white.
224
225 // Prints the end of the banner text. Why this?
226 // I don't know it looks a bit Linuxier this way.
227 channel -> print( (const char*)":~$ " );
228
229}
230
231void Shellminator::begin( char* banner_p ) {
232
233 // Copy the content from banner_p to banner. Because strncpy we can be sure that it wont overflow.
234 strncpy( banner, banner_p, SHELLMINATOR_BANNER_LEN );
235
236 // Just in case close the string
237 banner[ SHELLMINATOR_BANNER_LEN - 1 ] = '\0';
238
239 // Set the terminal color and style to the defined settings for the logo
241
242 // Draw the startup logo.
243 drawLogo();
244
245 // Print the banner message.
246 printBanner();
247
248}
249
250void Shellminator::begin( const char* banner_p ) {
251
252 // Copy the content from banner_p to banner. Because strncpy we can be sure that it wont overflow.
253 strncpy( banner, banner_p, SHELLMINATOR_BANNER_LEN );
254
255 // Just in case close the string
256 banner[ SHELLMINATOR_BANNER_LEN - 1 ] = '\0';
257
258 // Set the terminal color and style to the defined settings for the logo
260
261 // Draw the startup logo.
262 drawLogo();
263
264 // Print the banner message.
265 printBanner();
266
267}
268
270
271 // Send a simple backspace combo to the serial port
272 channel -> print( (const char*)"\b \b" );
273
274}
275
276void Shellminator::redrawLine(){
277
278 // General counter variable
279 uint32_t i;
280
281 // Clear line and return to the beginning.
282 channel -> write( 27 );
283 channel -> print( "[2K\r" );
284
285 printBanner();
286
287 #ifdef COMMANDER_API_VERSION
288
289 // If the command is found in Commander's API-tree
290 // it will be highlighted.
291 if( commandFound ){
292
294
295 }
296
297 else{
298
300
301 }
302
303 #endif
304
305 // Print all characters.
306 for( i = 0; i < cmd_buff_cntr; i++ ){
307
308 #ifdef COMMANDER_API_VERSION
309 // If a space character is found, we have to change
310 // back the color to white for the arguments.
311 if( cmd_buff[ 0 ][ i ] == ' ' ){
312
314
315 }
316 #endif
317
318 channel -> print( cmd_buff[ 0 ][ i ] );
319
320 }
321
322 // Step left with the terminal cursor to match the
323 // position in the cursor variable.
324 for( i = cmd_buff_cntr; i > cursor; i-- ){
325
326 channel -> write( 27 ); // ESC character( decimal 27 )
327 channel -> print( '[' ); // VT100 Cursor command.
328 channel -> print( '1' ); // 1 character movement.
329 channel -> print( 'D' ); // Left.
330
331 }
332
333 // At the end no matter what we have to change back
334 // the terminal font to white.
335 #ifdef COMMANDER_API_VERSION
337 #endif
338
339}
340
341void Shellminator::process( char new_char ) {
342
343 // General counter variable
344 uint32_t i;
345
346 // Check if the new character is backspace character.
347 // '\b' or 127 are both meaning that the backspace kes is pressed
348 if ( ( new_char == '\b' ) || ( new_char == 127 ) ) {
349
350 // If we press a backspace we have to reset cmd_buff_dim to default value
351 cmd_buff_dim = 1;
352
353 // We have to check the number of the characters in the buffer.
354 // If the buffer is empty we must not do anything!
355 if ( cursor > 0 ) {
356
357 for( i = (cursor - 1); i < ( cmd_buff_cntr - 1 ); i++ ){
358
359 cmd_buff[ 0 ][ i ] = cmd_buff[ 0 ][ i + 1 ];
360
361 }
362
363 // If there is at least 1 character in the buffer we jus simply
364 // decrement the cmd_buff_cntr. This will result that the new character
365 // will be stored in the previous characters place in the buffer.
366 cmd_buff_cntr--;
367 cursor--;
368
369 redrawLine();
370
371 // We have no more things to do, so return
372 return;
373
374 }
375
376 }
377
378 // If the enter key is pressed in the keyboard, the terminal application
379 // will send a '\r' character.
380 else if ( new_char == '\r' ) {
381
382 // If the enter key is pressed cmd_buff_dim has to be reset to the default value
383 cmd_buff_dim = 1;
384
385 // Because a command is sent we have to close it. Basically we replace the arrived
386 // '\r' character with a '\0' string terminator character. Now we have our command
387 // in a C/C++ like standard string format.
388 cmd_buff[ 0 ][ cmd_buff_cntr ] = '\0';
389
390 // We send a line break to the terminal to put the next data in new line
391 channel -> print( '\r' );
392 channel -> print( '\n' );
393
394 // If the arrived tata is not just a single enter we have to process the command.
395 if ( cmd_buff_cntr > 0 ) {
396
397 // We haveto check that execution_fn is not NULL.
398 if( execution_fn != NULL ){
399
400 // If it is a valid, then call it's function.
401 execution_fn( cmd_buff[ 0 ] );
402
403 }
404
405 #ifdef COMMANDER_API_VERSION
406
407 // If a Commander object is added, it can be used
408 // to execute the command without an execution_fn.
409 else if( commander != NULL ){
410
411 // Check for the right response channel.
412 #ifdef SHELLMINATOR_USE_ARDUINO_SERIAL
413 if( channel == &arduinoSerialChannel ){
414
415 commander -> execute( cmd_buff[ 0 ], arduinoSerialChannel.getSerialObject() );
416
417 }
418 #endif
419
420 #ifdef SHELLMINATOR_USE_ARDUINO_32U4_SERIAL
421 if( channel == &arduino32U4SerialChannel ){
422
423 commander -> execute( cmd_buff[ 0 ], arduino32U4SerialChannel.getSerialObject() );
424
425 }
426 #endif
427
428 #ifdef SHELLMINATOR_USE_WIFI_CLIENT
429 if( channel == &wifiChannel ){
430
431 commander -> execute( cmd_buff[ 0 ], wifiChannel.getClientObject() );
432
433 }
434 #endif
435
436 }
437
438 #endif
439
440 // If not, then just print it with Serial.
441 else{
442 channel -> print( (const char*)"cmd: " );
443 channel -> print( cmd_buff[ 0 ] );
444 channel -> print( '\r' );
445 channel -> print( '\n' );
446 }
447
448 // After we processed the command we have to shift the history upwards.
449 // To protect the copy against buffer overflow we use strncpy
450 for ( i = ( SHELLMINATOR_BUFF_DIM - 1 ); i > 0; i-- ) {
451
452 strncpy( cmd_buff[ i ], cmd_buff[ i - 1 ], SHELLMINATOR_BUFF_LEN );
453
454 }
455
456 }
457
458 // After the command processing finished we print a new banner to the terminal.
459 // This means that the device is finished and waits the new command.
460 printBanner();
461
462 // To empty the incoming string we have to zero it's counter.
463 cmd_buff_cntr = 0;
464 cursor = 0;
465
466 // We have no more things to do, so return
467 return;
468
469 }
470
471 // This part handles the arrow detection. Arrows are composed as a VT100 command.
472 // These commands usually contains a pattern. This pattern usually starts with
473 // an Escape character, that is deciman 27 in ASCII table.
474 // The escape_state variable stores the state of the VT100 command interpreter
475 // state-machine.
476 else if ( new_char == 27 ) {
477
478 // If escape character recived we set escape_state variable to 1.
479 escape_state = 1;
480
481 // We have no more things to do, so return
482 return;
483 }
484
485 // If the escape_state variable is 1 that means we expect that the new character will be
486 // a '[' character.
487 else if ( escape_state == 1 ) {
488
489 // Check that the new character is '['
490 if ( new_char == '[' ) {
491
492 // If it is, we set escape_state variable to 2
493 escape_state = 2;
494
495 // We have no more things to do, so return
496 return;
497
498 }
499
500 else {
501
502 // If the new character is not '[', that means it is not a VT100 command so we have to stop
503 // the interpretation of the escape sequence.
504 escape_state = 0;
505
506 // We have no more things to do, so return
507 return;
508
509 }
510
511 }
512
513 // If the escape_state variable is 2 that means we expect that the new character will be
514 // an 'A', 'B', 'C' or 'D' character. These four characters are represent the four arrow keys.
515 // A -> Up
516 // B -> Down
517 // C -> Right
518 // D -> Left
519 else if ( escape_state == 2 ) {
520
521 // To chose between the four valid values the easyest way is a switch.
522 switch ( new_char ) {
523
524 // Up arrow pressed
525 case 'A':
526
527 // Check if the arrow function is overriden.
528 if( upArrowOverrideFunc ){
529
530 upArrowOverrideFunc();
531 break;
532
533 }
534
535 // Because we have finished the ecape sequence interpretation we reset the state-machine.
536 escape_state = 0;
537
538 // We have to check that we can go upper in history
539 if ( cmd_buff_dim < ( SHELLMINATOR_BUFF_DIM ) ) {
540
541 // If we can we have to check that the previous command was not empty.
542 if ( cmd_buff[ cmd_buff_dim ][0] == '\0' ) {
543
544 // If it was empty we can't do much with an empty command so we return.
545 break;
546
547 }
548
549 // Now we have to copy the characters form the histoy to the 0th element in the buffer.
550 // Remember the 0th element is always reserved for the new data. If we brows the history the
551 // data in the history will overwrite the data in the 0th element so the historical data will be
552 // the new data. We use strncpy to prevent overflow.
553 strncpy( cmd_buff[ 0 ], cmd_buff[ cmd_buff_dim ], SHELLMINATOR_BUFF_LEN );
554
555 // We have to calculate the historical data length to pass it to the cmd_buff_cntr variable.
556 // It is important to track the end of the loaded string.
557 cmd_buff_cntr = strlen( cmd_buff[ 0 ] );
558 cursor = cmd_buff_cntr;
559
560 // We print the loaded command to the terminal interface.
561 //channel -> print( cmd_buff[ 0 ] );
562
563 redrawLine();
564
565 // We have to increment the cmd_buff_dim variable, to track the history position.
566 // Greater number means older command!
567 cmd_buff_dim++;
568
569 }
570
571 // We have finished so we can break from the switch.
572 break;
573
574 // Down arrow pressed
575 case 'B':
576
577 // Check if the arrow function is overriden.
578 if( downArrowOverrideFunc ){
579
580 downArrowOverrideFunc();
581 break;
582
583 }
584
585 // Because we have finished the ecape sequence interpretation we reset the state-machine.
586 escape_state = 0;
587
588 // We have to check that we can go lover in history, and we are not in the first previous command.
589 if ( cmd_buff_dim > 2 ) {
590
591 // We have to decrement the cmd_buff_dim variable, to track the history position.
592 // Lower number means newer command!
593 cmd_buff_dim--;
594
595 // Now we have to copy the characters form the histoy to the 0th element in the buffer.
596 // Remember the 0th element is always reserved for the new data. If we brows the history the
597 // data in the history will overwrite the data in the 0th element so the historical data will be
598 // the new data. We use strncpy to prevent overflow.
599 strncpy( cmd_buff[ 0 ], cmd_buff[ cmd_buff_dim - 1 ], SHELLMINATOR_BUFF_LEN );
600
601 // We have to calculate the historical data length to pass it to the cmd_buff_cntr variable.
602 // It is important to track the end of the loaded string.
603 cmd_buff_cntr = strlen( cmd_buff[ 0 ] );
604 cursor = cmd_buff_cntr;
605
606 // We print the loaded command to the terminal interface.
607 //channel -> print( cmd_buff[ 0 ] );
608 redrawLine();
609
610 }
611
612 // Check that if we are in the first previous command.
613 else if ( cmd_buff_dim == 2 ) {
614
615 // If we are in the first previous command, and we press the down key,
616 // that means we want to go to the 0th element in the terminal.
617 // In this case we have to clear the 0th element.
618 for ( i = 0; i < cmd_buff_cntr; i++ ) {
619
620 // The easyest way is to sand as many backspaces as many character was in the 0th element in the buffer.
622
623 }
624
625 // To empty the incoming string we have to zero it's counter.
626 cmd_buff_cntr = 0;
627 cursor = 0;
628
629 // We have to reset the cmd_buff_dim variable to the default value.
630 cmd_buff_dim = 1;
631
632 }
633
634 // We have finished so we can break from the switch.
635 break;
636
637 // Right arrow pressed
638 // Currently not used
639 case 'C':
640
641 // Check if the arrow function is overriden.
642 if( rightArrowOverrideFunc ){
643
644 rightArrowOverrideFunc();
645 break;
646
647 }
648
649 // Check if we can move to right.
650 if( cursor < cmd_buff_cntr ){
651
652 channel -> write( 27 ); // ESC character( decimal 27 )
653 channel -> print( '[' ); // VT100 Cursor command.
654 channel -> print( '1' ); // 1 character movement.
655 channel -> print( 'C' ); // Left.
656
657 // Increment the cursor variavble.
658 cursor++;
659
660 }
661
662 // We just simply reset the state-machine.
663 escape_state = 0;
664
665 // We have finished so we can break from the switch.
666 break;
667
668 // Left arrow pressed
669 // Currently not used
670 case 'D':
671
672 // Check if the arrow function is overriden.
673 if( leftArrowOverrideFunc ){
674
675 leftArrowOverrideFunc();
676 break;
677
678 }
679
680 // Check if we can move to left.
681 if( cursor > 0 ){
682
683 channel -> write( 27 ); // ESC character( decimal 27 )
684 channel -> print( '[' ); // VT100 Cursor command.
685 channel -> print( '1' ); // 1 character movement.
686 channel -> print( 'D' ); // Left.
687
688 // Decrement the cursor variable.
689 cursor--;
690
691 }
692
693 // We just simply reset the state-machine.
694 escape_state = 0;
695
696 // We have finished so we can break from the switch.
697 break;
698
699 // Check for Del key;
700 case '3':
701 escape_state = 3;
702 break;
703
704 // Any other cases means that it was probably a VT100 command but not supported.
705 default:
706
707 // In this case we just simply reset the state-machine.
708 escape_state = 0;
709
710 // We have finished so we can break from the switch.
711 break;
712
713 }
714
715 // We have finished so we can return.
716 return;
717
718 }
719
720 // Detect del key termination.
721 else if ( escape_state == 3 ) {
722
723 if( new_char == '~' ){
724
725 // Del key detected.
726 // If we press a delet key we have to reset cmd_buff_dim to default value
727 cmd_buff_dim = 1;
728
729 // We have to check the number of the characters in the buffer.
730 // If the buffer is full we must not do anything!
731 if ( cursor != cmd_buff_cntr ) {
732
733 for( i = cursor; i < ( cmd_buff_cntr - 1 ); i++ ){
734
735 cmd_buff[ 0 ][ i ] = cmd_buff[ 0 ][ i + 1 ];
736
737 }
738
739 // If there is at least 1 character in the buffer we jus simply
740 // decrement the cmd_buff_cntr. This will result that the new character
741 // will be stored in the previous characters place in the buffer.
742 cmd_buff_cntr--;
743
744 redrawLine();
745
746 }
747
748 }
749
750 escape_state = 0;
751 return;
752
753 }
754
755 // todo Commander command search.
756 else if( new_char == '\t' ){
757
758 // Auto complete section.
759 #ifdef COMMANDER_API_VERSION
760
761 // Firstly, we have to set the cursor to the end of the input command.
762 // If the algorythm fills the missing characters, they have to placed
763 // at the end.
764 cursor = cmd_buff_cntr;
765
766 // Pointer to a Commander API-tree element.
767 Commander::API_t *commandAddress;
768
769 // The next auto filled character will be placed in this variable.
770 char nextChar;
771
772 // This flag holds an auto complete conflict event.
773 // Conflict event happens:
774 // - after the first character of mismatch( restart, reboot will trigger conflict at the third character )
775 // - if cmd_buff_cntr would overflow Commanders command tree.
776 // - if we found the end of the last command.
777 bool conflict = false;
778
779 // If there is no conflict event, we are trying
780 // to fill as many characters as possible.
781 while( !conflict ){
782
783 // Reset the counter to the first Commander API-tree element.
784 i = 0;
785
786 // Get the address of the element indexed by i.
787 // If the indexed elment does not exists, Commander
788 // will return NULL.
789 commandAddress = commander -> operator[]( (int)i );
790
791 // Set to default state.
792 nextChar = '\0';
793
794 // Go through all elements in Commanders API-tree.
795 while( commandAddress ){
796
797 // We have to check that the typed command is exists within an existing command.
798 if( strncmp( (const char*)cmd_buff[ 0 ], commandAddress -> name, cmd_buff_cntr ) == 0 ){
799
800 // If it does, we have to check for conflict.
801 if( ( nextChar == '\0' ) && ( cmd_buff_cntr < COMMANDER_MAX_COMMAND_SIZE ) && ( commandAddress -> name[ cmd_buff_cntr ] != '\0' ) ){
802
803 // If there is no conflict we can set the next character from the command that we found.
804 nextChar = commandAddress -> name[ cmd_buff_cntr ];
805
806 }
807
808 else{
809
810 // We have to check that the next character in the command
811 // tree is not the same as the value in nextChar.
812 if( commandAddress -> name[ cmd_buff_cntr ] != nextChar ){
813
814 // Trigger conflict.
815 conflict = true;
816
817 }
818
819 }
820
821 }
822
823 // Increment i to get the next command's index.
824 i++;
825
826 // Get the address of the element indexed by i.
827 commandAddress = commander -> operator[]( (int)i );
828
829 }
830
831 // If nextChar does not changed since start, that means
832 // we did not found anything similar.
833 if( nextChar == '\0' ){
834
835 // We have to trigger conflict to abort the process.
836 conflict = true;
837
838 }
839
840 // If we does not had a conflict event, we have to process
841 // the foind character as a regular character.
842 if( !conflict ){
843
844 process( nextChar );
845
846 }
847
848 }
849
850
851 #endif
852
853
854 return;
855
856 }
857
858 // Abort key detection.
859 else if( new_char == 0x03 ){
860
861 if( abortKeyFunc ){
862
863 abortKeyFunc();
864
865 }
866
867 // If the abort key is pressed cmd_buff_dim has to be reset to the default value
868 cmd_buff_dim = 1;
869
870 // We send a line break to the terminal to put the next data in new line
871 channel -> print( '\r' );
872 channel -> print( '\n' );
873
874 printBanner();
875
876 cursor = 0;
877 cmd_buff_cntr = 0;
878
879 return;
880
881 }
882
883 // Any other cases means that the new character is just a simple character that was pressed on the keyboard.
884 else {
885
886 // If the cursor is at the end of the command,
887 // we simply store the new character.
888 if( cursor == cmd_buff_cntr ){
889
890 cmd_buff[ 0 ][ cmd_buff_cntr ] = new_char;
891
892 }
893
894 // If the cursor is somewhere in the middle, we have to shift the
895 // end of the command by one character end insert the new character
896 // to the cursor position.
897 else{
898
899 for( i = cmd_buff_cntr; i > cursor; i-- ){
900 cmd_buff[ 0 ][ i ] = cmd_buff[ 0 ][ i - 1 ];
901 }
902
903 // Add the new character to the end of the 0th element in the buffer
904 cmd_buff[ 0 ][ cursor ] = new_char;
905
906 }
907
908 // In this case we have to reset the cmd_buff_dim variable to the default value.
909 cmd_buff_dim = 1;
910
911 // Increment counters.
912 cmd_buff_cntr++;
913 cursor++;
914
915 // Check if the counters are overlowed.
916 if( cmd_buff_cntr >= ( SHELLMINATOR_BUFF_LEN - 1 ) ){
917
918 cmd_buff_cntr = ( SHELLMINATOR_BUFF_LEN - 2 );
919
920 }
921
922 if( cursor >= ( SHELLMINATOR_BUFF_LEN - 1 ) ){
923
924 cursor = ( SHELLMINATOR_BUFF_LEN - 2 );
925
926 }
927
928 // If the cursor was at the end we have to print the
929 // new character if the cmd_buff had free space at
930 // the end.
931 if( cursor == cmd_buff_cntr ){
932
933 if( cmd_buff_cntr < ( SHELLMINATOR_BUFF_LEN - 2 ) ){
934
935 channel -> print( new_char );
936
937 }
938
939 }
940
941 else{
942
943 // Redraw the command line.
944 redrawLine();
945
946 }
947
948
949 // We have finished so we can return.
950 return;
951
952 }
953
954}
955
956void Shellminator::overrideUpArrow( void( *func )( void ) ){
957
958 upArrowOverrideFunc = func;
959
960}
961
962void Shellminator::overrideDownArrow( void( *func )( void ) ){
963
964 downArrowOverrideFunc = func;
965
966}
967
968void Shellminator::overrideLeftArrow( void( *func )( void ) ){
969
970 leftArrowOverrideFunc = func;
971
972}
973
974void Shellminator::overrideRightArrow( void( *func )( void ) ){
975
976 rightArrowOverrideFunc = func;
977
978}
979
980void Shellminator::overrideAbortKey( void( *func )( void ) ){
981
982 abortKeyFunc = func;
983
984}
985
987
988 upArrowOverrideFunc = NULL;
989
990}
991
993
994 downArrowOverrideFunc = NULL;
995
996}
997
999
1000 leftArrowOverrideFunc = NULL;
1001
1002}
1003
1005
1006 rightArrowOverrideFunc = NULL;
1007
1008}
1009
1011
1012 abortKeyFunc = NULL;
1013
1014}
1015
1017
1018 // This variable will hold the character that was read from the channel buffer.
1019 char c;
1020
1021 // todo ESP stuff.
1022
1023 // We have to check the channel buffer. If it is not empty we can read as many characters as possible.
1024 while ( channel -> available() ) {
1025
1026 // Read one character from channel Buffer.
1027 c = (char)channel -> read();
1028
1029 // Process the new character.
1030 process( c );
1031
1032 #ifdef COMMANDER_API_VERSION
1033 commandCheckTimerStart = millis();
1034 commandChecked = false;
1035 #endif
1036
1037 }
1038
1039 #ifdef COMMANDER_API_VERSION
1040
1041 // Command highlight section.
1042 Commander::API_t *commandAddress;
1043
1044 // If Commander is available and we did not checked the
1045 // typed command, we are trying to find and highlight it.
1046 if( commander && !commandChecked ){
1047
1048 // We have to wait 100ms after the last keypress.
1049 if( ( millis() - commandCheckTimerStart ) > 100 ){
1050
1051 // Generic counter variable.
1052 uint32_t i = 0;
1053
1054 // Commander expects a null terminated string, so we
1055 // have to terminate the string at the end, or at
1056 // space character. But after the search we have to
1057 // store bactk this character to it's original state.
1058 char charCopy;
1059
1060 // Find the end of theinput command, or the first space
1061 // character in it, store it's value to charCopy, and
1062 // replace it with null character.
1063 for( i = 0; i <= cmd_buff_cntr; i++ ){
1064
1065 if( ( cmd_buff[ 0 ][ i ] == ' ' ) || ( i == cmd_buff_cntr ) ){
1066
1067 charCopy = cmd_buff[ 0 ][ i ];
1068 cmd_buff[ 0 ][ i ] = '\0';
1069 break;
1070
1071 }
1072
1073 }
1074
1075 // Try to found the command in Commander's API-tree.
1076 commandAddress = commander -> operator[]( cmd_buff[0] );
1077
1078 // If Commander responds with a non-null pointer, it means
1079 // that we have a mach.
1080 if( commandAddress ){
1081
1082 commandFound = true;
1083
1084 }
1085
1086 else{
1087
1088 commandFound = false;
1089
1090 }
1091
1092 // Restore the original state.
1093 cmd_buff[ 0 ][ i ] = charCopy;
1094
1095 // Set the flag.
1096 commandChecked = true;
1097 redrawLine();
1098
1099 }
1100
1101 }
1102
1103 #endif
1104
1105}
1106
1107void Shellminator::setTerminalCharacterColor( uint8_t style, uint8_t color ) {
1108
1109 if( !enableFormatting ){
1110
1111 return;
1112
1113 }
1114
1115 // The reference what I used can be found here: https://www.nayab.xyz/linux/escapecodes.html
1116 channel -> write( 27 );
1117 channel -> print( '[' );
1118 channel -> print( style );
1119 channel -> print( ';' );
1120 channel -> print( color );
1121 channel -> print( 'm' );
1122
1123}
1124
1125void Shellminator::setTerminalCharacterColor( HardwareSerial *serialPort, uint8_t style, uint8_t color ){
1126
1127 // The reference what I used can be found here: https://www.nayab.xyz/linux/escapecodes.html
1128 serialPort -> write( 27 );
1129 serialPort -> print( '[' );
1130 serialPort -> print( style );
1131 serialPort -> print( ';' );
1132 serialPort -> print( color );
1133 serialPort -> print( 'm' );
1134
1135}
1136
1138
1139 // Draws the startup logo to the terminal interface.
1140 channel -> print( logo );
1141
1142}
1143
1144#ifdef COMMANDER_API_VERSION
1145
1146void Shellminator::attachCommander( Commander* commander_p ){
1147
1148 commander = commander_p;
1149
1150}
1151
1152#endif
1153
1154//----- QR-code generator part -----//
1155#ifdef SHELLMINATOR_ENABLE_QR_SUPPORT
1156
1157void Shellminator::generateQRText( char* text, enum qrcodegen_Ecc ecc ){
1158
1159 // The result of the QR-code generation will be stored in this variable.
1160 bool result;
1161
1162 // The actual size of the QR-code will be stored in this variable.
1163 uint32_t qr_size;
1164
1165 // This variable helps to track the x direction while drawing the QR code.
1166 uint32_t x;
1167
1168 // This variable helps to track the y direction while drawing the QR code.
1169 uint32_t y;
1170
1171 // This variable will store the upper pixel while drawing.
1172 bool upper_pixel;
1173
1174 // This variable will store the lower pixel while drawing.
1175 bool lower_pixel;
1176
1177 // Generate the QR-code with default settings, and store the result to
1178 // the result variable.
1179 result = qrcodegen_encodeText( text,
1180 qr_tempBuff,
1181 qr_data,
1182 ecc,
1183 qrcodegen_VERSION_MIN,
1184 qrcodegen_VERSION_MAX,
1185 qrcodegen_Mask_AUTO,
1186 true
1187 );
1188
1189 // We have to check the result variable. If it is true,
1190 // the QR-code generation went well.
1191 if( !result ){
1192
1193 return;
1194
1195 }
1196
1197 // Determinate the size of the QR-code.
1198 qr_size = qrcodegen_getSize( qr_data );
1199
1200 // The unicode character table does not have a rectangular square,
1201 // but it has a half rectangular square on, top, bottom, and a full bar.
1202 // Check these links to make it more clear:
1203 // -Full bar: https://www.fileformat.info/info/unicode/char/2588/index.htm
1204 // -Upper square: https://www.fileformat.info/info/unicode/char/2580/index.htm
1205 // -lower square: https://www.fileformat.info/info/unicode/char/2584/index.htm
1206 // To draw a QR-code with a terminal emulator the easyest way to combine these
1207 // unicode characters. Because it is two 'pixels' high, we have to step the y
1208 // variable by two lines.
1209 for( y = 0; y < ( qr_size / 2 ); y++ ){
1210
1211 // Print a new line at the begining of the horizontal drawing.
1212 channel -> println();
1213
1214 // Draw all horizontal 'pixels'.
1215 for( x = 0; x < qr_size; x++ ){
1216
1217 // Determinate the upper pixel value.
1218 upper_pixel = qrcodegen_getModule( qr_data, x, ( y * 2 ) );
1219
1220 // Determinate the lower pixel value.
1221 lower_pixel = qrcodegen_getModule( qr_data, x, ( ( y * 2 ) + 1 ) );
1222
1223 // Draw the right pattern accordingly.
1224
1225 // Both pixels are black.
1226 if( upper_pixel && lower_pixel ){
1227
1228 channel -> print( "\u2588" );
1229 continue;
1230
1231 }
1232
1233 // Upper pixel is black.
1234 if( upper_pixel && ( !lower_pixel ) ){
1235
1236 channel -> print( "\u2580" );
1237 continue;
1238
1239 }
1240
1241 // Lower pixel is black.
1242 if( ( !upper_pixel ) && lower_pixel ){
1243
1244 channel -> print( "\u2584" );
1245 continue;
1246
1247 }
1248
1249 // If we get here we have to draw an empty bar.
1250 // The space character will do the job.
1251 channel -> print( " " );
1252
1253 }
1254
1255 }
1256
1257 // If the QR code size is not even, we have to draw
1258 // the last line as well.
1259 if( ( qr_size % 2 ) != 0 ){
1260
1261 // Print a new line at the begining of the horizontal drawing.
1262 channel -> println();
1263
1264 // Draw all horizontal 'pixels'.
1265 for( x = 0; x < qr_size; x++ ){
1266
1267
1268 // Determinate the pixel value. We store it to upper_pixel variable.
1269 upper_pixel = qrcodegen_getModule( qr_data, x, ( qr_size - 1 ) );
1270
1271 // Check if we have to draw.
1272 if( upper_pixel ){
1273
1274 channel -> print( "\u2580" );
1275 continue;
1276
1277 }
1278
1279 // If we get here we have to draw an empty bar.
1280 // The space character will do the job.
1281 channel -> print( " " );
1282
1283 }
1284
1285 }
1286
1287 // Finally create a new line.
1288 channel -> println();
1289
1290}
1291
1292void Shellminator::generateQRText( const char* text, enum qrcodegen_Ecc ecc ){
1293
1294 generateQRText( (char*)text, ecc );
1295
1296}
1297
1298void Shellminator::generateQRText( const char* text ){
1299
1300 generateQRText( (char*)text, qrcodegen_Ecc_MEDIUM );
1301
1302}
1303
1304void Shellminator::generateQRText( char* text ){
1305
1306 generateQRText( text, qrcodegen_Ecc_MEDIUM );
1307
1308}
1309
1310#endif
#define SHELLMINATOR_BUFF_LEN
+---— Costum configuration ---—+ | | | This is where you have to config | | your defines!...
#define SHELLMINATOR_VERSION
Version of the module.
#define SHELLMINATOR_BANNER_LEN
Maximum length of the banner text.
#define SHELLMINATOR_LOGO_COLOR
#define SHELLMINATOR_BUFF_DIM
Definition of the maximum length of the previous command memory.
#define SHELLMINATOR_LOGO_FONT_STYLE
Color and style of the startup logo.
void begin(char *banner_p)
Shellminator initialization function.
void clear()
Clear screen.
void freeLeftArrow()
Reset left arrow key functionality to default.
void overrideUpArrow(void(*func)(void))
Override up arrow key behaviour.
void update()
Update function.
void addExecFunc(void(*execution_fn_p)(char *))
Execution function adder function.
bool enableFormatting
This flag enables or disables character formatting.
void overrideRightArrow(void(*func)(void))
Override right arrow key behaviour.
void freeAbortKey()
Reset abort key functionality to default.
void sendBackspace()
Sends a backspace.
void freeUpArrow()
Reset up arrow key functionality to default.
void overrideDownArrow(void(*func)(void))
Override down arrow key behaviour.
Shellminator(HardwareSerial *serialPort_p)
Shellminator Constructor.
void overrideAbortKey(void(*func)(void))
Override abort key behaviour.
void attachLogo(char *logo_p)
This function attaches a logo to the terminal.
void overrideLeftArrow(void(*func)(void))
Override left arrow key behaviour.
void setTerminalCharacterColor(uint8_t style, uint8_t color)
Bring some color into your code.
void freeRightArrow()
Reset right arrow key functionality to default.
void drawLogo()
Draws the startup logo.
void freeDownArrow()
Reset down arrow key functionality to default.
static const char * version
String that holds the version information.
void select(Serial_ *serialPort_p)
Select Serial Port.
Serial_ * getSerialObject()
Get the address of the chosen Serial Port.
void select(HardwareSerial *serialPort_p)
Select Serial Port.
HardwareSerial * getSerialObject()
Get the address of the chosen Serial Port.
void select(WiFiClient *client_p)
Select WiFi Client.
WiFiClient * getClientObject()
Get the address of the chosen WiFi Client.