]> git.donarmstrong.com Git - kiibohd-controller.git/blob - Debug/cli/cli.c
HUGE AVR RAM optimization (~28%).
[kiibohd-controller.git] / Debug / cli / cli.c
1 /* Copyright (C) 2014 by Jacob Alexander
2  *
3  * Permission is hereby granted, free of charge, to any person obtaining a copy
4  * of this software and associated documentation files (the "Software"), to deal
5  * in the Software without restriction, including without limitation the rights
6  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7  * copies of the Software, and to permit persons to whom the Software is
8  * furnished to do so, subject to the following conditions:
9  *
10  * The above copyright notice and this permission notice shall be included in
11  * all copies or substantial portions of the Software.
12  *
13  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19  * THE SOFTWARE.
20  */
21
22 // ----- Includes -----
23
24 // Compiler Includes
25 //#include <stdarg.h>
26
27 // Project Includes
28 #include <buildvars.h>
29 #include "cli.h"
30 #include <led.h>
31 #include <print.h>
32
33
34
35 // ----- Variables -----
36
37 // Basic command dictionary
38 CLIDict_Entry( cliDebug, "Enables/Disables hex output of the most recent cli input." );
39 CLIDict_Entry( help,     "You're looking at it :P" );
40 CLIDict_Entry( led,      "Enables/Disables indicator LED. Try a couple times just in case the LED is in an odd state.\r\n\t\t\033[33mWarning\033[0m: May adversely affect some modules..." );
41 CLIDict_Entry( reload,   "Signals microcontroller to reflash/reload." );
42 CLIDict_Entry( reset,    "Resets the terminal back to initial settings." );
43 CLIDict_Entry( restart,  "Sends a software restart, should be similar to powering on the device." );
44 CLIDict_Entry( version,  "Version information about this firmware." );
45
46 CLIDict_Def( basicCLIDict, "General Commands" ) = {
47         CLIDict_Item( cliDebug ),
48         CLIDict_Item( help ),
49         CLIDict_Item( led ),
50         CLIDict_Item( reload ),
51         CLIDict_Item( reset ),
52         CLIDict_Item( restart ),
53         CLIDict_Item( version ),
54         { 0, 0, 0 } // Null entry for dictionary end
55 };
56
57
58
59 // ----- Functions -----
60
61 inline void prompt()
62 {
63         print("\033[2K\r"); // Erases the current line and resets cursor to beginning of line
64         print("\033[1;34m:\033[0m "); // Blue bold prompt
65 }
66
67 // Initialize the CLI
68 inline void CLI_init()
69 {
70         // Reset the Line Buffer
71         CLILineBufferCurrent = 0;
72
73         // Set prompt
74         prompt();
75
76         // Register first dictionary
77         CLIDictionariesUsed = 0;
78         CLI_registerDictionary( basicCLIDict, basicCLIDictName );
79
80         // Initialize main LED
81         init_errorLED();
82         CLILEDState = 0;
83
84         // Hex debug mode is off by default
85         CLIHexDebugMode = 0;
86 }
87
88 // Query the serial input buffer for any new characters
89 void CLI_process()
90 {
91         // Current buffer position
92         uint8_t prev_buf_pos = CLILineBufferCurrent;
93
94         // Process each character while available
95         while ( 1 )
96         {
97                 // No more characters to process
98                 if ( Output_availablechar() == 0 )
99                         break;
100
101                 // Retrieve from output module
102                 char cur_char = (char)Output_getchar();
103
104                 // Make sure buffer isn't full
105                 if ( CLILineBufferCurrent >= CLILineBufferMaxSize )
106                 {
107                         print( NL );
108                         erro_print("Serial line buffer is full, dropping character and resetting...");
109
110                         // Clear buffer
111                         CLILineBufferCurrent = 0;
112
113                         // Reset the prompt
114                         prompt();
115
116                         return;
117                 }
118
119                 // Place into line buffer
120                 CLILineBuffer[CLILineBufferCurrent++] = cur_char;
121         }
122
123         // Display Hex Key Input if enabled
124         if ( CLIHexDebugMode && CLILineBufferCurrent > prev_buf_pos )
125         {
126                 print("\033[s\r\n"); // Save cursor position, and move to the next line
127                 print("\033[2K");    // Erases the current line
128
129                 uint8_t pos = prev_buf_pos;
130                 while ( CLILineBufferCurrent > pos )
131                 {
132                         printHex( CLILineBuffer[pos++] );
133                         print(" ");
134                 }
135
136                 print("\033[u"); // Restore cursor position
137         }
138
139         // If buffer has changed, output to screen while there are still characters in the buffer not displayed
140         while ( CLILineBufferCurrent > prev_buf_pos )
141         {
142                 // Check for control characters
143                 switch ( CLILineBuffer[prev_buf_pos] )
144                 {
145                 case 0x0D: // Enter
146                         CLILineBuffer[CLILineBufferCurrent - 1] = ' '; // Replace Enter with a space (resolves a bug in args)
147
148                         // Remove the space if there is no command
149                         if ( CLILineBufferCurrent == 1 )
150                                 CLILineBufferCurrent--;
151
152                         // Process the current line buffer
153                         CLI_commandLookup();
154
155                         // Reset the buffer
156                         CLILineBufferCurrent = 0;
157
158                         // Reset the prompt after processing has finished
159                         print( NL );
160                         prompt();
161
162                         // XXX There is a potential bug here when resetting the buffer (losing valid keypresses)
163                         //     Doesn't look like it will happen *that* often, so not handling it for now -HaaTa
164                         return;
165
166                 case 0x09: // Tab
167                         // Tab completion for the current command
168                         CLI_tabCompletion();
169
170                         CLILineBufferCurrent--; // Remove the Tab
171
172                         // XXX There is a potential bug here when resetting the buffer (losing valid keypresses)
173                         //     Doesn't look like it will happen *that* often, so not handling it for now -HaaTa
174                         return;
175
176                 case 0x1B: // Esc
177                         // Check for escape sequence
178                         // TODO
179                         return;
180
181                 case 0x08:
182                 case 0x7F: // Backspace
183                         // TODO - Does not handle case for arrow editing (arrows disabled atm)
184                         CLILineBufferCurrent--; // Remove the backspace
185
186                         // If there are characters in the buffer
187                         if ( CLILineBufferCurrent > 0 )
188                         {
189                                 // Remove character from current position in the line buffer
190                                 CLILineBufferCurrent--;
191
192                                 // Remove character from tty
193                                 print("\b \b");
194                         }
195
196                         break;
197
198                 default:
199                         // Place a null on the end (to use with string print)
200                         CLILineBuffer[CLILineBufferCurrent] = '\0';
201
202                         // Output buffer to screen
203                         dPrint( &CLILineBuffer[prev_buf_pos] );
204
205                         // Buffer reset
206                         prev_buf_pos++;
207
208                         break;
209                 }
210         }
211 }
212
213 // Takes a string, returns two pointers
214 //  One to the first non-space character
215 //  The second to the next argument (first NULL if there isn't an argument). delimited by a space
216 //  Places a NULL at the first space after the first argument
217 void CLI_argumentIsolation( char* string, char** first, char** second )
218 {
219         // Mark out the first argument
220         // This is done by finding the first space after a list of non-spaces and setting it NULL
221         char* cmdPtr = string - 1;
222         while ( *++cmdPtr == ' ' ); // Skips leading spaces, and points to first character of cmd
223
224         // Locates first space delimiter
225         char* argPtr = cmdPtr + 1;
226         while ( *argPtr != ' ' && *argPtr != '\0' )
227                 argPtr++;
228
229         // Point to the first character of args or a NULL (no args) and set the space delimiter as a NULL
230         (++argPtr)[-1] = '\0';
231
232         // Set return variables
233         *first = cmdPtr;
234         *second = argPtr;
235 }
236
237 // Scans the CLILineBuffer for any valid commands
238 void CLI_commandLookup()
239 {
240         // Ignore command if buffer is 0 length
241         if ( CLILineBufferCurrent == 0 )
242                 return;
243
244         // Set the last+1 character of the buffer to NULL for string processing
245         CLILineBuffer[CLILineBufferCurrent] = '\0';
246
247         // Retrieve pointers to command and beginning of arguments
248         // Places a NULL at the first space after the command
249         char* cmdPtr;
250         char* argPtr;
251         CLI_argumentIsolation( CLILineBuffer, &cmdPtr, &argPtr );
252
253         // Scan array of dictionaries for a valid command match
254         for ( uint8_t dict = 0; dict < CLIDictionariesUsed; dict++ )
255         {
256                 // Parse each cmd until a null command entry is found, or an argument match
257                 for ( uint8_t cmd = 0; CLIDict[dict][cmd].name != 0; cmd++ )
258                 {
259                         // Compare the first argument and each command entry
260                         if ( eqStr( cmdPtr, (char*)CLIDict[dict][cmd].name ) == -1 )
261                         {
262                                 // Run the specified command function pointer
263                                 //   argPtr is already pointing at the first character of the arguments
264                                 (*(void (*)(char*))CLIDict[dict][cmd].function)( argPtr );
265
266                                 return;
267                         }
268                 }
269         }
270
271         // No match for the command...
272         print( NL );
273         erro_dPrint("\"", CLILineBuffer, "\" is not a valid command...type \033[35mhelp\033[0m");
274 }
275
276 // Registers a command dictionary with the CLI
277 void CLI_registerDictionary( const CLIDictItem *cmdDict, const char* dictName )
278 {
279         // Make sure this max limit of dictionaries hasn't been reached
280         if ( CLIDictionariesUsed >= CLIMaxDictionaries )
281         {
282                 erro_print("Max number of dictionaries defined already...");
283                 return;
284         }
285
286         // Add dictionary
287         CLIDictNames[CLIDictionariesUsed] = (char*)dictName;
288         CLIDict[CLIDictionariesUsed++] = (CLIDictItem*)cmdDict;
289 }
290
291 inline void CLI_tabCompletion()
292 {
293         // Ignore command if buffer is 0 length
294         if ( CLILineBufferCurrent == 0 )
295                 return;
296
297         // Set the last+1 character of the buffer to NULL for string processing
298         CLILineBuffer[CLILineBufferCurrent] = '\0';
299
300         // Retrieve pointers to command and beginning of arguments
301         // Places a NULL at the first space after the command
302         char* cmdPtr;
303         char* argPtr;
304         CLI_argumentIsolation( CLILineBuffer, &cmdPtr, &argPtr );
305
306         // Tab match pointer
307         char* tabMatch = 0;
308         uint8_t matches = 0;
309
310         // Scan array of dictionaries for a valid command match
311         for ( uint8_t dict = 0; dict < CLIDictionariesUsed; dict++ )
312         {
313                 // Parse each cmd until a null command entry is found, or an argument match
314                 for ( uint8_t cmd = 0; CLIDict[dict][cmd].name != 0; cmd++ )
315                 {
316                         // Compare the first argument piece to each command entry to see if it is "like"
317                         // NOTE: To save on processing, we only care about the commands and ignore the arguments
318                         //       If there are arguments, and a valid tab match is found, buffer is cleared (args lost)
319                         //       Also ignores full matches
320                         if ( eqStr( cmdPtr, (char*)CLIDict[dict][cmd].name ) == 0 )
321                         {
322                                 // TODO Make list of commands if multiple matches
323                                 matches++;
324                                 tabMatch = (char*)CLIDict[dict][cmd].name;
325                         }
326                 }
327         }
328
329         // Only tab complete if there was 1 match
330         if ( matches == 1 )
331         {
332                 // Reset the buffer
333                 CLILineBufferCurrent = 0;
334
335                 // Reprint the prompt (automatically clears the line)
336                 prompt();
337
338                 // Display the command
339                 dPrint( tabMatch );
340
341                 // There are no index counts, so just copy the whole string to the input buffer
342                 while ( *tabMatch != '\0' )
343                 {
344                         CLILineBuffer[CLILineBufferCurrent++] = *tabMatch++;
345                 }
346         }
347 }
348
349
350
351 // ----- CLI Command Functions -----
352
353 void cliFunc_cliDebug( char* args )
354 {
355         // Toggle Hex Debug Mode
356         if ( CLIHexDebugMode )
357         {
358                 print( NL );
359                 info_print("Hex debug mode disabled...");
360                 CLIHexDebugMode = 0;
361         }
362         else
363         {
364                 print( NL );
365                 info_print("Hex debug mode enabled...");
366                 CLIHexDebugMode = 1;
367         }
368 }
369
370 void cliFunc_help( char* args )
371 {
372         // Scan array of dictionaries and print every description
373         //  (no alphabetical here, too much processing/memory to sort...)
374         for ( uint8_t dict = 0; dict < CLIDictionariesUsed; dict++ )
375         {
376                 // Print the name of each dictionary as a title
377                 print( NL "\033[1;32m" );
378                 _print( CLIDictNames[dict] ); // This print is requride by AVR (flash)
379                 print( "\033[0m" NL );
380
381                 // Parse each cmd/description until a null command entry is found
382                 for ( uint8_t cmd = 0; CLIDict[dict][cmd].name != 0; cmd++ )
383                 {
384                         dPrintStrs(" \033[35m", CLIDict[dict][cmd].name, "\033[0m");
385
386                         // Determine number of spaces to tab by the length of the command and TabAlign
387                         uint8_t padLength = CLIEntryTabAlign - lenStr( (char*)CLIDict[dict][cmd].name );
388                         while ( padLength-- > 0 )
389                                 print(" ");
390
391                         _print( CLIDict[dict][cmd].description ); // This print is required by AVR (flash)
392                         print( NL );
393                 }
394         }
395 }
396
397 void cliFunc_led( char* args )
398 {
399         CLILEDState ^= 1 << 1; // Toggle between 0 and 1
400         errorLED( CLILEDState ); // Enable/Disable error LED
401 }
402
403 void cliFunc_reload( char* args )
404 {
405         // Request to output module to be set into firmware reload mode
406         Output_firmwareReload();
407 }
408
409 void cliFunc_reset( char* args )
410 {
411         print("\033c"); // Resets the terminal
412 }
413
414 void cliFunc_restart( char* args )
415 {
416         // Trigger an overall software reset
417         Output_softReset();
418 }
419
420 void cliFunc_version( char* args )
421 {
422         print( NL );
423         print( " \033[1mRevision:\033[0m      " CLI_Revision       NL );
424         print( " \033[1mBranch:\033[0m        " CLI_Branch         NL );
425         print( " \033[1mTree Status:\033[0m   " CLI_ModifiedStatus NL );
426         print( " \033[1mRepo Origin:\033[0m   " CLI_RepoOrigin     NL );
427         print( " \033[1mCommit Date:\033[0m   " CLI_CommitDate     NL );
428         print( " \033[1mCommit Author:\033[0m " CLI_CommitAuthor   NL );
429         print( " \033[1mBuild Date:\033[0m    " CLI_BuildDate      NL );
430         print( " \033[1mBuild OS:\033[0m      " CLI_BuildOS        NL );
431         print( " \033[1mArchitecture:\033[0m  " CLI_Arch           NL );
432         print( " \033[1mChip:\033[0m          " CLI_Chip           NL );
433         print( " \033[1mCPU:\033[0m           " CLI_CPU            NL );
434         print( " \033[1mDevice:\033[0m        " CLI_Device         NL );
435         print( " \033[1mModules:\033[0m       " CLI_Modules        NL );
436 }
437