Il serait bien de retourner le nombre de paramètres trouvés.
Oui exact, donc en retournant le nombre d'élément du tableaux de pointeur.
Oui, en retournant cpt
Pas du tout. Cela simplifie. Un petit exemple :
int argc = gettok(s, " ", parg);
if (argc > 0) {
if (strcmp(parg[0], "echo") == 0) {
echoCmd(parg, argc);
}
else if (strcmp(parg[0], "cat") == 0) {
catCmd(parg, argc);
}
// etc.
else {
Serial.println("what's up doc ???");
}
}
// etc.
int echoCmd(char **argv, int argc)
{
for (int cnt = 0 ; cnt < argc ; cnt++) {
Serial.println(argv[cnt]);
}
}
int catCmd(char **argv, int argc)
{
for (int cnt = 0 ; cnt < argc ; cnt++) {
// ouverture d'un fichier (ou plusieurs) et affichage
}
}
Chaque commande ayant le même prototype, argv + argc, cela te permet de tout faire, avec ou sans getop().
Super ton code et très professionnel, si j'avais su, je vais devoir remanier tout mon système de commande car je me suis basé sur le source ansi terminal qui utilise des case avec des Enum, dans un système de boucle lol.
Je vais terminé le principe de fonctionnement de l'echo et surement que je vais reprendre le principe de ton code qui va simplifier largement le système de commande.
Pour récupérer les arguments de commande actuellement je fait comme ceci, tu va surement rigoler:
void decode_command()
{
if (newData == true) {
/*if (lastCommand) {
SERDEBUG.println(lastCmd);
strcpy(tempChars, lastCmd);
ltrim(tempChars);
lastCommand = false;
} else {*/
// SERDEBUG.println(receivedChars);
strcpy(tempChars, receivedChars);
ltrim(tempChars);
// SERDEBUG.printf("decode_command() size: %d tempChars: %s lenRecv: %d lastCmd: %s\r\n", strlen(tempChars), tempChars, lenRecv, lastCmd);
// }
auto inputLine = tempChars;
char Cmd[numChars] = {0};
if (inputLine == nullptr || *inputLine == 0 || inputLine[0] == '\r' /*|| inputLine[0] == ' '*/ || strlen(inputLine) == 0) {
state = State::Prompt;
newData = false;
return;
}
if (strlen(inputLine) > numChars) inputLine[numChars] = '\0'; //Max input reached
int linelen = strchr(inputLine, '\r') - inputLine;
strncpy(Cmd, inputLine, linelen);
rtrim(Cmd);
if (strncmp(inputLine, "dir", 3) == 0 || (strncmp(inputLine, "dir", 3) == 0 && inputLine[3] == ' '))
state = State::Dir;
else if (strcmp(Cmd, "info") == 0)
state = State::Info;
else if (strcmp(Cmd, "reset") == 0)
state = State::Reset;
else if (strcmp(Cmd, "boot") == 0)
state = State::Reset;
else if (strcmp(Cmd, "scan") == 0)
state = State::Scan;
else if (strcmp(Cmd, "cls") == 0)
state = State::Cls;
else if (strncmp(inputLine, "del", 3) == 0)
state = State::Del;
else if (strcmp(Cmd, "ascii") == 0)
state = State::Ascii;
else if (strcmp(Cmd, "wifi") == 0)
state = State::Wifi;
else if (strcmp(Cmd, "help") == 0)
state = State::Help;
else if (strcmp(Cmd, "color") == 0)
state = State::Color;
// else if (strcmp(Cmd, "test") == 0)
// state = State::Test;
else if (strcmp(Cmd, "mem") == 0)
state = State::Mem;
else if (strcmp(Cmd, "mcpr") == 0)
state = State::Mcpr;
else if (strcmp(Cmd, "dcnx") == 0)
state = State::Dcnx;
else if (strcmp(Cmd, "keybinfo") == 0)
state = State::Keybinfo;
else if (strcmp(Cmd, "curson") == 0)
state = State::Curson;
else if (strcmp(Cmd, "cursoff") == 0)
state = State::Cursoff;
else if (strcmp(Cmd, "date") == 0)
state = State::Date;
else if (strncmp(inputLine, "test", 4) == 0 && inputLine[4] == ' ')
state = State::Test;
else if (strncmp(inputLine, "touch", 5) == 0 && inputLine[5] == ' ')
state = State::Touch;
else if (strncmp(inputLine, "type", 4) == 0 && inputLine[4] == ' ')
state = State::Type;
else if (strncmp(inputLine, "more", 4) == 0 && inputLine[4] == ' ')
state = State::More;
else if (strncmp(inputLine, "mkdir", 5) == 0 && inputLine[5] == ' ')
state = State::Mkdir;
else if (strncmp(inputLine, "rmdir", 5) == 0 && inputLine[5] == ' ')
state = State::Rmdir;
else if (strncmp(inputLine, "wifi", 4) == 0 && inputLine[4] == ' ')
state = State::Wifi;
else if (strncmp(inputLine, "dcnx", 4) == 0 && inputLine[4] == ' ')
state = State::Dcnx;
else if (strncmp(inputLine, "wget", 4) == 0 && inputLine[4] == ' ')
state = State::Wget;
else if (strncmp(inputLine, "telnet", 6) == 0 && inputLine[6] == ' ')
state = State::TelnetInit;
else if (strncmp(inputLine, "ren", 3) == 0 && inputLine[3] == ' ')
state = State::Ren;
else if (strncmp(inputLine, "copy", 4) == 0 && inputLine[4] == ' ')
state = State::Copy;
else if (strncmp(inputLine, "keyb", 4) == 0 && inputLine[4] == ' ')
state = State::Keyb;
else if (strncmp(inputLine, "send", 4) == 0 && inputLine[4] == ' ')
state = State::Send;
else if (strncmp(inputLine, "hex", 3) == 0 && inputLine[3] == ' ')
state = State::Hex;
else if (strncmp(inputLine, "hexdump", 7) == 0 && inputLine[7] == ' ')
state = State::Hexdump;
else if (strncmp(inputLine, "sread", 5) == 0 && inputLine[5] == ' ')
state = State::Sread;
else if (strncmp(inputLine, "ping", 4) == 0 && inputLine[4] == ' ')
state = State::Ping;
else if (strncmp(inputLine, "echo", 4) == 0 && inputLine[4] == ' ')
state = State::Echo;
else
state = State::InvalidSyntax;
newData = false;
memset(receivedChars, 0, numChars);
}
}
Le loop:
void loop() {
switch (state) {
case State::Prompt:
exe_prompt();
break;
case State::PromptInput:
exe_promptInput();
break;
case State::Help:
exe_help();
break;
case State::Dir:
exe_dir();
break;
case State::Info:
exe_info();
break;
case State::Boot:
Terminal.clear();
Terminal.write("\r\n[REBOOT]");
Terminal.deactivate();
delay(500);
ESP.restart();
break;
case State::Reset:
Terminal.setBackgroundColor(Color::Black);
Terminal.setForegroundColor(Color::BrightGreen);
Terminal.print("\e[2J");
Terminal.print("\e[H");
state = State::Prompt;
break;
case State::Scan:
exe_scan();
break;
case State::Cls:
Terminal.print("\e[2J");
Terminal.print("\e[H");
state = State::Prompt;
break;
case State::Del:
exe_del();
break;
case State::Touch:
exe_touch();
break;
case State::Type:
exe_type();
break;
case State::More:
exe_more();
break;
case State::Mkdir:
exe_mkdir();
break;
case State::Rmdir:
exe_rmdir();
break;
case State::Wifi:
exe_wifi();
break;
case State::Dcnx:
exe_dcnx();
break;
case State::Wget:
exe_wget();
break;
case State::TelnetInit:
exe_telnetInit();
break;
case State::Telnet:
exe_telnet();
break;
case State::Ren:
exe_rename();
break;
case State::Copy:
exe_copy();
break;
case State::Keyb:
exe_keyb();
break;
case State::Send:
exe_send();
break;
case State::Hex:
exe_hex();
break;
case State::Test:
exe_test();
break;
case State::Ascii:
exe_ascii();
break;
case State::Color:
exe_color();
break;
case State::Hexdump:
exe_hexdump();
break;
case State::Mem:
showmemory();
break;
case State::Sread:
exe_sread(&Serial);
break;
case State::Ping:
exe_ping();
break;
case State::Keybinfo:
exe_keybinfo();
break;
case State::Curson:
exe_curson();
break;
case State::Cursoff:
exe_cursoff();
break;
case State::Mcpr:
exe_mcpr();
break;
case State::Echo:
exe_echo();
break;
case State::Date:
exe_date();
break;
case State::UnknownCommand: {
strcpy(tempChars, receivedChars);
ltrim(tempChars);
auto inputLine = tempChars;
Terminal.printf("\r\n%s is not recognized as an internal command", inputLine);
Terminal.print(F("\r\nor external, an executable program or a batch file.\r\n"));
state = State::Prompt;
}
break;
case State::InvalidSyntax:
Terminal.println(F("\r\nThe command syntax is incorrect."));
state = State::Prompt;
break;
default:
Terminal.print(F("\r\nNot Implemeted\r\n"));
state = State::Prompt;
break;
}
doOtherStuff();
Terminal.onVirtualKeyItem = [&](VirtualKeyItem * vkItem)
{
/*//Retreive last command
if (vkItem->vk == VirtualKey::VK_b) {
if (vkItem->CTRL && !vkItem->down) {
if (strlen(lastCmd) > 0 && lastCmd[0] != '\r') {
Terminal.print("\e[A\r\n\e[2K");
Terminal.print(PROMPT);
Terminal.print(lastCmd);
lastCommand = true;
keyF3 = false;
}
}
vkItem->vk = VirtualKey::VK_NONE;
}
//Retreive last command
if (vkItem->vk == VirtualKey::VK_F3) {
if (!vkItem->down) {
if (strlen(lastCmd) > 0 && lastCmd[0] != '\r') {
Terminal.print("\e[A\r\n\e[2K");
Terminal.print(PROMPT);
Terminal.print(lastCmd);
lastCommand = false;
keyF3 = true;
}
}
vkItem->vk = VirtualKey::VK_NONE;
}*/
//Reboot
if (vkItem->vk == VirtualKey::VK_DELETE || vkItem->vk == VirtualKey::VK_KP_DELETE ) {
if (vkItem->CTRL && vkItem->LALT && !vkItem->down) {
Terminal.clear();
delay(500);
Terminal.write("\r\n[REBOOT]");
delay(500);
Terminal.deactivate();
ESP.restart();
}
vkItem->vk = VirtualKey::VK_NONE;
}
//Change color
if (vkItem->vk == VirtualKey::VK_t) {
if (vkItem->CTRL && !vkItem->down) {
switch (myScreen) {
case 0:
Terminal.setBackgroundColor(Color::Black);
Terminal.setForegroundColor(Color::Yellow);
myScreen = 1;
break;
case 1:
Terminal.setBackgroundColor(Color::Black);
Terminal.setForegroundColor(Color::BrightWhite);
myScreen = 2;
break;
case 2:
Terminal.setBackgroundColor(Color::Blue);
Terminal.setForegroundColor(Color::White);
myScreen = 3;
break;
case 3:
Terminal.setBackgroundColor(Color::Black);
Terminal.setForegroundColor(Color::BrightGreen);
myScreen = 0;
break;
}
}
vkItem->vk = VirtualKey::VK_NONE;
}
/* if (vkItem->vk == VirtualKey::VK_BREAK && !vkItem->down) {
// BREAK (CTRL PAUSE) -> short break (TX low for 3.5 s)
// SHIFT BREAK (SHIFT CTRL PAUSE) -> long break (TX low for 0.233 ms)
SerialPort.sendBreak(true);
vTaskDelay((vkItem->SHIFT ? 3500 : 233) / portTICK_PERIOD_MS);
SerialPort.sendBreak(false);
vkItem->vk = VirtualKey::VK_NONE;
}*/
};
}
J'ai un peu amélioré. Vérification argc > 0 et message à la Bugs Bunny si la commande est inconnue.
oui j'ai vu ca, et surtout merci pour ton aide et pour m'avoir conseiller sur la direction à prendre.
ca va surtout m'aider pour d'autres commande qui doivent prendre plusieurs paramètres
Comment je pourrais procéder pour faire un système d'historique de commande ?
J'imagine avec une liste chainée ?
ou un tableau de pointeur qui gardent les 10 dernières commande par exemple ?
Actuellement j'arrive à récupérer la dernière commande avec la touche flèche HAUT, mais c'est simple pour une commande.
A chaque validation d'une commande je la stock dans un tableau de pointeurs, en vérifiant que je ne dépasse pas l'indice max du tableaux,
Avec les touches fléchée HAUT ou BAS j'incrémente ou je décrémente l'indice du tableaux en vérifiant les bornes min et max du tableaux ?
Il faudrait un tableau à deux dimensions, 10 chaînes de 80 caractères.
Un index permettrait de savoir quelle est la commande courante. Quand l'index atteint la valeur de 10, le remettre à ZÉRO.
Exemple : la commande courante est la 5. Quand la commande est validée, on exécute la commande, on incrémente l'index à 6 et on vide la ligne 6 pour une nouvelle saisie.
Merci, je vais m'orienter sur ça.
J'ai testé pour savoir si déjà il n'y a pas d'erreur de code, pour l'instant je n'ai pas géré l'index, les dépassement, et les touche HAUT BAS, c'est juste un test
initTabHist();
addHistoryCmd("Commande1", 0);
addHistoryCmd("Commande2", 1);
addHistoryCmd("Commande3", 2);
addHistoryCmd("Commande4", 3);
addHistoryCmd("Commande5", 4);
addHistoryCmd("Commande6", 5);
addHistoryCmd("Commande7", 6);
addHistoryCmd("Commande8", 7);
addHistoryCmd("Commande9", 8);
addHistoryCmd("Commande9", 9);
showHistory();
Terminal.printf("affiche commande 5: %s\r\n", getHistCmd(4));
Le code:
#pragma once
#define HISTORY_SIZE 10
#define HISTORY_LENGTH 80
#define ROW_COUNT(array) (sizeof(array) / sizeof(*array))
#define COLUMN_COUNT(array) (sizeof(array) / (sizeof(**array) * ROW_COUNT(array)))
char tab_history[HISTORY_SIZE][HISTORY_LENGTH];
//Init array of hitory command
void initTabHist()
{
for (int i = 0; i < HISTORY_SIZE; i++)
for (int j = 0; j < HISTORY_LENGTH; j++)
tab_history[i][j] = 0;
}
//Add command to history
void addHistoryCmd(char *cmd, int index)
{
if (index < 0)
index = 0;
if (index > HISTORY_SIZE - 1)
index = HISTORY_SIZE - 1;
memset((char *)&tab_history[index][0], 0, HISTORY_LENGTH);
strcpy(tab_history[index], cmd);
}
//Return command from index history
char * getHistCmd(int index)
{
if (index < 0)
index = 0;
if (index > HISTORY_SIZE - 1)
index = HISTORY_SIZE - 1;
return &tab_history[index][0];
}
//Show history of command
void showHistory()
{
for (int i = 0; i < HISTORY_SIZE; i++)
Terminal.printf("%d: %s\r\n", i + 1, tab_history[i]);
}
J'ai réussi à faire l'historique des commande.
J'en ai profité pour créer la commande history que l'ont retrouve sur linux avec aussi le paramètre -c pour effacer l'historique.
En fait il fallait deux index, en comparant sous dos, il y a un index de position pour ajouter la commande à la fin du tableau, en vérifiant les limites bien sûr et un index pour le déplacement du curseur à la bonne position du tableaux de l'histoire, en sauvegardant cette position au moment ou l'on entre une commande
Bien vu.
Je voulais savoir si c'était la bonne approche:
J'ai fait la commande dir mais avec possibilités de paramètres /p, /ad, /a-d comme sous DOS.
Est ce qu'il faut tester toutes les possibilité aux niveaux des arguments ou est ce qu'il y a plus simple ?
par exemple
dir /p
dir /p /ad
dir /ad /p
dir /a-d /p
dir /p /a-d
dir /a /a-d <directory>
dir /a-d /p <directory>
je dois comparer tous les arguments argv1 et argv2 qui contiennent les deux ou un paramètre.
Heureusement qu'il n'y a pas trois paramètres possible, sinon il y aurait 2 puissance 3 de possibilité, ce qui ferait beaucoup de If et de ELSE ainsi que imbrications
Ci-dessous le code pour la fonction DIR prenant en compte les paramètres:
/p
/ad
/a-d
/p /a-d
/a-d /p
Je n'ai pas pris en compte les deux paramètres /p /ad et /ad /p, pour l'instant car il très rare d'avoir 50 répertoires dans une sdcard pour une utilisation sur ESP32, mais c'est possible de le faire.
J'ai l'impression que mon code est lourd et il doit y avoir plus simple.
void exe_dir()
{
char root[32] = {0};
char path[32] = {0};
if (_argc > 0)
{
if (_argc == 1)
{
listDir(SD, "/", 0, NULL);
state = State::Prompt;
}
else if (_argc == 2)
{
if (strcmp(_argv[1], "/p") == 0)
{ //Paramter list directory by page
strcpy(root, "/");
strcat(root, path);
listDir(SD, root, 0, "/p");
state = State::Prompt;
}
else if (strcmp(_argv[1], "/ad") == 0)
{ //Paramter list only dir
strcpy(root, "/");
strcat(root, path);
listDir(SD, root, 0, "/ad");
state = State::Prompt;
}
else if (strcmp(_argv[1], "/a-d") == 0)
{
//Paramter list only files
SERDEBUG.println("ok3");
strcpy(root, "/");
strcat(root, path);
listDir(SD, root, 0, "/a-d");
state = State::Prompt;
} else {
strcpy(root, "/");
strcat(root, _argv[1]);
File fname = SD.open(root);
if (!fname) {
Terminal.println("File not found");
state = State::Prompt;
} else {
listDir(SD, root, 0, NULL);
state = State::Prompt;
}
}
}
else if (_argc == 3)
{
if (strcmp(_argv[1], "/p") == 0 && strcmp(_argv[2], "/a-d") == 0 || strcmp(_argv[1], "/a-d") == 0 && strcmp(_argv[2], "/p") == 0) {
strcpy(root, "/");
strcat(root, path);
listDir(SD, root, 0, "/p /a-d");
state = State::Prompt;
} else state = State::OptionNotValid;
}
} else state = State::InvalidSyntax;
}
En #19 j'ai parlé de getopt().
J'ai utilisé le gettok du code plus haut, qui stock dans un tableaux de pointeur les arguments.
qui fonctionne très bien.
je l'ai incorporé sur la partie principale du code, mais vu l'architecture du prog, le système d'enum dans un loop, je ne peux pas tout reprendre, ce serait un travail fastidieux, du coup pour chaque commande entrée je stock argv et argc de la dernière commande tapée, je peux les utiliser dans n'importe quel endroit du programme.
Pour le getopt, j'ai regardé sur le lien, mais c'est impossible de l'utiliser comme tel, le fichier fait appel a un include anis.h qui lui fait appel à d'autre fichier include, etc ..
Je ne suis pas sur de m'en sortir, par contre je suis d'accord avec toi.
Je trouve dommage que dans la bibliothèque arduino en standard, qu'il n'y a pas un équivalent de getopt
Je vais regarder pour getopt, apparemment c'est inclus dans arduino, car le #include <getopt.h> ne plante pas à la compil, et j'ai vu des sources sur le NET mais beaucoup plus simple que lien de ce post. Je vais donc investir dessus.
J'ai adapté un morceau de code que j'ai trouvé, qui simule un main(argc, argv).
J'ai pris l'exemple de echo, même si les paramètres n'existe pas ou ne sont pas bon.
void exe_test()
{
char *exe[] = {"echo", "-b", "-e", "test"};
test_getopt(3, exe);
CRLF;
state = State::Prompt;
}
Le code de test_getopt:
unsigned int dbg = 0;
char *progname;
void test_getopt(int argc, char **argv)
{
int c;
progname = *argv;
while ((c = getopt(argc, argv, "b:e:i:q")) != -1) {
printf (" %s: c %c\n", __func__, c);
switch (c) {
case 'b':
dbg = atoi (optarg);
SERDEBUG.printf ("option b: optind: %d optarg: %s\r\n", dbg, optarg);
break;
case 'e':
dbg = atoi (optarg);
SERDEBUG.printf ("option e: optind: %d optarg: %s\r\n", dbg, optarg);
break;
case 'i':
dbg = atoi (optarg);
SERDEBUG.printf ("option i: opind: %d optarg: %s\r\n", dbg, optarg);
break;
case 'q':
dbg = atoi (optarg);
SERDEBUG.printf ("option q: optind: %d optarg: %s\r\n", dbg, optarg);
break;
default:
SERDEBUG.printf("Error: unknown option '%c'\n", optopt);
break;
};
SERDEBUG.printf("programme: %s optind: %d\r\n", progname, optind);
}
}
La sortie sur le terminal arduino
test_getopt: c b
option b: optind: 0 optarg: -e
programme: echo optind: 3
pour la première ligne en sortie: le "b" doit être la première option trouvé, avec le nom de la fonction
En deuxième ligne c'est bien option -b, optargs aurait du être -b et non -e ? optind est bien 0 car premier argument je suppose qui commence à l'index 0.
La dernière ligne affiche le nom du programme (echo) et le nombre paramètre qui est 3
On ne voit ni la définition, ni l'affectation d'optarg dans ton code