String/Array auflösen bei Webabfrage

Moinsen!

Ich habe da ein "interessantes" Problem, bei dem mir nicht klar ist, warum das so ist und für mich ist es irgendwie unlogisch. Aber ich bin ja noch Greenhorn :grin:

Ich habe eine Webabfrage, der Ardu spricht in regelmäßigen Abständen einen Server an und holt den Inhalt einer Textdatei. Diese hat als Inhalt eine 6-stellige Zahlenkombination. An Hand der Kombination soll der Ardu nun reagieren.

Das tut er aber komischerweise nur mit der Funktion string.endsWith, alle anderen Abfragen werden ignoriert, bzw ergeben nur Grütze.

Kann mir einer verraten, warum das so ist?

Hier mal die wichtigen Code Ausschnitte:

// include usw... rausgekürzt

char website[ ] PROGMEM = "meineURL.de";

// called when the client request is complete
static void my_callback (byte status, word off, word len){

String  schalter = ((const char*) Ethernet::buffer + off);
 
  Serial.println (schalter); // liefert den kompletten 6-stelligen Textcode
  
  Serial.println (schalter.substring(0,3));  // liefert irgendwelche Grütze, aber keine Teile des o.g. Codes
  
 //Abfragen funktionieren, wenn der Code in der Textdatei entsprechend endet
 
 if (schalter.endsWith("000")) {
   digitalWrite(2, LOW); 
 }

  
  if (schalter.endsWith("999")) {
  digitalWrite(2, HIGH); 
 } 
  
 
// Abfragen funktionieren nicht

  if (schalter.substring(3,7) = "000") {
    digitalWrite(3, LOW); 
  }
  
  if (schalter.substring(3,7) = "999") {
    digitalWrite(3, HIGH); 
  }


// Variante Abfragen funktionieren auch nicht

if (schalter.startsWith("000")) {
    digitalWrite(3, LOW); 
  }
  
iif (schalter.startsWith("999")) {
    digitalWrite(3, HIGH); 
  }

//um das void setup gekürzt

void loop () {

  
  ether.packetLoop(ether.packetReceive());
  
  if (millis() > timer) {
    timer = millis() + 5000;
    ether.browseUrl(PSTR("/verzeichnis/"), "datei.txt", website, my_callback);   

  }


}

Auch nur einzelne Stellen mit Substring abzufragen funktioniert nicht.

OK, ich kann mir jetzt erstmal behelfen, indem ich mit eine mathematische Formel überlege, um alle Möglichkeiten mit möglichst wenig Code in solche "endsWith" zu bekommen, aber spätestens wenn es nicht nur zwei relevante Zustände und Empfänger (Schalter) sind, wird es unübersichtlich...

Jemand eine Idee, wie man das Problem lösen kann und warum das so ist?

Keiner einen Plan, warum "endsWith" als Einziges geht und wie man da sonst Teilvariablen auslesen kann?

plaubel:
Keiner einen Plan, warum "endsWith" als Einziges geht

Das ist nix:
String schalter = ((const char*) Ethernet::buffer + off);

Zwischen Pointern auf String-Objekte und Pointern auf Char-Arrays gibt es keine Zuweisungskompatibilität.

Keine Ahnung, warum nach dieser Zeile mit "schalter" überhaupt irgendwas noch scheinbar funktioniert.

Selbst:
Serial.println (schalter); // liefert den kompletten 6-stelligen Textcode
würde ich mal anzweifeln. Lasse Dir mal die genaue Länge von Schalter an der Stelle ausgeben und ob das wirklich nur der beabsichtigte "6-stellige Textcode" ist. Wenn ich mal raten soll ist die Variable dort schon deutlich länger als mit Serial.Println angezeigt wird und vorneweg kommen noch mehrere Zeichen mit ASCII-Codes, die am seriellen Terminal nicht dargestellt werden bzw. nicht darstellbar sind.

jurs:
Zwischen Pointern auf String-Objekte und Pointern auf Char-Arrays gibt es keine Zuweisungskompatibilität.

Doch, gibt es. Das zeigt ein Blick in die WString.h :

// The string class
class String
{
        // use a function pointer to allow for "if (s)" without the
        // complications of an operator bool(). for more information, see:
        // http://www.artima.com/cppsource/safebool.html
        typedef void (String::*StringIfHelperType)() const;
        void StringIfHelper() const {}

public:
        // constructors
        // creates a copy of the initial value.
        // if the initial value is null or invalid, or if memory allocation
        // fails, the string will be marked as invalid (i.e. "if (s)" will
        // be false).
        String(const char *cstr = "");
        ...

Hier die letzte Zeile. Das Problem ist aber, das dieser Konstruktor immer eine komplette Kopie des übergebenen Strings macht. Das frisst zusätzlich Speicher, da die ENC28J60 Lib schon 700 Bytes Buffer verwendet. Dazu kommt dann die Kopie von einem großen Teil des Buffers.

mkl0815:
Doch, gibt es. Das zeigt ein Blick in die WString.h :
...
Hier die letzte Zeile.

Danke für die Info, das funktioniert tatsächlich (wußte ich auch noch nicht)!

mkl0815:
Das Problem ist aber, das dieser Konstruktor immer eine komplette Kopie des übergebenen Strings macht. Das frisst zusätzlich Speicher, da die ENC28J60 Lib schon 700 Bytes Buffer verwendet. Dazu kommt dann die Kopie von einem großen Teil des Buffers.

Hättest Du eine elegantere Möglichkeit? Ich bin halt noch am lernen, versuch das ganze zu begreifen. Nachdem meine Ausflüger bisher eher im Basic (damals der gute alte C 64) und später HTLM und Javascript waren erscheint mir hier alles etwas.... kompliziert, um es mal so auszudrücken.
Wichtig wäre mir eine Lösung, bei der ich irgendwie einzelne Stellen und Blöcke aus einer Datei (ob ich sie nun .txt nenne oder wie auch immer) auszulesen.

@jurs: wenn ich die ersten Stellen als "substring" ausgeben lasse, kommt irgendwas mit "HTTP/1.1.0 OK" oder so ein Kram, der mir gar keinen Sinn ergibt.
Ich würde den genauen Text ja gerne posten, aber aus irgendeinem Grunde bekomme ich grade "DNS failed" die letzten 15 Minuten, vorhin lief es wunderbar :~

  1. Frage: Kann man die ENC28J60 Lib für den Fall nicht auch noch kürzen irgendwie? Im Moment läuft es so auf nem 328er (Uno), auf nem 168er bekomme ich es nicht zum Laufen, die 1kb SRAM reichen wohl nicht (hab auch schon versucht den Buffer kleiner zu machen, bringt nix). Wenn nicht, ist das auch nicht sooo schlimm, dann werd ich die 168er eben nur für stationäre Anwendungen nutzen, der 328er Pro Mini kost ja als Klon auch nicht mehr. Ist halt nur so, daß die 168er grade noch rumfliegen :smiley:

plaubel:
@jurs: wenn ich die ersten Stellen als "substring" ausgeben lasse, kommt irgendwas mit "HTTP/1.1.0 OK" oder so ein Kram, der mir gar keinen Sinn ergibt.
Ich würde den genauen Text ja gerne posten, aber aus irgendeinem Grunde bekomme ich grade "DNS failed" die letzten 15 Minuten, vorhin lief es wunderbar :~

Wenn Dir Dein Programm durchdreht, weil ihm der RAM-Speicher ausgegangen ist und Stack und Heap sich bereits so überlappen, dass sich die Daten im RAM wie von Geisterhand verändern, müßtest Du gewaltig anfangen, RAM-Speicher zu sparen.

Eine Maßnahme, die mir dazu einfällt: Sämtliche(!) String-Objekte aus dem Programm rigoros rausschmeißen und durch sparsam definierte (und möglichst mehrfach verwendete) C-Strings (Char-Arrays) ersetzen.

Zweite Maßnahme wäre, das originale Arduino Ethernet-Shield für die Netzwerkanbindung zu verwenden und das Board mit ENC28J60 Chip (mitsamt der ENC28J60 Library) rauszuschmeissen.

plaubel:
@jurs: wenn ich die ersten Stellen als "substring" ausgeben lasse, kommt irgendwas mit "HTTP/1.1.0 OK" oder so ein Kram, der mir gar keinen Sinn ergibt.
Ich würde den genauen Text ja gerne posten, aber aus irgendeinem Grunde bekomme ich grade "DNS failed" die letzten 15 Minuten, vorhin lief es wunderbar :~

Das HTTP/1.1.0 OK ergibt schon Sinn, denn das ist die Antwort des Webservers (HTTP-Response Header) Theoretisch muss danach noch eine "200" kommen.
Der Header ist von der eigentlichen Antwort (HTML-Ausgabe z.B.) durch 2 Zeilenumbrüche getrennt. Die Headerzeilen ansich sind nur jeweils durch einen Zeilenumbruch getrennt.
So wie ich die ENC28J60 Lib verstanden habe, sollte eigentlich der Wert "off" genau das Offset sein, das auf das Ende des header und den Anfang der eigentlichen Antwortdaten verweist. Daher auch das
String schalter = ((const char*) Ethernet::buffer + off);
Damit sollte der String eigentlich am Anfang der Nutzdaten anfangen.
Ich würde das String auch weglassen und stattdessen ein

const char* result = Ethernet::buffer + off;

machen. Damit hast Du einen C-String (readonly) der auf den gleichen Daten des Buffers arbeitet und keinen zusätzlichen Speicher verbraucht. Mit einem kleinen Trick kann man z.B. das Zeichen "$" vor die Zahl in der Textdatei schreiben und dann nach diesem Zeichen suchen, das kommt nämlich im Header nicht vor, falls das "off" doch "daneben" liegt.
Anschliessend kopiert man sich die nächsten 6 Zeichen.

static void my_callback (byte status, word off, word len){

//definiere 2 C-String mit 3 Zeichen (4. wird für \0 benötigt)
char val1[4] = "";
char val2[4] = "";


//result holen
const char* result = (const char*) Ethernet::buffer + off;

int index = 0;

//laufe solange durch result bis entweder "ende" oder zeichen '

Das wäre ein Ansatz ohne die String-Klasse.
Achtung, Code ist ungetestet, da ich weder die Lib noch ein ENC28J60 habe.

Mario. gefunden
while( result[index++] != '


Das wäre ein Ansatz ohne die String-Klasse. 
Achtung, Code ist ungetestet, da ich weder die Lib noch ein ENC28J60 habe.

Mario. && index < (len - off);
 
//wenn  noch nicht ende
if(index < (len - off)) {

  //ersten wert (3 zeichen aus result) in val1 stecken und \0-Terminierung anhängen
   val1[0] = result[index++];
   val1[1] = result[index++];
   val2[2] = result[index++];
   val1[3] = 0;

  //zweiten wert (nächste 3 zeichen aus result) in val2 stecken und \0-Terminierung anhängen
   val2[0] = result[index++];
   val2[1] = result[index++];
   val2[2] = result[index++];
   val2[3] = 0;

}

 //jetzt C-String vergleichen
 //ACHTUNG !strcmp ist richtig, da bei gleicheit 0 (unde damit eigntlich false) geliefert wird

 if (!strcmp(val1,"000")) {
   digitalWrite(2, LOW); 
 }

  
  if (!strcmp(val1,"999")) {
  digitalWrite(2, HIGH); 
 } 
  
 
//jetzt das Ganze für den 2. wert

  if (!strcmp(val2,"000")) {
    digitalWrite(3, LOW); 
  }
  
  if (!strcmp(val2, "999")) {
    digitalWrite(3, HIGH); 
  }
}

Das wäre ein Ansatz ohne die String-Klasse.
Achtung, Code ist ungetestet, da ich weder die Lib noch ein ENC28J60 habe.

Mario.

Uffff!

Werd mir das mal bei Gelegenheit anschauen, ob ich das verstehe, auf dem ersten Blick sehr... verwirrend.

Im Moment habe ich aber erstmal das Problem, daß ich (warum auch immer) heute keine DNS Auflösung bekomme (DNS failed). Meine Versuche ihm von Hand einen unterzuschieben funktionieren irgendwie nicht. Wobei es gestern auch so ging....

NARRRRFFFFFFFFFFFFFFFFFFFFFFFFFFFFF :stuck_out_tongue_closed_eyes:

plaubel:
Uffff!

Werd mir das mal bei Gelegenheit anschauen, ob ich das verstehe, auf dem ersten Blick sehr... verwirrend.

Im Moment habe ich aber erstmal das Problem, daß ich (warum auch immer) heute keine DNS Auflösung bekomme (DNS failed). Meine Versuche ihm von Hand einen unterzuschieben funktionieren irgendwie nicht. Wobei es gestern auch so ging....

NARRRRFFFFFFFFFFFFFFFFFFFFFFFFFFFFF :stuck_out_tongue_closed_eyes:

Hab im nun nen google DNS unter geschoben, funzt aber trotzdem nicht mehr. Ist doch alles sch***** :frowning:

Ich bekomme nicht mal mehr das getViaDNS Beispiel zum Laufen im heute...

Und bei dem Beispiel sollte man ja wohl erwarten, daß es funktioniert, oder?

Ich sehe grade, das Beispiel "getDHCPandDNS" ist fast mein Code (der seit 2 Tagen nicht funktioniert, das Beispiel auch nicht)...

// This demo does web requests via DHCP and DNS lookup.
// 2011-07-05 <jc@wippler.nl> http://opensource.org/licenses/mit-license.php

#include <EtherCard.h>

#define REQUEST_RATE 5000 // milliseconds

// ethernet interface mac address
static byte mymac[] = { 0x74,0x69,0x69,0x2D,0x30,0x31 };
// remote website name
char website[] PROGMEM = "google.com";

byte Ethernet::buffer[700];
static long timer;

// called when the client request is complete
static void my_result_cb (byte status, word off, word len) {
  Serial.print("<<< reply ");
  Serial.print(millis() - timer);
  Serial.println(" ms");
  Serial.println((const char*) Ethernet::buffer + off);
}

void setup () {
  Serial.begin(57600);
  Serial.println("\n[getDHCPandDNS]");
  
  if (ether.begin(sizeof Ethernet::buffer, mymac) == 0) 
    Serial.println( "Failed to access Ethernet controller");

  if (!ether.dhcpSetup())
    Serial.println("DHCP failed");
  
  ether.printIp("My IP: ", ether.myip);
  // ether.printIp("Netmask: ", ether.mymask);
  ether.printIp("GW IP: ", ether.gwip);
  ether.printIp("DNS IP: ", ether.dnsip);

  if (!ether.dnsLookup(website))
    Serial.println("DNS failed");
  ether.printIp("Server: ", ether.hisip);
  
  timer = - REQUEST_RATE; // start timing out right away
}

void loop () {
    
  ether.packetLoop(ether.packetReceive());
  
  if (millis() > timer + REQUEST_RATE) {
    timer = millis();
    Serial.println("\n>>> REQ");
    ether.browseUrl(PSTR("/foo/"), "bar", website, my_result_cb);
  }
}

Warum ging das vor 2 Tagen und jetzt nicht mehr?