MQTT, Lan oder Code Probleme ?

Hi liebes Forum.
In letzter Zeit versuche ich Funksteckdosen mit meinem Arduino über Lan, MQTT und Openhab anzusteueren, was auch funktioniert. Lediglich habe ich ein Problem, was immer auftritt. Wenn der Arduino eingeschaltet wird verbindet er sich via Lan mit einem Raspberry Pi auf dem der Mosquitto Broker läuft, um Anwesiungen für die Funksteckdosen und aderes zu erhalten. Das Problem ist, das die ganze Sache nach mehreren Stunden nicht mehr funktioniert, d.h. der Arduino nimmt keine Anweisungen mehr eintegegen und man kann die Funksteckdosen nicht mehr ein und aus schalten. Was dabei auffällt ist, dass die gelbe LED am Arduino, die beim starten leuchtet, nicht mehr leuchtet.

Hier der erste Teil meines Codes:

#include <SPI.h>
#include <UIPEthernet.h>
#include "PubSubClient.h"
#include <RCSwitch.h>
#include <OneWire.h>
#include <DallasTemperature.h>

// unkommemntieren um Debug-Modus zu aktivieren
//#define DEBUG

// --------------------------------
// Ethernet Server
// http://arduino.cc/de/Reference/Ethernet
// --------------------------------
EthernetClient ethClient;


// --------------------------------
// MQTT Client
// http://knolleary.net/arduino-client-for-mqtt/
// --------------------------------
byte MQTTserver[] = { 192, 168, 0, 1 };
PubSubClient MQTTClient(MQTTserver, 1883, callback, ethClient);

unsigned long time;
long previoustime = 0;
long MQTTTimer = 7200000;  //2 Stunden


// --------------------------------
// LED Leiste
// --------------------------------
// Pins des RGB LED Lichts
int LEDredPIN=12;
int LEDgreenPIN=10;
int LEDbluePIN=11;

// Übermittelte Farbwerte
int LEDredValue = 0;
int LEDgreenValue = 0;
int LEDblueValue = 0;

// Gerade gesetzte Farbe
int LEDredCurrent = 0;
int LEDgreenCurrent = 0;
int LEDblueCurrent = 0;

// Timervariable für eine Verzögerung. Als alternative zu delay was die verarbeitung anhält.
int LEDtimer = 0;
int LEDendTimer = 50; 


// --------------------------------
// RC-Switch
// https://code.google.com/p/rc-switch/
// --------------------------------
RCSwitch RCsender = RCSwitch();
String rcID = ""; // Übermittelte ID
String rcCommand = ""; // Übermittelter Befehl
char rcID1[6]; // ID erster Teil
char rcID2[6]; // ID zweiter Teil


// --------------------------------
// Temperatur
// http://milesburton.com/Main_Page?title=Dallas_Temperature_Control_Library
// http://www.pjrc.com/teensy/td_libs_OneWire.html
// --------------------------------
#define SENSOR_PIN 2  // Any pin 2 to 12 (not 13) and A0 to A5
OneWire temp(SENSOR_PIN);

unsigned long TEMPtimer;
unsigned long TEMPendTimer = 300000; // Schleifendurchläufe bis neue Abfrage startet

float tempC;

#define TEMPERATURE_PRECISION 9
DallasTemperature sensors(&temp);
DeviceAddress insideThermometer; // Variable zum zwischenspeichern der Adresse eines Sensors


//================================================================================================================
void setup()
//================================================================================================================
{

#ifdef DEBUG
  Serial.begin(9600);
  Serial.println("Basestation v1.0");
#endif

  // --------------------------------
  // LED Leiste
  // --------------------------------
  // Setzen der PINS als Ausgang
  pinMode(LEDbluePIN, OUTPUT);
  pinMode(LEDredPIN, OUTPUT);
  pinMode(LEDgreenPIN, OUTPUT);

  // Bei start Farbe Blau setzen
  analogWrite(LEDredPIN, 0);
  analogWrite(LEDgreenPIN, 0);
  analogWrite(LEDbluePIN, 10);


  // --------------------------------
  // RC-Switch
  // --------------------------------
  RCsender.enableTransmit(40); //Arduino Pin #40
  RCsender.setRepeatTransmit(15);

  // --------------------------------
  // Temperatur
  // --------------------------------
  sensors.begin();
  sensors.getAddress(insideThermometer, 0);
  sensors.setResolution(insideThermometer, 12);
  byte addr[8];
  temp.search(addr);
  temp.reset_search();
  
  
  // --------------------------------
  // Ethernet Server
  // --------------------------------
  // Initialisierung des Ethernets
  Ethernet.begin(addr);
  if (Ethernet.begin(addr) == 0) {
    // Wenn DHCP fehlschlägt dann rot setzen und aufhören
    analogWrite(LEDredPIN, 100);
    analogWrite(LEDgreenPIN, 0);
    analogWrite(LEDbluePIN, 0);
    while (true);
  }
  else {
    // Wenn DHCP OK ist dann grün setzen
    LEDgreenValue = 10;

#ifdef DEBUG
    Serial.print("IP-Adresse:");
    Serial.println(Ethernet.localIP());
#endif

  }
  
}

Hier der zweite :

//================================================================================================================
void loop(){
  //================================================================================================================

  // --------------------------------
  // MQTT Client
  // --------------------------------
  // Aufbau der Verbindung mit MQTT falls diese nicht offen ist.
  time = millis();
  if (!MQTTClient.connected()) {
    MQTTClient.connect("Basestation");
    // Abonieren von Nachrichten mit dem angegebenen Topics
    // MQTTClient.subscribe("/haus/moritz/ledkugel/RED/#");
    // MQTTClient.subscribe("/haus/moritz/ledkugel/GREEN/#");
    // MQTTClient.subscribe("/haus/moritz/ledkugel/BLUE/#");
    MQTTClient.subscribe("/haus/moritz/ledkugel/#");

    // RC Steckdosen Commands
    MQTTClient.subscribe("/haus/RC/#");
  }
  
  Ethernet.maintain();
 /* if(time - previoustime > MQTTTimer){
    previoustime = time;
    MQTTClient.disconnect();
    delay(100);
    MQTTClient.connect("Basestation");
    MQTTClient.subscribe("/haus/moritz/ledkugel/#");
    MQTTClient.subscribe("/haus/RC/#");
  } */


  // --------------------------------
  // LED Leiste
  // --------------------------------
  if (LEDtimer <= LEDendTimer) LEDtimer++;
  else {
    LEDtimer = 0;

    if (LEDredValue < LEDredCurrent) LEDredCurrent--;
    else if (LEDredValue > LEDredCurrent) LEDredCurrent++;

    if (LEDgreenValue < LEDgreenCurrent) LEDgreenCurrent--;
    else if (LEDgreenValue > LEDgreenCurrent) LEDgreenCurrent++;

    if (LEDblueValue < LEDblueCurrent) LEDblueCurrent--;
    else if (LEDblueValue > LEDblueCurrent) LEDblueCurrent++;
  }

  analogWrite(LEDredPIN, LEDredCurrent);
  analogWrite(LEDgreenPIN, LEDgreenCurrent);
  analogWrite(LEDbluePIN, LEDblueCurrent);


  // --------------------------------
  // RC Steckdosen
  // --------------------------------

  if (rcID.length() == 10) {

#ifdef DEBUGDEBUG
    Serial.println("ID : " + rcID);
#endif

    rcID.toCharArray(rcID1,6);
    rcID = rcID.substring(5,10);
    rcID.toCharArray(rcID2,6);

    if (rcCommand == "ON")RCsender.switchOn(rcID1, rcID2);
    if (rcCommand == "OFF")RCsender.switchOff(rcID1, rcID2);
    rcID = "";
  } 
  // if (rcID.length() == 10)

  if (rcID.length() == 5) {

#ifdef DEBUG
    Serial.println("ID : " + rcID);
#endif

    rcID.toCharArray(rcID1,6);
    rcID = rcID.substring(5,10);
    rcID.toCharArray(rcID2,6);

    if (rcCommand == "ON") RCsender.switchOn(rcID1, 1);
    if (rcCommand == "OFF")RCsender.switchOff(rcID1, 1);

    rcID = "";
  } 
  // if (rcID.length() == 6)

  // --------------------------------
  // Temperatur
  // --------------------------------
  if (TEMPtimer <= TEMPendTimer) TEMPtimer++;
  else {
    TEMPtimer = 0;
    sensors.requestTemperatures();

    // Bei Änderungen zum vorherigen Wert Publizieren
    if (tempC != sensors.getTempCByIndex(0) ) {
      tempC = sensors.getTempC(insideThermometer);
      char tempbuf[4];
      dtostrf(tempC, 2, 2, tempbuf);
      //client.connect("Arduino-Mega");
      MQTTClient.publish("/haus/moritz/temperatur", tempbuf);

#ifdef DEBUG
      Serial.println("Temp: " + tempbuf);
#endif

    } // TEMPSensor

  }  // else TEMPtimer

  // --------------------------------
  // MQTT Client Loop
  // --------------------------------
  MQTTClient.loop(); // Schleife für MQTT

}

//================================================================================================================
// Callback Funktion von MQTT. Die Funktion wird aufgerufen
// wenn ein Wert empfangen wurde.
//================================================================================================================
void callback(char* topic, byte* payload, unsigned int length) {

  TEMPtimer = 0; // Wenn eine Nachricht kommt wird der Timer
  // für die Temperatur auf 0 gesetzt um den Ablauf nicht zu Verzögern

  // Zähler
  int i = 0;
  // Hilfsvariablen für die Convertierung der Nachricht in ein String
  char message_buff[100];

  // Kopieren der Nachricht und erstellen eines Bytes mit abschließender \0
  for(i=0; i<length; i++) {
    message_buff[i] = payload[i];
  }
  message_buff[i] = '\0';

  // Konvertierung der nachricht in ein String
  String msgString = String(message_buff);
  String topString = String(topic);

#ifdef Debug
  Serial.println("Topic: " + topString);
  Serial.println("Msg : " + msgString);
#endif

  // --------------------------------
  // RC Steckdose
  // --------------------------------
  // Wenn das Topic mit "/haus/RC/" anfängt dann ID und Message ermitteln und schalten
  if (topString.startsWith("/haus/RC/")) {
    rcID = topString.substring(topString.lastIndexOf('/') + 1);
    rcCommand = msgString;
  } // if (topString.startsWith("/openHAB/wzRC/"))

  // --------------------------------
  // LED Leiste
  // --------------------------------
  // Überprüfung des Topics und setzen der Farbe je nach übermittelten Topic
  if (topString == "/haus/moritz/ledkugel/RED") LEDredValue = round(msgString.toInt() * 2.55);
  if (topString == "/haus/moritz/ledkugel/GREEN") LEDgreenValue = round(msgString.toInt() * 2.55);
  if (topString == "/haus/moritz/ledkugel/BLUE") LEDblueValue = round(msgString.toInt() * 2.55);

} // void callback()

Hat keiner eine Idee, was ich hier übersehe ?

Was sagt das LOG aus OpenHab? Kommen da Befehle an?
Könnte ein RAM Problem sein, deswegen wollt ich nicht mit dem MQTT anfangen und hab Modbus genommen.

Ja im Event log stehen die Events für die Temperatur und Funk drin.

Ich denke ich werde einfach im Arduino Code alle 4 Stunden den Code neu starten mit:

void (softReset){
asm volatile ("  jmp 0");
}

Das funktioniert mehr oder weniger, ist aber kein vollständiger Reset! Das springt in den Reset-Vektor und startet das Programm von vorne, aber die Register werden dabei nicht auf 0 zurückgesetzt.
Ein anderer Weg ist einen Funktionszeiger aufzurufen der NULL ist. Das macht das gleiche.

Man kann auch einen I/O Pin auf den Reset Pin legen:

Einen richtigen Software-Reset löst man aus indem man den Watchdog Timer started und in eine Endlosschleife geht:
http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_softreset
http://www.xappsoftware.com/wordpress/2013/06/24/three-ways-to-reset-an-arduino-board-by-code/

Der soft Reset funktioniert sehr gut, da ich jetzt keine Probleme mehr habe, jedoch bleibt immer noch die Frage offen wodurch diese Probleme auftreten.

Würd mal frech behaupten, wegen try and error.

Was ich auf die Schnelle in deinem Code für die Loop gesehen habe:

message_buff[i] = '\0';

Das schreibt unter Umständen hinter dein alloziiertes Array weil nach der Schleife das i nochmal eins hochgezählt wird. Das könnte zu Abstürzen führen.

Edit Ich sehe gerade noch, dass du die Länge der Nachricht überhaupt nicht abfängst sondern einfach die Payload in den alloziierten Buffer von 100 Bytes kopierst. Auch wenn die Länge viel größer ist!! Damit zerschießt du dir dein ganzes Programm. Du musst das Kopieren der Payload auf 99 Bytes begrenzen.

Gruß
Jarny

PS:
Ausserdem vergleicht man Strings in C nicht mit dem == Operator sondern mit strcmp

Jarny:
Ausserdem vergleicht man Strings in C nicht mit dem == Operator sondern mit strcmp

C Strings ja. Aber er hat die Arduino String Klasse drin. Ist zwar nicht empfehlenswert, aber es da geht es mit ==

Wenn schon String Klasse, dann sollte man aber auf toCharArray() verzichten und statt dessen c_str() nehmen, wenn man nur lesend auf den String zugreifen muss:
http://www.cplusplus.com/reference/string/string/c_str/

Das wurde erst später hinzugefügt ist ist dummerweise nicht dokumentiert. Aber es ist unendlich besser, da man einfach einen Zeiger auf das interne Array bekommt (den man nicht verändern kann) und nicht extra ein Array anlegen muss

Jarny:
Was ich auf die Schnelle in deinem Code für die Loop gesehen habe:

message_buff[i] = '\0';

Das schreibt unter Umständen hinter dein alloziiertes Array weil nach der Schleife das i nochmal eins hochgezählt wird. Das könnte zu Abstürzen führen.

Edit Ich sehe gerade noch, dass du die Länge der Nachricht überhaupt nicht abfängst sondern einfach die Payload in den alloziierten Buffer von 100 Bytes kopierst. Auch wenn die Länge viel größer ist!! Damit zerschießt du dir dein ganzes Programm. Du musst das Kopieren der Payload auf 99 Bytes begrenzen.

Gruß
Jarny

PS:
Ausserdem vergleicht man Strings in C nicht mit dem == Operator sondern mit strcmp

Frage: Wie setze ich das im Code um ... habe es gerade probiert und sehe nur Fehler ... wahrscheinlich weil ich es falsch gemacht habe

Außerdem an Serenifly: Das mit dem c_str() verstehe ich auch nicht so ganz

Einfach length - 1 machen

for(i=0; i < length - 1; i++)

Dann wird das letzte Byte nicht beschrieben

Das mit dem c_str() verstehe ich auch nicht so ganz

Es steht doch dort:

Returns a pointer to an array that contains a null-terminated sequence of characters (i.e., a C-string) representing the current value of the string object.

Anstatt also extra ein Array anzulegen und den Inhalt des String Objekts mit toCharArray() da reinzukopieren, hast du direkt lesenden Zugriff auf das interne Array des Objekts.

Statt dem hier:

char rcID1[6];
rcID.toCharArray(rcID2,6);

if (rcCommand == "ON") RCsender.switchOn(rcID1, 1);

Kannst du also einfach das machen:

if (rcCommand == "ON") RCsender.switchOn(rcID.c_str(), 1);

c_str() ist die Standard C++ Funktion um aus einem String Objekt einen C-String zu machen. Die Arduino Macher haben aber gemeint sie müssen da wieder was besonderes machen und haben die Sache dabei für lesenden Zugriff unnötig kompliziert gemacht und vor allem so dass man extra Speicher braucht. Später hat dann auch jemand c_str() implementiert, aber in der Doku ist es leider nicht aufgeführt.

Das spielt bei 12 Bytes zwar keine so große Rolle (die String Klasse selbst ist da viel schlimmer), aber es ist trotzdem einfacher.

Serenifly:
Kannst du also einfach das machen:

if (rcCommand == "ON") RCsender.switchOn(rcID.c_str(), 1);

c_str() ist die Standard C++ Funktion um aus einem String Objekt einen C-String zu machen. Die Arduino Macher haben aber gemeint sie müssen da wieder was besonderes machen und haben die Sache dabei für lesenden Zugriff unnötig kompliziert gemacht und vor allem so dass man extra Speicher braucht. Später hat dann auch jemand c_str() implementiert, aber in der Doku ist es leider nicht aufgeführt.

Das spielt bei 12 Bytes zwar keine so große Rolle (die String Klasse selbst ist da viel schlimmer), aber es ist trotzdem einfacher.

Wenn ich das so mache bekomme ich diese Fehlermeldung:

In file included from Basestation.ino:3:
PubSubClient.h:72: warning: '__progmem__' attribute ignored
Basestation.ino: In function 'void loop()':
Basestation.ino:179: warning: deprecated conversion from string constant to 'char*'
Basestation.ino:184: warning: deprecated conversion from string constant to 'char*'
Basestation.ino:187: warning: deprecated conversion from string constant to 'char*'
Basestation:247: error: call of overloaded 'switchOn(const char*, int)' is ambiguous
/Users/moritzwirger/Documents/Arduino/libraries/RCSwitch/RCSwitch.h:61: note: candidates are: void RCSwitch::switchOn(int, int) <near match>
/Users/moritzwirger/Documents/Arduino/libraries/RCSwitch/RCSwitch.h:63: note:                 void RCSwitch::switchOn(char*, int) <near match>
/Users/moritzwirger/Documents/Arduino/libraries/RCSwitch/RCSwitch.h:67: note:                 void RCSwitch::switchOn(char*, char*) <near match>
/Users/moritzwirger/Documents/Arduino/libraries/RCSwitch/RCSwitch.h:69: note:                 void RCSwitch::switchOn(char, int) <near match>
Basestation.ino:266: warning: deprecated conversion from string constant to 'char*'
Basestation.ino:280: warning: comparison between signed and unsigned integer expressions
Basestation.ino: In function 'void callback(char*, byte*, unsigned int)':
Basestation.ino:305: warning: comparison between signed and unsigned integer expressions

Aua. Das ist sehr doof. Dann kommt die Lib nicht damit zurecht dass das ein Zeiger auf const Daten ist. Das mit dem "ambigious overloaded functions" ist wahrscheinlich eher eine Folge-Fehler. Dass er da so viel meckert ist sehr verwirrend und erschwert die Fehlersuche. Liegt aber wahrscheinlich nur daran, dass c_str() eben einen const char* liefert (da man den String nicht verändern darf).

Ich war davon ausgegangen dass das gehen müsste, weil es z.B. mit Serial geht. Aber wenn man nachschaut, dann ist der Parameter der entsprechenden print() Funktion const.

Das würde aber auch nicht passieren wenn Programmierer (gerade bei Libraries) bei Zeigern mehr auf const-correctness achten würden. Anders herum (einen char* an eine Funktion übergeben die einen const char* nimmt), geht es nämlich.

Sorry für die Verwirrung :frowning:

Wäre es denn möglich die Lib anzupassen ?

Serenifly:
Sorry für die Verwirrung :frowning:

Nicht schlimm ... man will ja auch was lernen :smiley:

Außerdem seitdem ich

for(i=0; i < length; i++)

zu

for(i=0; i < length - 1; i++)

geändert habe fehlt bei der ankommenden Nachricht ein Zeichen oder Buchstabe

ARG! Dann wieder weg. Geht so nicht, da sich length auf die Quelle bezieht. Boundary check dann vielleicht so:

for(i=0; i < length && i < sizeof(message_buff) - 1; i++)

Wäre es denn möglich die Lib anzupassen ?

Theoretisch ja. Die char* Parameter der Methoden auf const char* ändern. Das sein mehrere da die Methoden überladen sind und die Argumente an andere Methoden weitergereicht werden.

Aber besser und einfacher ist es man verzichtet auf die String Klasse und verwendet gleich C Strings. Dann muss man auch nichts wandeln :slight_smile:

Serenifly:
Aber besser und einfacher ist es man verzichtet auf die String Klasse und verwendet gleich C Strings. Dann muss man auch nichts wandeln :slight_smile:

Wäre das umsetzbar und wenn ja wie ?

PS: Danke für die super Hilfe Serenifly :smiley: