Forum: Mikrocontroller und Digitale Elektronik AT Kommandos / Befehlsliste verarbeiten


von Axel R. (Gast)


Lesenswert?

Liebe Mitleser ;)

als Hobby C-Progger bin ich gerade dabei, mir einen
Kommandozeilen-Interpreter zu bauen.
Man findet tatsächlich nur schwer etwas zu diesem Thema.

In der Codesammlung habe ich einen Beitrag gefunden
Beitrag "Einfacher Interpreter für Komandozeilen/Befehlszeilen"
und bei RN im wiki gibt es einen Anhaltspunkt:
http://www.rn-wissen.de/index.php/Avr-gcc (Section 31 Sprungtabelle)

Bei AT Befehlen, wie man sie bei Modems zB. eingibt,
lässt sich ja generell mit:

1) AT+xyz=? der erlaubte Bereich des Parameters abfragen
2) AT+xyz? der aktuelle Wert von "xyz" abfragen
3) AT+xyz=100 den Wert von "xyz" mit 100 festlegen
4) AT+xyz= 1,2,"0000",5 der Funktione, die hinter "xyz" steht,
   mit Werten und Parametern füttern.
Hinzu kommt dann noch "AT*" irgentwas, "AT^", "ATA", usw. also Befehle, 
bei denen das "PLUS" Zeichen als Befehlstrenner fehlt.

Was soll ich nun in die Sprungtabelle eintragen, wenn ich das Beispiel 
ausm RN als Grundlagen nehmen würde?

Eigentlich müsste ich in diese Sprungtabelle

1) AT+
2) AT*
3) ATA
4) ATH

usw. eintragen und das, was danach kommt, also
1) xyz
2) test1
3) test2

usw. in eine zweite Sprungtabelle?
Was mache ich mit den unterschiedlichen Parametern, welche ja als Zahl 
oder als String vorliegen können un dauch noch von der Anzahl her 
unterschiedlich sein können?

Dem Beispiel in der Codesammlung konnte ich entnehmen, das es dieses 
Problem wohl schon mal gegeben hat.
Beitrag "Einfacher Interpreter für Komandozeilen/Befehlszeilen"
Leider sind hier verschiedene Punkte für mich unverständlich, da hier 
ganz viel Pointerrechnerei betrieben wurde und viele vorhandene 
String-Funktionen selbst geschrieben wurde und ich bei denen leider mit 
meinen bescheidenen C-Kenntnissen nicht wirklich durchsteige.

Ein ganz simples Beispiel aus dem Thread:
1
int8_t execute_cmd_interface(char *string){
2
  /* local variables */
3
  register uint8_t  ui8_cnt    =0;
4
  register char    act_char  =1;
5
  register int8_t    cmd_ret_val  =0;
6
  register uint8_t  cmd_count  =0;
7
  int8_t         ret_val    =0;
8
  /* start reading string */
9
  act_char=string[ui8_cnt++];  /* Read next sign */

Speziell /* Read next sign */

um welches Vorzeichen handelt es sich? Ich dachte char ist deshalb 
Vorzeichen behaftet, weil die Druckbaren Zeich im 7Bit Bereich liegen, 
also bis 127 gehen.

So wird auch viel hin-und herkopiert -k.A. ;)

Ich werde nun versuchen, mit Hilfe der Standardbefehle vom WINAvr, mir 
das nochmal aufzudröseln. Auwei - ob ich das hinbekomme?

Aber mit:
1
if (buffer[i] =='+' $$ buffer[i-1]=='T' && buffer[i-2] =='A')
wollte ich eben diesmal nicht machen, hmm.

Hat evtl. jemand solch eine Kommandoschnittstelle schon einmal 
implementiert?

BTW. Muss ja nicht mit "AT+ oder "AT*" laufen. Das sollte nur als 
Beispiel dienen. Auch andere Kommadostrukturen wären sicher denkbar.

Danke fürs Lesen

Axelr.

Frohe Pfingsten

von G. B. (garyb)


Lesenswert?

Hallo Axel...

ohne zu wissen was du wie mit den AT Befehehlen machen möchtest - hier 
mein Vorschlag wie du mit meinem "Pointern"-Beispiel ;O) es hinbekommen 
könntest.
1
    irgendwo in main
2
  /* add command-functions to command-interface */
3
  cmd_interface_add_cmd("ATA",  &command_0);

damit wäre der Befehl schon mal bekannt.. die Anzahl der Argumente 
interessieren den Code erst mal nicht, du musst aber den Funktionszeiger
&command_0 mit Leben füllen, indem du eine eine Funktion dazu 
schreibst...

Beispiel - machen das Leben leichter ;O)
1
uint8_t command_0(int8_t argc, char *argval[]){ /* ATA-Befehl */
2
  uint8_t ui8_cnt;
3
        /*Sende Befehl*/
4
  USART_SENDEFUNKTION("ATA");
5
        /*Sende Parameter so wie sie eingegeben wurden ohne Trennzeichen */
6
  for (ui8_cnt=0; argc>ui8_cnt; ui8_cnt++){
7
    USART_SENDEFUNKTION(argval[ui8_cnt]);
8
  }
9
  return 0;
10
}

uint8_t command_0(int8_t argc, char *argval[])
die Parameterliste muss so bleiben, das bestimmt mein Code so.
argc    - hier bekommst du die Anzahl der Parameter
argval  - hier bekommst du die pointer auf die verschidenen Strings
          deiner Argumente/Parameter

Beispiel: ATA P1,p2 - keine Ahnung ob das Sinn macht
argval[0] = "P1"
argval[1] = "p2"

Hilft dir das?

Gruss Gary

von Axel R. (Gast)


Lesenswert?

Hallo Gary,

>ohne zu wissen was du wie mit den AT Befehehlen machen möchtest

Ich will ein SIEMENS TC35 emulieren.

Mir geht es in erster Linie um die Herangehensweise. Deinen BeispielCode 
habe ich ans Laufen gebracht. Habs an meinen Mega128 angepasst.
Der Befehl "ATA" nimmt gerade bei der Abarbeitung eine Sonderstellung 
ein, so wie alle Befehle, die halt einen Buchstaben mehr haben, als eben 
nur AT.

Ich werde nun doch eine Statemachine programmieren, in der ich
AT, AT*, AT^, AT+, AT& ...
usw erkenne.

Wenn keines davon dabei war, nehm ich mir die restlichen bleibenden
ATA, ATH, ATE, usw. vor.

Dann werde ich nachsehen, ob Kommas drinn sind und wieviele Zeichen 
zwischen den Kommas.
Hier hatte ich schon einst einen NMEA GPS String Auswerter (APRS nach 
N4TXI) gebastelt. Den leihe ich mir dafür aus.
Dann wären noch das "=" interessant und das "?".
Hieran werde ich entscheiden, ob die dahinterstehende Funktion ein GET, 
ein SET oder ein Report bringen muss.

Im Anschluss daran die Funktionszeigertabelle, die dann die 
entsprechende Funktion anspringt.

Nunja - diese Verbindung zwischen Statmachine und Funktionszeigertabelle 
bereitete mir etwas Kopfzerbrechen.

Vielen Dank und viele Grüße

Axelr.

von Klaus W. (mfgkw)


Lesenswert?

Axel Rühl schrieb:
> Nunja - diese Verbindung zwischen Statmachine und Funktionszeigertabelle
> bereitete mir etwas Kopfzerbrechen.

Die Sache mit der Tabelle würde ich mir etwa so vorstellen:
1
#include <stdlib.h>
2
#include <stddef.h>
3
#include <stdio.h>
4
#include <string.h>
5
6
// Z.B. Typ einer Funktion, die ein Kommando bekommt (erster Parameter
7
// ist das Kommando, zweiter der Rest der Kommandozeile).
8
// Rückgabe: eine int als Fehlerkennung
9
// Könnte auch anders sein, aber alle Funktionen müssen
10
// gleiche Signatur haben:
11
12
typedef int (*cmd_f_t)( const char *p_cmd, char *p_rest );
13
14
15
// Die einzelnen Funktionen (eine zum Kopieren, eine zum Löschen, ...)
16
// Alle müssen zu der obigen Signatur passen (cmd_f_t):
17
18
int copy_f( const char *p_cmd, char *p_rest )
19
{
20
  // Kopiere irgendwas anhand p_rest...
21
  printf( "Kopierbefehl: <%s> <%s>\n", p_cmd, p_rest );
22
23
  return 0;
24
}
25
26
int delete_f( const char *p_cmd, char *p_rest )
27
{
28
  // Lösche irgendwas anhand p_rest...
29
  printf( "Löschbefehl: <%s> <%s>\n", p_cmd, p_rest );
30
31
  return 0;
32
}
33
34
int rename_f( const char *p_cmd, char *p_rest )
35
{
36
  // Benenne irgendwas anhand p_rest um...
37
  printf( "Schiebebefehl: <%s> <%s>\n", p_cmd, p_rest );
38
39
  return 0;
40
}
41
42
// ggf. weitere Funktionen...
43
44
45
// Tabelle mit allen Kommandos und den zugehörigen Funktionen:
46
47
typedef struct
48
{
49
  const char    *p_cmd;   // Kommando
50
  cmd_f_t        p_f_t;   // Zeiger auf zugehörige Funktion
51
} cmd_map_entry_t;
52
53
cmd_map_entry_t     cmd_map[] =
54
{
55
  { "copy", &copy_f },
56
  { "delete", &delete_f },
57
  { "rename", &rename_f },
58
  // ... ggf. weitere
59
};
60
61
// Anzahl Elemente in der Map:
62
const int   l_cmd_map = sizeof(cmd_map)/sizeof(cmd_map[0]);
63
64
65
// Vergleichsfunktion für cmd_map_entry_t-Elemente (zum Sortieren mit
66
// qsort() und zum Suchen mit bsearch() geeignet):
67
int compare_cmd_map_entry( const void *p1, const void *p2 )
68
{
69
  cmd_map_entry_t  *p_cmd_map_entry_1 = (cmd_map_entry_t*)p1;
70
  cmd_map_entry_t  *p_cmd_map_entry_2 = (cmd_map_entry_t*)p2;
71
72
  return strcmp( p_cmd_map_entry_1->p_cmd, p_cmd_map_entry_2->p_cmd );
73
74
}
75
76
77
int main( int nargs, char **args )
78
{
79
  // Map sortieren (kann entfallen, wenn die Kommandos bereits
80
  // alphabetisch sortiert in cmd_map[] stehen):
81
  qsort( cmd_map, l_cmd_map, sizeof(cmd_map[0]), compare_cmd_map_entry );
82
83
  // Anhand eines Kommandos die Funktion suchen und aufrufen:
84
  const char        *p_cmd = "copy";
85
  char              *p_restliches_Kommando = "a.txt b.bak";
86
  cmd_map_entry_t   *p_map_entry_gefunden;
87
  cmd_map_entry_t    schluessel = { "copy", NULL }; // 2. Parameter
88
                                                    // egal, da
89
                                                    // compare_cmd_map_entry
90
                                                    // nur den ersten anschaut
91
92
  if( (p_map_entry_gefunden=bsearch( &schluessel,
93
                                     cmd_map,
94
                                     l_cmd_map,
95
                                     sizeof(cmd_map[0]),
96
                                     compare_cmd_map_entry
97
                                     )
98
       )
99
      )
100
  {
101
    // p_map_entry_gefunden ungleich NULL, also Eintrag in Map
102
    // gefunden!
103
    // Funktion aufrufen:
104
    int err = p_map_entry_gefunden->p_f_t( p_cmd, p_restliches_Kommando );
105
    printf( "Kommando liefert %d als Fehler\n", err );
106
  }
107
  else
108
  {
109
    // Kommando nicht gefunden!
110
    printf( "Kommando <%s> unbekannt\n", p_cmd );
111
  }
112
113
  return 0;
114
}

Nach diesem Strickmuster kann man leicht neue Kommandos hinzufügen.
Mit wenig Änderung (Feld verlängerbar) könnte man sogar zur
Laufzeit neue Kommandos dazubauen. Nach dem Verlängern des Feldes
muß man wieder qsort() aufrufen, damit das Feld sortiert ist und
bsearch() wieder die Kommandos findet.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.