Kiibohd Controller
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. /* Copyright (C) 2014-2015 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. // ----- Includes -----
  22. // Compiler Includes
  23. //#include <stdarg.h>
  24. // Project Includes
  25. #include <buildvars.h>
  26. #include "cli.h"
  27. #include <led.h>
  28. #include <print.h>
  29. // ----- Variables -----
  30. // Basic command dictionary
  31. CLIDict_Entry( clear, "Clear the screen.");
  32. CLIDict_Entry( cliDebug, "Enables/Disables hex output of the most recent cli input." );
  33. CLIDict_Entry( help, "You're looking at it :P" );
  34. 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..." );
  35. CLIDict_Entry( reload, "Signals microcontroller to reflash/reload." );
  36. CLIDict_Entry( reset, "Resets the terminal back to initial settings." );
  37. CLIDict_Entry( restart, "Sends a software restart, should be similar to powering on the device." );
  38. CLIDict_Entry( version, "Version information about this firmware." );
  39. CLIDict_Def( basicCLIDict, "General Commands" ) = {
  40. CLIDict_Item( clear ),
  41. CLIDict_Item( cliDebug ),
  42. CLIDict_Item( help ),
  43. CLIDict_Item( led ),
  44. CLIDict_Item( reload ),
  45. CLIDict_Item( reset ),
  46. CLIDict_Item( restart ),
  47. CLIDict_Item( version ),
  48. { 0, 0, 0 } // Null entry for dictionary end
  49. };
  50. // ----- Functions -----
  51. inline void prompt()
  52. {
  53. print("\033[2K\r"); // Erases the current line and resets cursor to beginning of line
  54. print("\033[1;34m:\033[0m "); // Blue bold prompt
  55. }
  56. // Initialize the CLI
  57. inline void CLI_init()
  58. {
  59. // Reset the Line Buffer
  60. CLILineBufferCurrent = 0;
  61. // History starts empty
  62. CLIHistoryHead = 0;
  63. CLIHistoryCurrent = 0;
  64. CLIHistoryTail = 0;
  65. // Set prompt
  66. prompt();
  67. // Register first dictionary
  68. CLIDictionariesUsed = 0;
  69. CLI_registerDictionary( basicCLIDict, basicCLIDictName );
  70. // Initialize main LED
  71. init_errorLED();
  72. CLILEDState = 0;
  73. // Hex debug mode is off by default
  74. CLIHexDebugMode = 0;
  75. }
  76. // Query the serial input buffer for any new characters
  77. void CLI_process()
  78. {
  79. // Current buffer position
  80. uint8_t prev_buf_pos = CLILineBufferCurrent;
  81. // Process each character while available
  82. while ( 1 )
  83. {
  84. // No more characters to process
  85. if ( Output_availablechar() == 0 )
  86. break;
  87. // Retrieve from output module
  88. char cur_char = (char)Output_getchar();
  89. // Make sure buffer isn't full
  90. if ( CLILineBufferCurrent >= CLILineBufferMaxSize )
  91. {
  92. print( NL );
  93. erro_print("Serial line buffer is full, dropping character and resetting...");
  94. // Clear buffer
  95. CLILineBufferCurrent = 0;
  96. // Reset the prompt
  97. prompt();
  98. return;
  99. }
  100. // Place into line buffer
  101. CLILineBuffer[CLILineBufferCurrent++] = cur_char;
  102. }
  103. // Display Hex Key Input if enabled
  104. if ( CLIHexDebugMode && CLILineBufferCurrent > prev_buf_pos )
  105. {
  106. print("\033[s\r\n"); // Save cursor position, and move to the next line
  107. print("\033[2K"); // Erases the current line
  108. uint8_t pos = prev_buf_pos;
  109. while ( CLILineBufferCurrent > pos )
  110. {
  111. printHex( CLILineBuffer[pos++] );
  112. print(" ");
  113. }
  114. print("\033[u"); // Restore cursor position
  115. }
  116. // If buffer has changed, output to screen while there are still characters in the buffer not displayed
  117. while ( CLILineBufferCurrent > prev_buf_pos )
  118. {
  119. // Check for control characters
  120. switch ( CLILineBuffer[prev_buf_pos] )
  121. {
  122. // Enter
  123. case 0x0A: // LF
  124. case 0x0D: // CR
  125. CLILineBuffer[CLILineBufferCurrent - 1] = ' '; // Replace Enter with a space (resolves a bug in args)
  126. // Remove the space if there is no command
  127. if ( CLILineBufferCurrent == 1 )
  128. {
  129. CLILineBufferCurrent--;
  130. }
  131. else
  132. {
  133. // Only do command-related stuff if there was actually a command
  134. // Avoids clogging command history with blanks
  135. // Process the current line buffer
  136. CLI_commandLookup();
  137. // Add the command to the history
  138. CLI_saveHistory( CLILineBuffer );
  139. // Keep the array circular, discarding the older entries
  140. if ( CLIHistoryTail < CLIHistoryHead )
  141. CLIHistoryHead = ( CLIHistoryHead + 1 ) % CLIMaxHistorySize;
  142. CLIHistoryTail++;
  143. if ( CLIHistoryTail == CLIMaxHistorySize )
  144. {
  145. CLIHistoryTail = 0;
  146. CLIHistoryHead = 1;
  147. }
  148. CLIHistoryCurrent = CLIHistoryTail; // 'Up' starts at the last item
  149. CLI_saveHistory( NULL ); // delete the old temp buffer
  150. }
  151. // Reset the buffer
  152. CLILineBufferCurrent = 0;
  153. // Reset the prompt after processing has finished
  154. print( NL );
  155. prompt();
  156. // XXX There is a potential bug here when resetting the buffer (losing valid keypresses)
  157. // Doesn't look like it will happen *that* often, so not handling it for now -HaaTa
  158. return;
  159. case 0x09: // Tab
  160. // Tab completion for the current command
  161. CLI_tabCompletion();
  162. CLILineBufferCurrent--; // Remove the Tab
  163. // XXX There is a potential bug here when resetting the buffer (losing valid keypresses)
  164. // Doesn't look like it will happen *that* often, so not handling it for now -HaaTa
  165. return;
  166. case 0x1B: // Esc / Escape codes
  167. // Check for other escape sequence
  168. // \e[ is an escape code in vt100 compatible terminals
  169. if ( CLILineBufferCurrent >= prev_buf_pos + 3
  170. && CLILineBuffer[ prev_buf_pos ] == 0x1B
  171. && CLILineBuffer[ prev_buf_pos + 1] == 0x5B )
  172. {
  173. // Arrow Keys: A (0x41) = Up, B (0x42) = Down, C (0x43) = Right, D (0x44) = Left
  174. if ( CLILineBuffer[ prev_buf_pos + 2 ] == 0x41 ) // Hist prev
  175. {
  176. if ( CLIHistoryCurrent == CLIHistoryTail )
  177. {
  178. // Is first time pressing arrow. Save the current buffer
  179. CLILineBuffer[ prev_buf_pos ] = '\0';
  180. CLI_saveHistory( CLILineBuffer );
  181. }
  182. // Grab the previus item from the history if there is one
  183. if ( RING_PREV( CLIHistoryCurrent ) != RING_PREV( CLIHistoryHead ) )
  184. CLIHistoryCurrent = RING_PREV( CLIHistoryCurrent );
  185. CLI_retreiveHistory( CLIHistoryCurrent );
  186. }
  187. if ( CLILineBuffer[ prev_buf_pos + 2 ] == 0x42 ) // Hist next
  188. {
  189. // Grab the next item from the history if it exists
  190. if ( RING_NEXT( CLIHistoryCurrent ) != RING_NEXT( CLIHistoryTail ) )
  191. CLIHistoryCurrent = RING_NEXT( CLIHistoryCurrent );
  192. CLI_retreiveHistory( CLIHistoryCurrent );
  193. }
  194. }
  195. return;
  196. case 0x08:
  197. case 0x7F: // Backspace
  198. // TODO - Does not handle case for arrow editing (arrows disabled atm)
  199. CLILineBufferCurrent--; // Remove the backspace
  200. // If there are characters in the buffer
  201. if ( CLILineBufferCurrent > 0 )
  202. {
  203. // Remove character from current position in the line buffer
  204. CLILineBufferCurrent--;
  205. // Remove character from tty
  206. print("\b \b");
  207. }
  208. break;
  209. default:
  210. // Place a null on the end (to use with string print)
  211. CLILineBuffer[CLILineBufferCurrent] = '\0';
  212. // Output buffer to screen
  213. dPrint( &CLILineBuffer[prev_buf_pos] );
  214. // Buffer reset
  215. prev_buf_pos++;
  216. break;
  217. }
  218. }
  219. }
  220. // Takes a string, returns two pointers
  221. // One to the first non-space character
  222. // The second to the next argument (first NULL if there isn't an argument). delimited by a space
  223. // Places a NULL at the first space after the first argument
  224. void CLI_argumentIsolation( char* string, char** first, char** second )
  225. {
  226. // Mark out the first argument
  227. // This is done by finding the first space after a list of non-spaces and setting it NULL
  228. char* cmdPtr = string - 1;
  229. while ( *++cmdPtr == ' ' ); // Skips leading spaces, and points to first character of cmd
  230. // Locates first space delimiter
  231. char* argPtr = cmdPtr + 1;
  232. while ( *argPtr != ' ' && *argPtr != '\0' )
  233. argPtr++;
  234. // Point to the first character of args or a NULL (no args) and set the space delimiter as a NULL
  235. (++argPtr)[-1] = '\0';
  236. // Set return variables
  237. *first = cmdPtr;
  238. *second = argPtr;
  239. }
  240. // Scans the CLILineBuffer for any valid commands
  241. void CLI_commandLookup()
  242. {
  243. // Ignore command if buffer is 0 length
  244. if ( CLILineBufferCurrent == 0 )
  245. return;
  246. // Set the last+1 character of the buffer to NULL for string processing
  247. CLILineBuffer[CLILineBufferCurrent] = '\0';
  248. // Retrieve pointers to command and beginning of arguments
  249. // Places a NULL at the first space after the command
  250. char* cmdPtr;
  251. char* argPtr;
  252. CLI_argumentIsolation( CLILineBuffer, &cmdPtr, &argPtr );
  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. return;
  266. }
  267. }
  268. }
  269. // No match for the command...
  270. print( NL );
  271. erro_dPrint("\"", CLILineBuffer, "\" is not a valid command...type \033[35mhelp\033[0m");
  272. }
  273. // Registers a command dictionary with the CLI
  274. void CLI_registerDictionary( const CLIDictItem *cmdDict, const char* dictName )
  275. {
  276. // Make sure this max limit of dictionaries hasn't been reached
  277. if ( CLIDictionariesUsed >= CLIMaxDictionaries )
  278. {
  279. erro_print("Max number of dictionaries defined already...");
  280. return;
  281. }
  282. // Add dictionary
  283. CLIDictNames[CLIDictionariesUsed] = (char*)dictName;
  284. CLIDict[CLIDictionariesUsed++] = (CLIDictItem*)cmdDict;
  285. }
  286. inline void CLI_tabCompletion()
  287. {
  288. // Ignore command if buffer is 0 length
  289. if ( CLILineBufferCurrent == 0 )
  290. return;
  291. // Set the last+1 character of the buffer to NULL for string processing
  292. CLILineBuffer[CLILineBufferCurrent] = '\0';
  293. // Retrieve pointers to command and beginning of arguments
  294. // Places a NULL at the first space after the command
  295. char* cmdPtr;
  296. char* argPtr;
  297. CLI_argumentIsolation( CLILineBuffer, &cmdPtr, &argPtr );
  298. // Tab match pointer
  299. char* tabMatch = 0;
  300. uint8_t matches = 0;
  301. // Scan array of dictionaries for a valid command match
  302. for ( uint8_t dict = 0; dict < CLIDictionariesUsed; dict++ )
  303. {
  304. // Parse each cmd until a null command entry is found, or an argument match
  305. for ( uint8_t cmd = 0; CLIDict[dict][cmd].name != 0; cmd++ )
  306. {
  307. // Compare the first argument piece to each command entry to see if it is "like"
  308. // NOTE: To save on processing, we only care about the commands and ignore the arguments
  309. // If there are arguments, and a valid tab match is found, buffer is cleared (args lost)
  310. // Also ignores full matches
  311. if ( eqStr( cmdPtr, (char*)CLIDict[dict][cmd].name ) == 0 )
  312. {
  313. // TODO Make list of commands if multiple matches
  314. matches++;
  315. tabMatch = (char*)CLIDict[dict][cmd].name;
  316. }
  317. }
  318. }
  319. // Only tab complete if there was 1 match
  320. if ( matches == 1 )
  321. {
  322. // Reset the buffer
  323. CLILineBufferCurrent = 0;
  324. // Reprint the prompt (automatically clears the line)
  325. prompt();
  326. // Display the command
  327. dPrint( tabMatch );
  328. // There are no index counts, so just copy the whole string to the input buffer
  329. while ( *tabMatch != '\0' )
  330. {
  331. CLILineBuffer[CLILineBufferCurrent++] = *tabMatch++;
  332. }
  333. }
  334. }
  335. inline int CLI_wrap( int kX, int const kLowerBound, int const kUpperBound )
  336. {
  337. int range_size = kUpperBound - kLowerBound + 1;
  338. if ( kX < kLowerBound )
  339. kX += range_size * ((kLowerBound - kX) / range_size + 1);
  340. return kLowerBound + (kX - kLowerBound) % range_size;
  341. }
  342. inline void CLI_saveHistory( char *buff )
  343. {
  344. if ( buff == NULL )
  345. {
  346. //clear the item
  347. CLIHistoryBuffer[ CLIHistoryTail ][ 0 ] = '\0';
  348. return;
  349. }
  350. // Copy the line to the history
  351. int i;
  352. for (i = 0; i < CLILineBufferCurrent; i++)
  353. {
  354. CLIHistoryBuffer[ CLIHistoryTail ][ i ] = CLILineBuffer[ i ];
  355. }
  356. }
  357. void CLI_retreiveHistory( int index )
  358. {
  359. char *histMatch = CLIHistoryBuffer[ index ];
  360. // Reset the buffer
  361. CLILineBufferCurrent = 0;
  362. // Reprint the prompt (automatically clears the line)
  363. prompt();
  364. // Display the command
  365. dPrint( histMatch );
  366. // There are no index counts, so just copy the whole string to the input buffe
  367. CLILineBufferCurrent = 0;
  368. while ( *histMatch != '\0' )
  369. {
  370. CLILineBuffer[ CLILineBufferCurrent++ ] = *histMatch++;
  371. }
  372. }
  373. // ----- CLI Command Functions -----
  374. void cliFunc_clear( char* args)
  375. {
  376. print("\033[2J\033[H\r"); // Erases the whole screen
  377. }
  378. void cliFunc_cliDebug( char* args )
  379. {
  380. // Toggle Hex Debug Mode
  381. if ( CLIHexDebugMode )
  382. {
  383. print( NL );
  384. info_print("Hex debug mode disabled...");
  385. CLIHexDebugMode = 0;
  386. }
  387. else
  388. {
  389. print( NL );
  390. info_print("Hex debug mode enabled...");
  391. CLIHexDebugMode = 1;
  392. }
  393. }
  394. void cliFunc_help( char* args )
  395. {
  396. // Scan array of dictionaries and print every description
  397. // (no alphabetical here, too much processing/memory to sort...)
  398. for ( uint8_t dict = 0; dict < CLIDictionariesUsed; dict++ )
  399. {
  400. // Print the name of each dictionary as a title
  401. print( NL "\033[1;32m" );
  402. _print( CLIDictNames[dict] ); // This print is requride by AVR (flash)
  403. print( "\033[0m" NL );
  404. // Parse each cmd/description until a null command entry is found
  405. for ( uint8_t cmd = 0; CLIDict[dict][cmd].name != 0; cmd++ )
  406. {
  407. dPrintStrs(" \033[35m", CLIDict[dict][cmd].name, "\033[0m");
  408. // Determine number of spaces to tab by the length of the command and TabAlign
  409. uint8_t padLength = CLIEntryTabAlign - lenStr( (char*)CLIDict[dict][cmd].name );
  410. while ( padLength-- > 0 )
  411. print(" ");
  412. _print( CLIDict[dict][cmd].description ); // This print is required by AVR (flash)
  413. print( NL );
  414. }
  415. }
  416. }
  417. void cliFunc_led( char* args )
  418. {
  419. CLILEDState ^= 1 << 1; // Toggle between 0 and 1
  420. errorLED( CLILEDState ); // Enable/Disable error LED
  421. }
  422. void cliFunc_reload( char* args )
  423. {
  424. // Request to output module to be set into firmware reload mode
  425. Output_firmwareReload();
  426. }
  427. void cliFunc_reset( char* args )
  428. {
  429. print("\033c"); // Resets the terminal
  430. }
  431. void cliFunc_restart( char* args )
  432. {
  433. // Trigger an overall software reset
  434. Output_softReset();
  435. }
  436. void cliFunc_version( char* args )
  437. {
  438. print( NL );
  439. print( " \033[1mRevision:\033[0m " CLI_Revision NL );
  440. print( " \033[1mBranch:\033[0m " CLI_Branch NL );
  441. print( " \033[1mTree Status:\033[0m " CLI_ModifiedStatus CLI_ModifiedFiles NL );
  442. print( " \033[1mRepo Origin:\033[0m " CLI_RepoOrigin NL );
  443. print( " \033[1mCommit Date:\033[0m " CLI_CommitDate NL );
  444. print( " \033[1mCommit Author:\033[0m " CLI_CommitAuthor NL );
  445. print( " \033[1mBuild Date:\033[0m " CLI_BuildDate NL );
  446. print( " \033[1mBuild OS:\033[0m " CLI_BuildOS NL );
  447. print( " \033[1mArchitecture:\033[0m " CLI_Arch NL );
  448. print( " \033[1mChip:\033[0m " CLI_Chip NL );
  449. print( " \033[1mCPU:\033[0m " CLI_CPU NL );
  450. print( " \033[1mDevice:\033[0m " CLI_Device NL );
  451. print( " \033[1mModules:\033[0m " CLI_Modules NL );
  452. }