Onewire DeviceAddress aus String

Hallo

Ich möchte aus einem String, welcher dem Arduino als Parameter übermittelt wird, eine DeviceAddress bilden. In etwa so:

String myParam = "0x28, 0x80, 0xEF, 0x5A, 0x06, 0x00, 0x00, 0x53";
DeviceAddress myDevice = myParam;

Ich habe bereits versucht, den String beim Komma zu zerlegen und dann mit den Einzelteilen als String oder Int die DeviceAddress zu bilden. Doch da komme ich nicht weiter. String wird nicht akzeptiert, Int erzeugt jeweils 0.

Wie muss ich vorgehen, um diesen String am einfachsten in eine DeviceAdresse zu verwandeln?

Dein Problem ist die dämliche String Klasse. Da fehlen wieder mal die einfachsten Konvertierungsfunktionen um Hex-Zahlen zu behandeln. Klassische C Strings verwenden und schon geht es.

#include <OneWire.h>
#include <DallasTemperature.h>

char string[] = "0x28,0x80,0xEF,0x5A,0x06,0x00,0x00,0x53";
DeviceAddress address;

void setup()
{
  Serial.begin(9600);

  char* ptr = string;
  for (int i = 0; i < 8; i++)
  {
    address[i] = strtoul(ptr, &ptr, 16);
    ptr++;
  }

  for (int i = 0; i < 8; i++)
  {
    Serial.print(address[i]);
    Serial.print(',');
  }
}

void loop()
{
}

Splitten geht auch mit strtok(), aber man kann hier auch gleich strtoul() für Splitten + Konvertieren verwenden

Wenn es denn unbedingt die String Klasse sein muss, gibt es c_str() um einen read-only Zeiger auf den internen C String zu erhalten:
http://www.arduino.cc/en/Reference/CStr
Das funktioniert mit strtoul(), da die Funktion das Array nicht verändert.

Warum nicht direkt DeviceAddress myAddress = 0x2880EF5A06000053;

DeviceAddress ist ein typedef für ein Byte Array. Das geht also so nicht.

Aber allgemein hast du schon recht. Man sollte es direkt in das Array schreiben wenn man die Adresse im Code definieren will. Vielleicht will er die Adresse von einer SD Karte lesen, oder ähnliches…

Byte array vorbesetzen wie char array (string), nur die Quotes (") weglassen.

@ Serenifly ich klinke mich hier mal ein. Genau das, was du ansprichst ist im Moment auch mein Problem. Bei meiner Heizungssteuerung hab ich bisher die DS18B20 fest im Code verankert. Ich möchte diese aber von SD Karte einlesen. Das mache ich mit vielen anderen Werten auch, aber bei den Dallas Adressen weiß ich nicht wie es geht.

Im Programm sieht es so aus:

DeviceAddress Dachboden_0 = {0x28, 0xCA, 0xF8, 0xF7, 0x04, 0x00, 0x00, 0xBF};// Solaranlage (I) DeviceAddress Dachboden_1 = {0x28, 0xCC, 0x51, 0xF7, 0x04, 0x00, 0x00, 0xCD};// Solaranlage (II)

Wie kann ich diese Werte von einer SD-Karte lesen und zuordnen?

Ist nämlich total blöd, wenn mal ein Fühler ausfällt, es ist ja nicht Sinn der Sache, das man dann erst im Code was ändern muß. Gruß Bernward

Von SD Karte habe ich ziemlich genau das hier gezeigt: http://forum.arduino.cc/index.php?topic=287092.0 Allerdings nur ein String pro Datei. Wenn man mehrere Zeilen mit mehrere Sensor-Adressen hat man muss das anpassen um noch die Zeilen auszuwerten (Zeilen sind per CR+LF abgeschlossen. Darauf kann man auch abfragen)

Generell bevorzuge ich es bei SD wie von Serial aber immer eine Zeile komplett einzulesen und dann zu Parsen. Damit ist man viel flexibler. Dann kann man erst mal eine Zeile in ein char Array einlesen und dann mit stroul() behandeln (oder strtok() + atoi() wenn man Dezimal- statt Hex-Zahlen hat. Braucht weniger Speicher). Genauso wie ich es hier bei Serial gemacht habe geht das auch mit SD: http://forum.arduino.cc/index.php?topic=311941.msg2162464#msg2162464

Und man kann schön die Zeilen zählen um z.B. gezielt die 3. Zeile auszulesen. Also erst mal eine Funktion schreiben die nur eine Zeile in ein char Array einliest. Diese kann man dann man x-mal aufrufen um die gewünschte Zeile zu erhalten.

Aber man kann es auch wie in dem ersten Link Zahl für Zahl machen. Wenn du nur die Adressen und sonst keine Parameter hast, kann das einfacher sein. Prinzipiell geht es so oder so.

Aktuell sende ich die Adresse via Webserver, dieser extrahiert die Adresse in den String myParam. Später soll diese auf der SD Card gespeichert werden, sozusagen ein Webinterface für die Konfiguration.

Das Problem bei deinem Vorschlag ist, dass ein String nicht angenommen wird:

Statt

char string[] = "0x28,0x80,0xEF,0x5A,0x06,0x00,0x00,0x53";

habe ich

char string[] = myParam;

Die führt zum Fehler:

error: initializer fails to determine size of 'string'
error: array must be initialized with a brace-enclosed initializer

lg

Ja klar. Weil man Arrays so nicht initialisieren kann. Mal abgesehen davon dass String Objekte und char Arrays so nicht direkt zueinander kompatibel sind. Lerne etwas C/C++ Grundlagen. Ist auch in anderen Bereichen wichtig.

Ich hatte dich auf die Funktion c_str() hingewiesen wenn du an String Objekte festhalten willst:

const char* ptr = myParam.c_str();

Jetzt hast du mit string einen Zeiger (daher “ptr” = pointer) auf das interne Array des String Objekts. Das ist nicht wirklich Arduino spezifisch. Die Funktion gibt es so auch in der Standard C++ String Klasse auf dem PC.

Zum Glück habe ich es aber gerade nochmal ausprobiert und dann doch gemerkt, dass es doch nicht so ganz mit strtoul() kompatibel ist. :frowning: Da man keine non-const Referenz auf ein einen const Zeiger übergeben kann. Das hatte ich übersehen. Die Lösung ist der const-cast, mit dem man das const entfernen kann:

char* ptr =  const_cast<char*>(myParam.c_str());

Das ist aber sehr gefährlich, also diesen Zeiger auf keinen Fall zum Schreiben auf das Array verwenden!! Und das schließt hier die Verwendung von strtok() mit ein, da strtok() den zu bearbeitenden String ändert.

Also nochmal den Code von oben mit einem String Objekt:

#include <OneWire.h>
#include <DallasTemperature.h>

String myParam = "0x28,0x80,0xEF,0x5A,0x06,0x00,0x00,0x53";
DeviceAddress address;

void setup()
{
  Serial.begin(9600);

  char* ptr =  const_cast<char*>(myParam.c_str());
  for (int i = 0; i < 8; i++)
  {
    address[i] = strtoul(ptr, &ptr, 16);
    ptr++;
  }

  for (int i = 0; i < 8; i++)
  {
    Serial.print(address[i]);
    Serial.print(',');
  }
}

void loop()
{
}

Vielen Dank. So funktionierts.
C/C++ Grundlagen kommen aus zeitmangel schleichend stück für stück :slight_smile:

Alternativ könnte man auch die Konvertierungsfunktion selbst schreiben:

#include <OneWire.h>
#include <DallasTemperature.h>

String myParam = "0x28,0x80,0xef,0xBA,0x06,0x00,0x00,0x5a";
DeviceAddress address;

void setup()
{
  Serial.begin(9600);

  int length = myParam.length();
  for (int i = 0; i < 8 && i * 5 < length; i++)
    address[i] = hexToDecimal(myParam.charAt(i * 5 + 2)) * 16 + hexToDecimal(myParam.charAt(i * 5 + 3));

  for (int i = 0; i < 8; i++)
  {
    Serial.print(address[i], HEX);
    Serial.print(',');
  }
}

void loop()
{
}

byte hexToDecimal(char c)
{
  if (c >= '0' && c <= '9')
    return c - 48;
  else if (c >= 'A' && c <= 'F')
    return c - 55;
  else if (c >= 'a' && c <= 'f')
    return c - 87;
  else
    return 0;
}

Damit verliert man Flexibilität, aber bei diesem einfachen String aus immer 8 zwei-stelligen Hex-Zahlen geht es. Und man spart etwas Speicher

wow! das ist für mich zu hoch! Als Anfänger bin ich schon froh wenn es funktioniert!

Das beruht nur auf der ASCII Tabelle und auf dem Zahlensystem.

http://www.asciitable.com/: Um von '0' auf 0 zu kommen musst du 48 abziehen, da '0' 48 ist. Um von 'A' auf 10 zu kommen musst du 55 abziehen, da 'A' 65 ist. Und von 'a' zu 10 zieht man 87 ab. Das macht man auch so wenn man Zahlen von der seriellen Schnittstelle einliest.

Hex ist auch nicht viel anders als Dezimal, nur mit 16 statt 10. Also z.B. A5= 16 * A + 5 = 16 * 10 + 5 = 165

Der Rest ist etwas Spielerei mit dem Index. Man iteriert über die 8 Stellen des Byte Arrays. Um dann auf den Index im String zu kommen, macht man erst mal * 5, da man 5 Zeichen pro Zahl hat (0x + 2 Zifern + Komma). Dann + 2 für die 1. Ziffer und + 3 für die 2. Ziffer

Um von ‘0’ auf 0 zu kommen musst du 48 abziehen, da ‘0’ 48 ist.

Kannst natürlich auch ‘0’ abziehen, “da ‘0’ 48 ist.” Dafür brauchst du keine Ascii-Tabelle, musst nur

  • den Unterschied zwischen 0 und ‘0’ verstehen
  • wissen dass ‘0’ … ‘9’ praktischerweise hintereinander liegen
  • wissen, dass ‘A’ leider nicht ‘9’ + 1 ist…

Aber der String-Umweg ist echt unnötig.
Selbst wenn du es von einer SD Karte liest, kannst du leicht dafür sorgen, dass es dort als Text wie
“28 80 EF BA 06 00 00 5A” steht, und entweder gleich beim Lesen, oder aus einem c-string gelesen wird.

char myparam[24] = "28 80 EF BA 06 00 00 5A";  
  for (int i = 0; i < 8; i++)
    address[i] = hexToDecimal(myParam[i * 3]) * 16 + hexToDecimal(myParam(i * 3 + 1));

Die Leerzeichen sind schon purer Luxus um es leichter menschenlesbar/schreibbar zu machen.