Auswertung von einkommenden BT-Signalen

Guten Abend zusammen,

ich benötige eure Hilfe da ich derzeit daran verzweifle.
Für die meisten dürfte die Problematik wahrscheinlich kein Problem darstellen...

Bisher war mein Programm so aufgebaut, dass ich den einkommenden String, mit einer bestimmten Zeichenfolge eingelesen habe mit Read.StringUntil();.
Anschließend konnte ich den zwischengespeicherten String abfragen und mit If-Befehlen daraufhin was ausführen lassen.

Die verkürzte Version vom Sketch mit den wichtigen Punkten:

String btmsg;                                                          // Variable fuer den Bluetooth Befehl


 
void setup()                                                        // Setup Beginn                         
{
  btSerial.begin(9600);                                             
  btSerial.println("Bluetooth available");                         
}




void loop()                                                         // Programmbeginn 
{
 
  if (btSerial.available())   
                                      
  {
    btmsg = btSerial.readStringUntil('\n');                                       
   
    
     
    if (btmsg == "STOP")
    {
       Stop();
       btSerial.println("STOP");
    }
    
    else if(btmsg == "VOR")
    {
       Vorwaerts();
       btSerial.println("Vorwaerts");
    }      
  }
}

Jetzt muss ich mein Programm erweitern indem der String eine Zeichenfolge und eine Zahl beeinhaltet. Die Zeichenfolge gibt die Aktion an welche ich ausführen möchte (z.B. PWM und Servo) und die Zahl beeinhaltet dann den Ansteuerwert (z.B. Servo den Winkel oder eine PWM-Vorgabe).

Und jetzt hoffe ich ihr könnt ihr mir helfen, wie man am besten die beiden Parameter abfragt aus einem String und in eine Variable speichert um darauf zurückzugreifen in einer If-Abfage bzw. den Wert daraus als int zuverarbeiten.

Ich hoffe es ist einigermaßen verständlich geschrieben und ihr könnt mir helfen.

Ich Danke euch im Voraus!

Gruß

Hallo,

strings kann man nicht wie Zahlen vergleichen. Dafür gibts die Funktion strncmp und du brauchst ein ausreichend großen Einlesepuffer.

Bsp. Einlesepuffer:

const byte SERIAL_BUFFER_SIZE = 30;
char serialBuffer[SERIAL_BUFFER_SIZE];

Bsp. Einlese Funktion:

bool read_Serial()
{
  static byte index = 0;

  if (Serial.available() > 0)  {
    byte c = Serial.read();
    if (c >= 32 && c != '\n' && (index < SERIAL_BUFFER_SIZE - 1))
    {
      serialBuffer[index++] = c;        // in Buffer schreiben
    }
    else if ( c == '\n') {                // Übertragungsende
      serialBuffer[index] = '\0';        // Null Terminator setzen
      index = 0;
      return true;
    }
  }
  return false;
}

Bsp. Auswertefunktion:

void ist_serial_was_da ()
{
  if ( read_Serial() == true)  {
      
    if (strncmp(serialBuffer, "STOP", 4) == 0)  {            // string vergleichen
      // ...
      // mach was sinnvolles
    }
    else if (strncmp(serialBuffer, "VOR", 3) == 0)  {        // string vergleichen
      // ...
      // mach was sinnvolles
    }
    memset(serialBuffer, '\0', sizeof(serialBuffer));          // seriellen Buffer löschen
  }
}

Das wäre für Strings das Grundgerüst. Das Endeerkennungszeichen \n und \r wird mit Serialprintln() automatisch erzeugt.
In Terminals wie hterm sieht man das.

Wenn du aber Datentypen mit strings mischen möchtest verkompliziert das alles. Du müsstest dann aus dem Strings einzelne Elemente rausfischen. Das geht auch, dafür gibts auch Funktionen, ich rate davon ab, wenn es nicht unbedingt notwendig ist.
Kannst du nicht Zahlencodes für bestimmte Befehle definieren? Du überträgst immer einen Datentyp unsigned int. Alle Werte die meinetwegen größer 1000 sind, sind irgendwleche Steuercodes. Alles was 0 bis 255 ist, sind deine PWM Werte.

strings kann man nicht wie Zahlen vergleichen. Dafür gibts die Funktion strncmp und du brauchst ein ausreichend großen Einlesepuffer.

Sicher? Ich hab das auch mal so gemacht, mit Strings versteht sich, nicht mit char arrays (hierfür strncmp). Da er String nutzt, sollte das daran nicht scheitern.

Ist hier beschrieben.

Hallo,

ein String besteht letztlich aus einem char array und ist Null terminiert und ein Zeiger zeigt auf die erste Adresse. Das ist der Grund warum ein direkter Vergleich nicht möglich ist. In dem Link von dir ist das in "Arduino Komforfunktionen" versteckt. Besser erklären kann ich das leider nicht. :confused:

Guten Morgen,

schonmal vielen Dank für die Rückmeldung :slight_smile:

Ich kann meine Vorstellung nochmal etwas konkretisieren...

Ich bekomme den String von einer selbst erstellten Handy-App (der String ist selbst definiert via Bluetooth zum Arduino.

Meine Vorstellung zur Umsetzung des Strings war folgende:

String Aufbau: AKTION | Steuerwert |ABSCHLUSS
Befehl BeispielPWM | 90 | \n

Wobei der Befehl nur zwei verschiedene Zeichenfolge beiinhaltet PWM / JOY.
Der Steuerwert kann Werte im Bereich von -180 - +180 annehmen (JOY) und bei PWM 0 - 100.

Der Aktionsbefehl soll abgefragt werden per If-Abfrage oder case Befehl um dann in den entsprechenden Abschnitt zu springen. Darauf wird im entsprechenden Aktionsabschnitt der Steuerwert direkt verarbeitet als Vorgabe.

Sollte es eine Möglichkeit geben, die die Programmier Umsetzung vereinfacht nehme ich diese auch sehr gerne an.

Meine Vorstellung zur Umsetzung des Strings war folgende:

Kann es sein dass du den CMDMessenger nutzen möchtest?
Denn der hat dieses alles schon eingebaut.

ein String besteht letztlich aus einem char array und ist Null terminiert und ein Zeiger zeigt auf die erste Adresse.

Aber doch nicht die String klasse, welche hier verwendet wird....
Es ist ok, wenn du die für dich ausblendest.
Aber da sie hier verwendet wird, ist deine Ansage leider unzutreffend.

combie:
Kann es sein dass du den CMDMessenger nutzen möchtest?
Denn der hat dieses alles schon eingebaut.

Nein, bisher nichts gehört vom CMDMessanger.
Das war für mich erstmal ein guter strukturierter Aufbau eines Strings.

Wäre das eine funktionierende Alternative:

Wäre die Auswertung des Strings denn viel einfacher wenn statt dem Aktionsbefehl "PWM" oder "JOY" Zahlenwerte wie "01" oder "02" verwendet werden? Sodass der Befehl dann z.B. aus "01_90\n" besteht?

Kann man dann nur mit dem Befehl parseInt(); arbeiten sodass für die beiden ankommenden Zahlenwerte jeweils eine Variable durch parseInt(); beschrieben wird? Da müsste ihr mir nur dann was für die negativen Zahlen einfallen lassen...

Aber doch nicht die String klasse, welche hier verwendet wird....

-->Jup... der Vergleichsoperator == wird hierbei mit der Funktion compare überladen (wenn ich das hier richtig deute)

Der Aktionsbefehl soll abgefragt werden per If-Abfrage oder case Befehl um dann in den entsprechenden Abschnitt zu springen. Darauf wird im entsprechenden Aktionsabschnitt der Steuerwert direkt verarbeitet als Vorgabe.

-->Dir geht es also nur darum, den String passend zu verarbeiten? Also kommt der Zeichensatz schon richtig an, beziehungsweise hast Du Dir diesen schon auf der seriellen Konsole ausgeben lassen?
Ansonsten kannst Du ja mithilfe der Trennzeichen | verschiedene Substrings generieren, ein String enthält den Befehl, auf den Du vergleichen kannst, ein String die Daten, welche Du zu einer Variablen umwandelst. Das Aufteilen des Strings dann entweder a) indem Du ein festes Muster verwendest, also z.B. das Trennzeichen befindet sich an genau der 5. Stelle... oder Du suchst das Trennzeichen mit find_first_of.

Hallo,

also wenn du auf Strings nicht angewiesen bist, dann verzichte darauf. Denn damit entfällt auch das "mühsame" herausfischen einzelner Strings aus dem Datenstrom. Seit paar Wochen nutze ich ein union struct als Datenpaket. Damit kannst du direkt und ohne Umschweife auf Einzelwerte zugreifen.

http://forum.arduino.cc/index.php?topic=499791.0

union Nachricht
{
	struct
	{
		uint8_t adresse;    
		int32_t data1;     
                int16_t data2;    
		uint8_t data3;    
	};
	uint8_t asArray[8];			// Summe aller struct Datentypen, für Zugriff über Index
} empfDaten, sendDaten;			// zwei gleiche union Buffer anlegen

Einlesen von der seriellen wie sicherlich jetzt auch schon über den Index in das empfDaten.asArray[]
Danach direkter Zugriff auf was du vergleichen oder wissen möchstest wie zum Bsp.
empfDaten.adresse
empfDaten.data1
empfDaten.data2
empfDaten.data3

und verschicken tuste dein sendDaten.asArray[] Byteweise auch über einen Index.

void send_Nachricht ()
{       
      Serial.write(STX);    // Startzeichen
      for (byte i=0; i < sizeof(Nachricht); i++) {
        Serial.write(sendDaten.asArray[i]);        // Bytes rausschieben
      }
      Serial.write(ETX);    // Endezeichen
}

Ich möchte das nun nicht als Weisheit letzter Schluss anpreisen. Aber ich finde das so dermaßen genial und einfach, dass ich auf Strings komplett verzichte. Die Handhabung ist wesentlich einfacher. Ob man nun Strings als Befehlscode festlegt oder paar Zahlen ist Jacke wie Hose.

Vielen Dank für die erneute Rückmeldung, das sieht auch verständlich aus.

Ich habe heute auch nochmal etwas versucht, wenn ich die Zeichenfolge "PWM" bzw "Joystick" doch durch eine Zahlenfolgen ersetze und dann mit dem Befehl parseInt() die Zahlenfolgen auswerte.

Kam ich auf folgenden Sketch:

int aktion;
int funktion;
char* c;

 

void setup()

{

Serial.begin(9600);

Serial.println("Kommunikation vorhanden");

}

 

void loop()

{

  if (Serial.available())

  {

    if (Serial.find('B'))

    {

    char c=Serial.read();

    int aktion = Serial.parseInt();

    int funktion = Serial.parseInt();

 

    Serial.print("Aktion: ");

    Serial.println(aktion);

    Serial.print("Funktion: ");

    Serial.println(funktion);

    }

  }

}

Gibt es da irgendwelche Gründe davon abzusehen?

Ansonsten danke ich euch allen für eure Ideen und Hilfestellung!

Denn damit entfällt auch das "mühsame" herausfischen einzelner Strings aus dem Datenstrom.

Mühsam ist das auch nur wirklich mit der String Klasse. Mit C Strings ist es ziemlich einfach Strings zu zerlegen auch Dinge wie "Kommando,123" in wenigen Zeilen zu bearbeiten

und verschicken tuste dein sendDaten.asArray[] Byteweise auch über einen Index.

Du hast eine Union mit einem Array! Da muss man nichts byte-weise verschicken. Serial.write() kann ein Array auf einmal versenden.

Hallo,

Strings und wieder nicht Strings. Mal ehrlich wer soll da durchblicken? Ich nutze demzufolge nur C Strings. Auch gut. Das ist gefühlt das letzte große Thema wo ich immer noch nicht ganz durchblicke. Weil das nicht im Datenblatt steht. :slight_smile:
Nochmal nachgefragt. Zaubern kann die Stringklasse doch auch nicht. Intern muss sie doch auf C-Strings zurückgreifen und alles zerlegen? Nur durch die fertigen Funktionen bekommt man das nicht mit?

Weil das nicht im Datenblatt steht.

Naja, das Verhalten, also das Interface, der String Klasse, ist recht gut dokumentiert.
Die Innereien nicht.

Aber, das ist in der OOP auch richtig und gut so.
Verbergen der Implementation, das ist ein Hauptsinn und Zweck, der OOP.

Intern muss sie doch auf C-Strings zurückgreifen und alles zerlegen?

Wahrscheinlich, aber kein MUSS.
z.B. kennt Pascal mehr String Typen, als als sich ein C Programmierer vorstellen kann
Und bei einer Stringklasse wie String "könnte" ich mir vorstellen, dass sie intern eine Variante, ähnlich den AnsiStrings nutzt.

Aber was solls:

  1. Die Innereien von String sind kein Geheimnis. Sie liegen im Quellcode vor.
  2. Der Programmierer muss sich nicht für die Innereien interessieren. Es reicht wenn sie funktionieren.

Tipps zum Umgang damit:

  1. Mit möglichst wenig String Instanzen arbeiten.
  2. In setup() schon den Speicher reservieren.
    Das wirkt der Fragmentierung entgegen.

Je weniger RAM zur Verfügung steht, desto wichtiger sind die beiden Punkte.

combie:
Je weniger RAM zur Verfügung steht, desto wichtiger sind die beiden Punkte.

...und desto eher sollte man vielleicht ganz auf die String-Klassen verzichten, und die Standard-C Funktionen nutzen. Da gibt es auch 'strtok', was genau das macht, was Pupile will.

Da gibt es auch 'strtok', was genau das macht, was Pupile will.

Ja, strtok() kann ihr/ihm helfen.
Ist allerdings nur ein Teil des notwendigen Parsers.

Darum habe ich ihm auch versucht den CMDMessenger schmackhaft zu machen.
Denn der hat alles drin. Und ist einfach zu nutzen.
Der hat zwar etwas Speicherhunger, neigt aber nicht zur Fragmentierung.

combie:
Darum habe ich ihm auch versucht den CMDMessenger schmackhaft zu machen.
Denn der hat alles drin. Und ist einfach zu nutzen.
Der hat zwar etwas Speicherhunger, neigt aber nicht zur Fragmentierung.

Wie fast überall: Bequemlichkeit kostet ... :wink:

Ich habe in meiner Anfangszeit mal ein Beispiel mit strtok() veröffentlicht. Evtl. hilft es dem TO von String weg zu kommen.

Gruß Tommy

Doc_Arduino:
Nochmal nachgefragt. Zaubern kann die Stringklasse doch auch nicht. Intern muss sie doch auf C-Strings zurückgreifen und alles zerlegen?

Natürlich verwendet sie intern C Strings. Aber die Methoden die sie nach außen zur Verfügung stellt sind nur recht eingeschränkt und umständlich. Mit C Strings kannst du mehr machen (vor allem was Konvertierung und Formatierung betrifft). Und das einfacher, da die Strings in situ behandelt werden; statt neuen Objekten oder einem Index bekommst du Zeiger zurück die man direkt an andere Funktionen übergeben kann. z.B. eine Such-Funktion liefert einen Zeiger auf den gefundenen Teil-String den man so wie er ist an eine Konvertierungs-Funktion-übergibt. Einfacher geht es nicht. Wenn du mit der String Klasse nach etwas suchst bekommst du nur den Index im String und wenn du damit subString() machst hast du einen neues Objekt

Hallo,

Danke euch für die näheren Erklärungen. Irgendwann raffe ich das bestimmt so richtig und würfel nichts mehr durcheinander.