Contact less boat tank monitor, help with libraries for newbie?

Hi,

we have assembled a Tanksensor (NMEA 0183) with WiFi similar to this:
https://open-boat-projects.org/en/di...standsmessung/

according to this manual (in German):
https://open-boat-projects.org…/Funk...ensor-deV2.pdf

To be continued below, new users can only add two links per post due to spam prevention I guess...

Arduino 1.8.19 is installed & the wemos-d1-mini-pro board has been installed and is recognized too.

The sensor lights up constantly blue after few initial blue flashes & the Wemos is flashing once when 12v is applied.
So far that seems right.

WiFi of the Wemos does not show yet, which might be due to missing libraries.

I have an issue with installing the necessary libraries from the library manager.
According to the instructions I shall install the following:

ESP8266WiFi.h GitHub - esp8266/Arduino: ESP8266 core for Arduino

DNSServer.h Standard Arduino IDE

ESP8266WebServer.h Standard Arduino IDE

WiFiManager.h

WiFiUdp.h Standard Arduino IDE

SoftwareSerial.h Standard Arduino IDE

MovingAverage.h Standard Arduino IDE

DS1603L.h

https:///github.com/wvmarle/Arduino_DS1603L

When I search for those from the Library Manager in the Arduino software, I get a lot of results but not those specific files.
Also, how do I install the additional files from the Github on the Arduino?

Is there someone over here in the forum who can help and/or perhaps share the complete compiled binary I need to Flash on my Wemos Arduino?

I guess I can do the calibration than and integrate it with OpenCPN.
Our tank is simple, it's a vertical stainless 120l drum.
We run OpenCPN on a PC with windows 10.

I also asked for help on the German forum, they were kind and helpful, but the solution is pending, as far as I understand the guy who has created the instructions has gone cruising and is offline....
I'm very very much a newbie when it comes to Arduino programming.
We only need and like our "budget" style contact less tank monitor to run, preferably without going full depth into Arduino programming.

Appreciate your help!!

Franziska

This wireless-tanksensor is another one of that many many projects where the uploader
was too lazy to provide enough information that a newcomer would be able to reproduce the project. And was too lazy to provide a pre-compiled binary file with the firmware.

I was able to compile the source-code.
Though uploading a pre-compiled firmware is not as easy as

  • start flashtool
  • choose firmware-file
  • click flash-button.

I have no experience using the ESP-flash-tool manually and as far as I have done a quick cross-reading some details like chrystal-frequency flash-roms-size must be adjusted to use it. Not newcomer-friendly.

Maybe there exists somewhere a newcomer-friendly flashtool but I haven't found it.

Yes indeed there is an easy to use flash-tool.exe see post # 5

So it seems to be easier to add the libraries and ESP8266-board-support to the Arduino-IDE
The main thing to make it work with the Arduino-IDE is to install ESP8266-Board-support which is something different than just installing libraries.
A detailed description how to install ESP8266-boards is here

This will install these libraries as they are inlcuded in the ESP8266-core-sourcecode-file

ESP8266WiFi.h
DNSServer.h
ESP8266WebServer.h
WiFiUdp.h
SoftwareSerial.h

Some of the libraries must be installed with a different function called adding ZIP-libraries.

It took me quite some time to find the MovingAverage-library as the author did not provide a GitHub-Link.
I had to do a pretty specific search for the function-names ."push" and ."get" that the tank-code
uses to finally find a similar library called MovingAVeragePlus

downloading ZIP-libraries
MovingAveragePlus.h

WiFiManager.h

DS1603L.h

Here is a short tutorial how to install ZIP-libraries

As I have only found the MovingAveragePlus-library a filename and an object name must be changed.
#include <MovingAveragePlus.h>
MovingAveragePlus hoehe_D(10); //Gleitender Durchschnitt aus 10 Werten

The original source-code from here
https://open-boat-projects.org/wp-content/uploads/2020/08/Ultraschall_Fuellstaandssensor_Firmware.zip

uses IO-pin names "D3" "D4" that require to choose the exact right ESP8266-board that has this IO-pin names
I changed that to the general valid integernumbers

const byte txPin = 0;// D3;                               // On Wemos D1 mini D3 is GPIO 0 tx of the Wemos to rx of the sensor
const byte rxPin = 2;// D4;                               // On Wemos D1 mini D4 is GPIO 2 rx of the Wemos to tx of the sensor

There is still a flaw in the code that might affect stability if the code is running for a long time. The code uses variable-type String which are global defined. There is a danger that assigning new values to such string-variables eats up all RAM-memory over time which will make the code crash.
Anyway here is the code-version with variable-type String that compiles
Compiling does not mean tested in real life.
This is what you have to do

/*
  Es werden Sensorwerte vom Ultraschallsensor DS1603L erfasst und mittels WiFi als NMEA Stream per UDP in das Netzwerk gesendet.
  Die Übertragung erfolgt auf die örtliche Broadcastadresse xxx.xxx.xxx.255 an den Port 50000.

  Nach dem erstmaligen Start oder bei Start in einem Netzwerk welches noch nicht bekannt ist erfolgt der
  Start mit einer Konfigurationsseite und Timeout
  Nach Ablauf von Timeout wird das WiFi-Modem für 3 Minuten abgeschaltet und dann erfolgt reset Wemos und Neustart mit Konfigurationsseite

  Geht die WiFi-Verbindung verloren wird die sichere Anmeldeseite aufgerufen, nach Timeout erfolgt reset Wemos und die normale Anmeldeschleife wie zuvor beschrieben startet.

  Automatische Wiederverbindung bei Wiederkehr WiFi ohne Neuverbindung

  Die Übertragung der Daten erfolgt über NMEA per UDP an BroadcastIP port 50000.
  Es werden 4 Pakete gesendet damit die auch wirklich nach dem aufwachen ankommen.

  Sensoreinbindung
  Die Sensorwerte werden in der Unterroutine alle 5 Sekunden auslesen.
  Die Sensorwerte werden in ein Schieberegister zum gleitenden Durchschnitt gegeben, jeweils 10 Werte bilden Durchschnitt, Register als FIFO (first In / first out)
  Es wird ein einfacher gleitender Durchschnitt gebildet.

  Der Datenstrom kann in OpenCPN eingelesen werden.
  Dazu unter Verbindungen eine neue Netzwerkverbindung einrichten, die Adresse ist die Broadcastadresse des Netzwerks (letzten drei Stellen .255) und der Port ist 50000
  Es wird eine XDR-Sequenz ausgegeben, die dann mit dem Enginedashboard-Plugin in Opencpn ausgelesen werden kann. Verschiedene Tanktypen können durch Anpassen von "FUEL" in der Unterroutine erfasst werden.
  Dazu bitte die Dokumentation vom EngineDashboard-Plugin lesen.


  adapted from Ethernet library examples
*/


#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>
#include <DS1603L.h>              //https://github.com/wvmarle/Arduino_DS1603L
#include <SoftwareSerial.h>
#include <MovingAveragePlus.h>

//Einstellungen für broadcasting
unsigned int portBroadcast = 50000;      // localer port an den gesendet wird
unsigned int broadCast = 0;

//Variablen für Timer um Sensorwerte zu lesen
unsigned long Timer_RX = 0;
long Timeout_RX = 5000;                // Intervall in ms hier 5000ms = 5s

// Angabe wie der Sensor angeschlossen ist. Nutze Dx statt x für Pin-Angabe wenn Wemos benutzt wird
//#define D3 0
//#define D4 2
const byte txPin = 0;// D3;                               // On Wemos D1 mini D3 is GPIO 0 tx of the Wemos to rx of the sensor
const byte rxPin = 2;// D4;                               // On Wemos D1 mini D4 is GPIO 2 rx of the Wemos to tx of the sensor
SoftwareSerial sensorSerial(rxPin, txPin);

// If your sensor is connected to Serial, Serial1, Serial2, AltSoftSerial, etc. pass that object to the sensor constructor.
DS1603L sensor(sensorSerial);

//WiFiUDP
WiFiUDP Udp;

//WiFiManager
WiFiManager wifiManager;

//Variable für Ergebnis Sensorwert
int hoehe = 0;

//Für Berechnungen Prozentwert als Ausgabe
String Fuell = "0";

// Definition eines Arrays von 10 Feldern für gleitenden Durchschnitt
MovingAveragePlus<unsigned> hoehe_D(10);    //Gleitender Durchschnitt aus 10 Werten

// Zum Senden des NMEA-Strings nötig Um die richtige Variable-Form zu bilden
char XDR;
String XDR1;

void setup() {

  Serial.begin(115200);
  Serial.println();

  sensorSerial.begin(9600);                         // Sensor transmits its data at 9600 bps.
  sensor.begin();                                   // Initialise the sensor library.

  //reset settings - zum Testen
  //wifiManager.resetSettings();

  //Timeout in sek., nach Ablauf wird die Setup-Seite ausgeschaltet
  wifiManager.setTimeout(120);


  //Automatische Startseite und nach Timeout (wifimanager.setTimeout) erfolgt reset
  if (!wifiManager.autoConnect("SSID", "Password")) {  //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
    Serial.println("failed to connect, shut down WiFi-Modem for 3 Minutes then reset Wemos");
    //Ausschalten WiFi-Modem
    WiFi.forceSleepBegin();
    delay(1);
    delay(180000);             //Warte 3 Minuten, in dieser Zeit ist das WiFi-Modul abgeschaltet
    ESP.reset();
  }

  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");
  Serial.println("local ip");
  Serial.println(WiFi.localIP());
}

void loop() {

  hoehe = Sensor();  //Aufruf Subroutine Sensor für Sensorwerte
  //Serial.println("Variable hoehe:");     //Zum Test ggf. auskommentieren
  //Serial.println(hoehe);                 //Zum Test ggf. auskommentieren
  //delay (500);                           //Zum Test ggf. auskommentieren
  Berechnung(hoehe); //Sprung Unterroutine Umrechnung Höhe in % Füllgrad Tank

  //Überprüfe ob Verbindung zum Netzwerk steht oder starte Setup-Seite
  if (!wifiManager.autoConnect("SSID", "Password")) {   //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
    Serial.println("WiFi lost, reset Wemos");
    delay(3000);
    ESP.reset();
  }

  //Setze Broadcastadresse
  IPAddress broadCast = WiFi.localIP();
  broadCast[3] = 255;

  //Erstelle Datenstring zum Senden
  Data(NMEA_XDR(Fuell));
  // Wandle den String fürs Senden um
  String str = XDR1;
  //Length (with one extra character for the null terminator)
  int str_len = str.length() + 1;
  // Prepare the character array (the buffer)
  char XDR[str_len];
  // Copy it over
  str.toCharArray(XDR, str_len);
  delay(50);

  //Sendeschleife Sende vier Pakete
  for (int i = 0; i < 4; i++) {
    Udp.beginPacket(broadCast, portBroadcast); // send UDP to Port 50000 and BroadcastIP
    Udp.write(XDR);
    Udp.endPacket();
  }
  delay(100);
}

//Subroutine zur Erstellung Datensatz zum Senden per UDP
void Data(String n) {
  XDR1 = n;
}

//Subroutine um Sensorwerte zu bekommen. Sensorwerte werden in ein Schieberegister geschrieben um aus 10 Werten den gleitenden Durchschnitt zu bekommen
unsigned int Sensor () {
  if (millis() - Timer_RX > Timeout_RX) {
    Timer_RX = millis ();
    Serial.println(F("Starting reading."));
    unsigned int reading = sensor.readSensor();       // Call this as often or as little as you want - the sensor transmits every 1-2 seconds.
    byte sensorStatus = sensor.getStatus();           // Check the status of the sensor (not detected; checksum failed; reading success).
    switch (sensorStatus) {                           // For possible values see DS1603L.h
      case DS1603L_NO_SENSOR_DETECTED:                // No sensor detected: no valid transmission received for >10 seconds.
        Serial.println(F("No sensor detected (yet). If no sensor after 1 second, check whether your connections are good."));
        break;

      case DS1603L_READING_SUCCESS:                   // Latest reading was valid and received successfully.
        Serial.println(F("Reading success."));
        Serial.println(reading);
        Serial.println(F(" mm."));
        break;

      case DS1603L_READING_CHECKSUM_FAIL:             // Checksum of the latest transmission failed.
        Serial.print(F("Data received; checksum failed. Latest level reading: "));
        break;

    }
    hoehe_D.push(reading);                            //Schieberegister zur Mittelwertbildung
    unsigned int reading_D = hoehe_D.get();
    //Serial.println("Durchschnittshöhe:");           //Zum Test
    //Serial.println(reading_D);                      //Zum Test
    return reading_D;
  }
  unsigned int reading_D = hoehe_D.get();
  return reading_D;
}

//Subroutine zur Ermittlung Höhe und Umrechnung in Prozent Füllung
void Berechnung(int h) {
  int Prozent = (h / 400.00) * 100; //Testrechnung muss an Tank angepasst werden. 400 mm als Gesamthöhe Tank zum Test
  Fuell = Prozent, DEC;
  //Serial.println("Zur Kontrolle, Variablen h, Prozent und Fuell:");      //Zum Test ggf. auskommentieren
  //Serial.println(h);                                                     //Zum Test ggf. auskommentieren
  //Serial.println(Prozent);                                               //Zum Test ggf. auskommentieren
  //Serial.println(Fuell);                                                 //Zum Test ggf. auskommentieren
  //delay(500);                                                            //Zum Test ggf. auskommentieren

}

//Create NMEA String XDR
String NMEA_XDR(String Val) {
  String nmea = "$IIXDR,V,";
  nmea += Val, DEC;
  nmea += ",P,FUEL*"; //FUEL für Treibstoff, Anpassen um weitere Tanktypen zu erfassen, siehe Dokumentation EngineDashboard-Plugin OpenCPN
  nmea += String (testsum(nmea), HEX);
  //nmea += '\r';
  //nmea += '\n';
  return nmea;
}

//Calculates the checksum for the NMEA String
int testsum(String strN) {
  int i;
  int XOR;
  int c;
  // Calculate testsum ignoring any $'s in the string
  for (XOR = 0, i = 0; i < 80; i++) {                                    // strlen(strN)
    c = (unsigned char)strN[i];
    if (c == '*') break;
    if (c != '$') XOR ^= c;
  }
  return XOR;
}

So I added another library called SafeString which can be installed with the library-manager
and modified to the code to use variable-type SafeString instead of variable-type String

Same thing here: it compiles does not mean it works properly.
This must be tested by youself

/*
  Es werden Sensorwerte vom Ultraschallsensor DS1603L erfasst und mittels WiFi als NMEA Stream per UDP in das Netzwerk gesendet.
  Die Übertragung erfolgt auf die örtliche Broadcastadresse xxx.xxx.xxx.255 an den Port 50000.

  Nach dem erstmaligen Start oder bei Start in einem Netzwerk welches noch nicht bekannt ist erfolgt der
  Start mit einer Konfigurationsseite und Timeout
  Nach Ablauf von Timeout wird das WiFi-Modem für 3 Minuten abgeschaltet und dann erfolgt reset Wemos und Neustart mit Konfigurationsseite

  Geht die WiFi-Verbindung verloren wird die sichere Anmeldeseite aufgerufen, nach Timeout erfolgt reset Wemos und die normale Anmeldeschleife wie zuvor beschrieben startet.

  Automatische Wiederverbindung bei Wiederkehr WiFi ohne Neuverbindung

  Die Übertragung der Daten erfolgt über NMEA per UDP an BroadcastIP port 50000.
  Es werden 4 Pakete gesendet damit die auch wirklich nach dem aufwachen ankommen.

  Sensoreinbindung
  Die Sensorwerte werden in der Unterroutine alle 5 Sekunden auslesen.
  Die Sensorwerte werden in ein Schieberegister zum gleitenden Durchschnitt gegeben, jeweils 10 Werte bilden Durchschnitt, Register als FIFO (first In / first out)
  Es wird ein einfacher gleitender Durchschnitt gebildet.

  Der Datenstrom kann in OpenCPN eingelesen werden.
  Dazu unter Verbindungen eine neue Netzwerkverbindung einrichten, die Adresse ist die Broadcastadresse des Netzwerks (letzten drei Stellen .255) und der Port ist 50000
  Es wird eine XDR-Sequenz ausgegeben, die dann mit dem Enginedashboard-Plugin in Opencpn ausgelesen werden kann. Verschiedene Tanktypen können durch Anpassen von "FUEL" in der Unterroutine erfasst werden.
  Dazu bitte die Dokumentation vom EngineDashboard-Plugin lesen.


  adapted from Ethernet library examples
*/


#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>
#include <DS1603L.h>              //https://github.com/wvmarle/Arduino_DS1603L
#include <SoftwareSerial.h>
#include <MovingAveragePlus.h>
#include <SafeString.h>

createSafeString(Fuell, 256);
createSafeString(XDR1,  256);
createSafeString(str,   256);
createSafeString(nmea,  256);
createSafeString(strN,  256);


//Einstellungen für broadcasting
unsigned int portBroadcast = 50000;      // localer port an den gesendet wird
unsigned int broadCast = 0;

//Variablen für Timer um Sensorwerte zu lesen
unsigned long Timer_RX = 0;
long Timeout_RX = 5000;                // Intervall in ms hier 5000ms = 5s

// Angabe wie der Sensor angeschlossen ist. Nutze Dx statt x für Pin-Angabe wenn Wemos benutzt wird
//#define D3 0
//#define D4 2
const byte txPin = 0;// D3;                               // On Wemos D1 mini D3 is GPIO 0 tx of the Wemos to rx of the sensor
const byte rxPin = 2;// D4;                               // On Wemos D1 mini D4 is GPIO 2 rx of the Wemos to tx of the sensor
SoftwareSerial sensorSerial(rxPin, txPin);

// If your sensor is connected to Serial, Serial1, Serial2, AltSoftSerial, etc. pass that object to the sensor constructor.
DS1603L sensor(sensorSerial);

//WiFiUDP
WiFiUDP Udp;

//WiFiManager
WiFiManager wifiManager;

//Variable für Ergebnis Sensorwert
int hoehe = 0;


// Definition eines Arrays von 10 Feldern für gleitenden Durchschnitt
MovingAveragePlus<unsigned> hoehe_D(10);    //Gleitender Durchschnitt aus 10 Werten

// Zum Senden des NMEA-Strings nötig Um die richtige Variable-Form zu bilden
char XDR;
//String XDR1;

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestConnectionTimer = 0;                   // Timer-variables MUST be of type unsigned long
unsigned long MyMeasuringIntervallTimer = 0;                   // Timer-variables MUST be of type unsigned long

void setup() {
  //Für Berechnungen Prozentwert als Ausgabe
  Fuell = "0";

  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  PrintFileNameDateTime();

  sensorSerial.begin(9600);                         // Sensor transmits its data at 9600 bps.
  sensor.begin();                                   // Initialise the sensor library.

  //reset settings - zum Testen
  //wifiManager.resetSettings();

  //Timeout in sek., nach Ablauf wird die Setup-Seite ausgeschaltet
  wifiManager.setTimeout(120);
  Serial.println("wifiManager.setTimeout(120)");

  //Automatische Startseite und nach Timeout (wifimanager.setTimeout) erfolgt reset
  if (!wifiManager.autoConnect("SSID", "Password")) {  //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
    Serial.println("failed to connect, shut down WiFi-Modem for 3 Minutes then reset Wemos");
    //Ausschalten WiFi-Modem
    WiFi.forceSleepBegin();
    delay(1);
    delay(180000);             //Warte 3 Minuten, in dieser Zeit ist das WiFi-Modul abgeschaltet
    ESP.reset();
  }


  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");
  Serial.println("local ip");
  Serial.println(WiFi.localIP());
}

void loop() {
  if ( TimePeriodIsOver(MyTestConnectionTimer, 5000) ) {
    //Überprüfe ob Verbindung zum Netzwerk steht oder starte Setup-Seite
    if (!wifiManager.autoConnect("SSID", "Password")) {   //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
      Serial.println("WiFi lost, reset Wemos");
      delay(3000);
      ESP.reset();
    }
  }

  if ( TimePeriodIsOver(MyMeasuringIntervallTimer, 1000) ) {
    hoehe = Sensor();  //Aufruf Subroutine Sensor für Sensorwerte
    //Serial.println("Variable hoehe:");     //Zum Test ggf. auskommentieren
    //Serial.println(hoehe);                 //Zum Test ggf. auskommentieren
    //delay (500);                           //Zum Test ggf. auskommentieren
    Berechnung(hoehe); //Sprung Unterroutine Umrechnung Höhe in % Füllgrad Tank

    //Setze Broadcastadresse
    IPAddress broadCast = WiFi.localIP();
    broadCast[3] = 255;

    //Erstelle Datenstring zum Senden
    //XDR1 = NMEA_XDR(Fuell.c_str() );
    NMEA_XDR(Fuell);
    // Wandle den String fürs Senden um
    //String str = XDR1;
    str = XDR1;
    //Length (with one extra character for the null terminator)
    int str_len = str.length() + 1;
    // Prepare the character array (the buffer)
    char XDR[str_len];
    // Copy it over
    for (int i = 0; i < str_len; i++) {
      XDR[i] = str[i];
    }
    XDR[str_len] = 0; // append terminating zero
    //str.toCharArray(XDR, str_len);
    delay(50);

    //Sendeschleife Sende vier Pakete
    for (int i = 0; i < 4; i++) {
      Udp.beginPacket(broadCast, portBroadcast); // send UDP to Port 50000 and BroadcastIP
      Udp.write(XDR);
      Udp.endPacket();

      Serial.print("   Paket Nr ");
      Serial.print(i);
      Serial.print(" #");
      Serial.print(XDR);
      Serial.print("# ");
    }
    Serial.println();
  }
}

/*
  //Subroutine zur Erstellung Datensatz zum Senden per UDP
  void Data(String n) {
  XDR1 = n;
  }
*/

//Subroutine um Sensorwerte zu bekommen. Sensorwerte werden in ein Schieberegister geschrieben um aus 10 Werten den gleitenden Durchschnitt zu bekommen
unsigned int Sensor () {
  if (millis() - Timer_RX > Timeout_RX) {
    Timer_RX = millis ();
    Serial.println(F("Starting reading."));
    unsigned int reading = sensor.readSensor();       // Call this as often or as little as you want - the sensor transmits every 1-2 seconds.
    byte sensorStatus = sensor.getStatus();           // Check the status of the sensor (not detected; checksum failed; reading success).

    switch (sensorStatus) {                           // For possible values see DS1603L.h
      case DS1603L_NO_SENSOR_DETECTED:                // No sensor detected: no valid transmission received for >10 seconds.
        Serial.println(F("No sensor detected (yet). If no sensor after 1 second, check whether your connections are good."));
        break;

      case DS1603L_READING_SUCCESS:                   // Latest reading was valid and received successfully.
        Serial.println(F("Reading success."));
        Serial.println(reading);
        Serial.println(F(" mm."));
        break;

      case DS1603L_READING_CHECKSUM_FAIL:             // Checksum of the latest transmission failed.
        Serial.print(F("Data received; checksum failed. Latest level reading: "));
        break;

    }
    hoehe_D.push(reading);                            //Schieberegister zur Mittelwertbildung
    unsigned int reading_D = hoehe_D.get();
    //Serial.println("Durchschnittshöhe:");           //Zum Test
    //Serial.println(reading_D);                      //Zum Test
    return reading_D;
  }
  unsigned int reading_D = hoehe_D.get();
  return reading_D;
}

//Subroutine zur Ermittlung Höhe und Umrechnung in Prozent Füllung
void Berechnung(int h) {
  int Prozent = (h / 400.00) * 100; //Testrechnung muss an Tank angepasst werden. 400 mm als Gesamthöhe Tank zum Test
  Fuell = Prozent, DEC;
  //Serial.println("Zur Kontrolle, Variablen h, Prozent und Fuell:");      //Zum Test ggf. auskommentieren
  //Serial.println(h);                                                     //Zum Test ggf. auskommentieren
  //Serial.println(Prozent);                                               //Zum Test ggf. auskommentieren
  //Serial.println(Fuell);                                                 //Zum Test ggf. auskommentieren
  //delay(500);                                                            //Zum Test ggf. auskommentieren

}

//Create NMEA String XDR

void NMEA_XDR(SafeString& p_RefToSS) {
  nmea = "$IIXDR,V,";
  nmea += p_RefToSS;// Val, DEC;
  nmea += ",P,FUEL*"; //FUEL für Treibstoff, Anpassen um weitere Tanktypen zu erfassen, siehe Dokumentation EngineDashboard-Plugin OpenCPN
  nmea += testsum(nmea), HEX;
  //nmea += '\r';
  //nmea += '\n';
  //return nmea;
}
/*
  String NMEA_XDR(String Val) {
  String nmea = "$IIXDR,V,";
  nmea += Val, DEC;
  nmea += ",P,FUEL*"; //FUEL für Treibstoff, Anpassen um weitere Tanktypen zu erfassen, siehe Dokumentation EngineDashboard-Plugin OpenCPN
  nmea += String (testsum(nmea), HEX);
  //nmea += '\r';
  //nmea += '\n';
  return nmea;
  }
*/
//Calculates the checksum for the NMEA String
int testsum(SafeString& strN) {
  int i;
  int XOR;
  int c;
  // Calculate testsum ignoring any $'s in the string
  for (XOR = 0, i = 0; i < 80; i++) {                                    // strlen(strN)
    c = (unsigned char)strN[i];
    if (c == '*') break;
    if (c != '$') XOR ^= c;
  }
  return XOR;
}

best regards Stefan

In the meantime I have found a flashing-tool that is easy to use

Here is the compiled binary as a ZIP-file
OpenBoat-WireLess-Tank-Sensor-001.ino.generic.zip (248.0 KB)
This file must be unzipped to extract the
F:\myData\Arduino\OpenBoat-WireLess-Tank-Sensor-001\OpenBoat-WireLess-Tank-Sensor-001.ino.generic.bin

and this file must be uploaded to the Wemos D1 Mini ESP8266 board

Here is the version that is modified to use the SafeStrings and has some additonal serial output for debugging

no guarantee for both versions that they work properly
I was able to flash both versions into a ESP826-board and the serial monitor is showing messages that the code is running
OpenBoat-WireLess-Tank-Sensor-003-SafeString.ino.generic.zip (251.7 KB)

best regards Stefan

This is, easily (!), the most thorough answer I ever got in a forum. Thank you so much.
Very kind of you.

I've spent the day painting our deck today and just noticed your reply. Impressive.

I can look deeper into this once we have a new internet card, running out of data at the moment.
Hope we can get this on Monday.

Have a fabulous weekend,

Franziska

Hi Franziska,

just in case you have not read post # 5.
You can almost completely skip post #4 with the manual how to install libraries.

Post #5 explains how to flash the pre-compiled binary into the ESP8266-microcontroller so you can do a first test if it is working. There is a good chance that it will work. Though I have experienced so often that there are some minor bugs that it should be tested. Expecially the SafeString-version might have a minor bug that the data is not yet transmitted properly.

best regards Stefan

Well,

I get to the WiFi Login now. Looks like there was a faulty/loose hardware connection in our setup. So that the WiFi was not working, seems hardware related. Sorry about that!
On the startup page I gave the sensor our onboard network credentials.

I configured a new UDP connection in OpenCPN and set it up as follows:

Adress: 0.0.0.0
Port: 8888
User comment: Fuel tank sensor
Priority: 1
Receive Input (Checked)
Control checksum (Checked)
Accept only sentences: Currently set to all, only XDR changes nothing.

Still the OpenCPN NMEA debug window shows no incoming data yet.
The board flashes every 2 seconds and the sensor is placed under a stainless saucepan with water in it. The sensor is not flashing (I try some gel now in between saucepan and sensor to get it to readout).

As only this sensor is attached it show its data, no?
Engine dashboard plugin is installed & configured to display Fuel 1.

Twocan Plugin is installed but disabled, as we do not have a NMEA2000 setup.

Thoughts on how I can verify that the signals are in our network?

COMMENT:
Further testing revealed I had to set network port to 50000.

I get data now in the debug window of OpenCPN & engine dashboard.

Is there an easy way to edit the bin or main.cpp somehow to adjust the 100% tank height to 720mm instead of the default 400mm?
Or do I need to recompile it for this?
Could I perhaps carefully ask you to be so kind to adjust this for me and reupload the safe string version with the 720mm? That would be absolute awesome!!!

Hi Franziska,

Yes you can. Should be pretty easy to change that.

As you are asking for the SafeString-Version:
does this mean that the SafeString-Version does send - in principle - correct data except for that 100% is reached too early as the code thinks 400 mm is 100% ?

I was unsure about that because I had to write some modifications to make it compile again.

EDIT:

I can add debug-options to the code. There are different options how to activate this debug-output:

  • using yet unused IO-pins with a switch. Depending on the switch-position debug-out-put is activated or not
  • sending serial commands to permanently or temporarily activate debug-output

EDIT:2 I started testing the NMEA-String-creation. There is stil something wrong with this inside the SafeString-version anaylsing it .....
best regards Stefan

1 Like

Hi,

thanks again!

We could not test a lot of different levels over here yet.
I want to, but our saucepans are to thick at the bottom and the Espressocan bottom part works fine, but is not high enough.

Will look for another receptacle tomorrow in the yard.

I chose the safe string version because it sounded logic what you did.
Btw. I have a parallel discussion (in German) going on over here:

Might be worth joining forces somehow with Norbert.

What would be great from a user standpoint would be an option to change the tank height without rewriting & flashing the code.
That way the enduser can get the hardware, solder it together key in her tank height and link it to OpenCPN.
Not sure if that is possible, most likely very involved and not worth the hassle. Maybe its better if I learn to compile this code new after modifying that tank height number.

I'm a typical user with little to none programming skills, so a great newbie Guinea pig :wink: to try things.
Having only a very modest budget forces me to use solutions like this. Still, I like it too because you always learn something new from it.

I'm German too, and yes we could swap to WhatsApp or the like, but than it will be hard for others to follow this later, no?

Have a nice evening! I'm grateful for your help!

Bad news, it is probably not working correctly.

I just used a cylindrical plastic Spaghetti Tupperware with washing up liquid between the Tupperware and the sensor.
The sensor is flashing every other second, as is the Wemos. The dial at OpenCPN is at a little less than 1/4 full, but OpenCPN shows no change if we increase the water column from 100mm to about 250mm.
Still, if I remove the Tupperware with the water the dial goes to zero, as it should.

So, I guess something is not yet working correctly.

Here is the original info on the sensor:
https://open-boat-projects.org/de/diy-ultraschall-fuellstandsmessung/

Hmm, guess more investigations ahead.

In the OpenCPN NMEA debug window I get the same values all the time, so thats not right.

22:06:34 (UDP:0.0.0.0:50000) $IIXDR,V,15,P,FUEL56<0x0D><0x0A>
22:06:34 (UDP:0.0.0.0:50000) $IIXDR,V,15,P,FUEL
56<0x0D><0x0A>
22:06:34 (UDP:0.0.0.0:50000) $IIXDR,V,15,P,FUEL56<0x0D><0x0A>
22:06:34 (UDP:0.0.0.0:50000) $IIXDR,V,15,P,FUEL
56<0x0D><0x0A>

Yes I'm working on it. I found the main reason why it did not work in the SafeString-Version still need to do some more tests.

For reference if you like and it is easy to do
you could flash the String-version (the not SafeString-Version)
and then post what you get in the OpenCPN NMEA debug window

best regards Stefan

New Code-Version 007 that send NMEA-Data
If this NMEA-data complies in all details with the definition ( specs - I can't say.

best thing would be to have some valid examples for this kind of NMEA-Data inside the source-code and a link to the NMEA-specs.

I added some code.
At the top if the file is a new constant for tank-height

float maxHeightOfTank = 720;

If there is no valid sensor-reading the value is set to maxHeightOfTank
There is still some potential to clean-up the code. The original code does some redundant or just unescessary things with the NMEA-data.

Also I'm wondering why the code is sending four times in a row the same data-packet.

#define CodeVersion "Code-Version 007"
// Absolutwert der Höhe der in der Umrechnung in Prozent benutzt wird
float maxHeightOfTank = 720;

const boolean DummySensorActive = false;//true;
unsigned int dummyHoehe = 50;
/*
  Es werden Sensorwerte vom Ultraschallsensor DS1603L erfasst und mittels WiFi als NMEA Stream per UDP in das Netzwerk gesendet.
  Die Übertragung erfolgt auf die örtliche Broadcastadresse xxx.xxx.xxx.255 an den Port 50000.

  Nach dem erstmaligen Start oder bei Start in einem Netzwerk welches noch nicht bekannt ist erfolgt der
  Start mit einer Konfigurationsseite und Timeout
  Nach Ablauf von Timeout wird das WiFi-Modem für 3 Minuten abgeschaltet und dann erfolgt reset Wemos und Neustart mit Konfigurationsseite

  Geht die WiFi-Verbindung verloren wird die sichere Anmeldeseite aufgerufen, nach Timeout erfolgt reset Wemos und die normale Anmeldeschleife wie zuvor beschrieben startet.

  Automatische Wiederverbindung bei Wiederkehr WiFi ohne Neuverbindung

  Die Übertragung der Daten erfolgt über NMEA per UDP an BroadcastIP port 50000.
  Es werden 4 Pakete gesendet damit die auch wirklich nach dem aufwachen ankommen.

  Sensoreinbindung
  Die Sensorwerte werden in der Unterroutine alle 5 Sekunden auslesen.
  Die Sensorwerte werden in ein Schieberegister zum gleitenden Durchschnitt gegeben, jeweils 10 Werte bilden Durchschnitt, Register als FIFO (first In / first out)
  Es wird ein einfacher gleitender Durchschnitt gebildet.

  Der Datenstrom kann in OpenCPN eingelesen werden.
  Dazu unter Verbindungen eine neue Netzwerkverbindung einrichten, die Adresse ist die Broadcastadresse des Netzwerks (letzten drei Stellen .255) und der Port ist 50000
  Es wird eine XDR-Sequenz ausgegeben, die dann mit dem Enginedashboard-Plugin in Opencpn ausgelesen werden kann. Verschiedene Tanktypen können durch Anpassen von "FUEL" in der Unterroutine erfasst werden.
  Dazu bitte die Dokumentation vom EngineDashboard-Plugin lesen.

  adapted from Ethernet library examples
*/

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);

#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>
#include <DS1603L.h>              //https://github.com/wvmarle/Arduino_DS1603L
#include <SoftwareSerial.h>
#include <MovingAveragePlus.h>
#include <SafeString.h>

createSafeString(Fuell, 256);
createSafeString(XDR1,  256);
createSafeString(str,   256);
createSafeString(nmea,  256);
createSafeString(strN,  256);


//Einstellungen für broadcasting
unsigned int portBroadcast = 50000;      // localer port an den gesendet wird
unsigned int broadCast = 0;

//Variablen für Timer um Sensorwerte zu lesen
unsigned long Timer_RX = 0;
long Timeout_RX = 5000;                // Intervall in ms hier 5000ms = 5s

// Angabe wie der Sensor angeschlossen ist. Nutze Dx statt x für Pin-Angabe wenn Wemos benutzt wird
//#define D3 0
//#define D4 2
const byte txPin = 0;// D3;                               // On Wemos D1 mini D3 is GPIO 0 tx of the Wemos to rx of the sensor
const byte rxPin = 2;// D4;                               // On Wemos D1 mini D4 is GPIO 2 rx of the Wemos to tx of the sensor
SoftwareSerial sensorSerial(rxPin, txPin);

// If your sensor is connected to Serial, Serial1, Serial2, AltSoftSerial, etc. pass that object to the sensor constructor.
DS1603L sensor(sensorSerial);

//WiFiUDP
WiFiUDP Udp;

//WiFiManager
WiFiManager wifiManager;

//Variable für Ergebnis Sensorwert
int hoehe = 0;


// Definition eines Arrays von 10 Feldern für gleitenden Durchschnitt
MovingAveragePlus<unsigned> hoehe_D(10);    //Gleitender Durchschnitt aus 10 Werten

// Zum Senden des NMEA-Strings nötig Um die richtige Variable-Form zu bilden
char XDR;

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
  Serial.println( F(CodeVersion) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestConnectionTimer = 0;                   // Timer-variables MUST be of type unsigned long
unsigned long MyMeasuringIntervallTimer = 0;                   // Timer-variables MUST be of type unsigned long

void setup() {
  //Für Berechnungen Prozentwert als Ausgabe
  Fuell = "0";

  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  PrintFileNameDateTime();

  sensorSerial.begin(9600);                         // Sensor transmits its data at 9600 bps.
  sensor.begin();                                   // Initialise the sensor library.

  //reset settings - zum Testen
  //wifiManager.resetSettings();

  //Timeout in sek., nach Ablauf wird die Setup-Seite ausgeschaltet
  wifiManager.setTimeout(120);
  Serial.println("wifiManager.setTimeout(120)");

  //Automatische Startseite und nach Timeout (wifimanager.setTimeout) erfolgt reset
  if (!wifiManager.autoConnect("SSID", "Password")) {  //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
    Serial.println("failed to connect, shut down WiFi-Modem for 3 Minutes then reset Wemos");
    //Ausschalten WiFi-Modem
    WiFi.forceSleepBegin();
    delay(1);
    delay(180000);             //Warte 3 Minuten, in dieser Zeit ist das WiFi-Modul abgeschaltet
    ESP.reset();
  }


  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");
  Serial.println("local ip");
  Serial.println(WiFi.localIP());
}

void loop() {
  if ( TimePeriodIsOver(MyMeasuringIntervallTimer, 1000) ) {
    hoehe = Sensor();  //Aufruf Subroutine Sensor für Sensorwerte
    dbg("1:",hoehe);                 //Zum Test ggf. auskommentieren
    Berechnung(hoehe); //Sprung Unterroutine Umrechnung Höhe in % Füllgrad Tank

    //Setze Broadcastadresse
    IPAddress broadCast = WiFi.localIP();
    broadCast[3] = 255;

    //Erstelle Datenstring zum Senden
    //XDR1 = NMEA_XDR(Fuell.c_str() );

    NMEA_XDR(Fuell,nmea); // erzeuge aus "Fuell" die nmea-daten
    
    // Wandle den String fürs Senden um
    //String str = XDR1;
    str = XDR1;
    //Length (with one extra character for the null terminator)
    int str_len = str.length() + 1;
    // Prepare the character array (the buffer)
    char XDR[str_len];
    // Copy it over
    for (int i = 0; i < str_len; i++) {
      XDR[i] = str[i];
    }
    XDR[str_len] = 0; // append terminating zero
    //str.toCharArray(XDR, str_len);
    delay(50);

    //Sendeschleife Sende vier Pakete
    for (int i = 0; i < 4; i++) {
      Udp.beginPacket(broadCast, portBroadcast); // send UDP to Port 50000 and BroadcastIP
      Udp.write(XDR);
      Udp.endPacket();

      Serial.print("   Paket Nr ");
      Serial.print(i);
      Serial.print(" #");
      Serial.print(XDR);
      Serial.print("# ");
    }
    Serial.print("UDP-Send to ");
    Serial.print(WiFi.localIP());
    Serial.print(" Port ");
    Serial.println(portBroadcast);
    
    Serial.println();
  }

  if ( TimePeriodIsOver(MyTestConnectionTimer, 10000) ) {
    //Überprüfe ob Verbindung zum Netzwerk steht oder starte Setup-Seite
    if (!wifiManager.autoConnect("SSID", "Password")) {   //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
      Serial.println("WiFi lost, reset Wemos");
      delay(3000);
      ESP.reset();
    }
  }

}


//Subroutine um Sensorwerte zu bekommen. Sensorwerte werden in ein Schieberegister geschrieben um aus 10 Werten den gleitenden Durchschnitt zu bekommen
unsigned int Sensor () {
  static unsigned int reading_D;
  reading_D = hoehe_D.get();

  if (millis() - Timer_RX > Timeout_RX) {
    Timer_RX = millis ();
    Serial.println(F("Starting reading."));
    unsigned int reading = sensor.readSensor();       // Call this as often or as little as you want - the sensor transmits every 1-2 seconds.
    if (reading > maxHeightOfTank) { // if value not valid set to max
      reading = maxHeightOfTank;    
    }
    byte sensorStatus = sensor.getStatus();           // Check the status of the sensor (not detected; checksum failed; reading success).

    switch (sensorStatus) {                           // For possible values see DS1603L.h
      case DS1603L_NO_SENSOR_DETECTED:                // No sensor detected: no valid transmission received for >10 seconds.
        if (DummySensorActive) {
          Serial.println( F("DummySensor active") );
        }
        else {
          Serial.println(F("No sensor detected (yet). If no sensor after 1 second, check whether your connections are good."));
        }
        break;

      case DS1603L_READING_SUCCESS:                   // Latest reading was valid and received successfully.
        Serial.println(F("Reading success."));
        Serial.println(reading);
        Serial.println(F(" mm."));
        break;

      case DS1603L_READING_CHECKSUM_FAIL:             // Checksum of the latest transmission failed.
        Serial.print(F("Data received; checksum failed. Latest level reading: "));
        break;

    }
    if (!DummySensorActive) {
      dbg("Sensor",reading);
      hoehe_D.push(reading);                            //Schieberegister zur Mittelwertbildung
    }      
    //unsigned int reading_D = hoehe_D.get();
    //Serial.println("Durchschnittshöhe:");           //Zum Test
    //Serial.println(reading_D);                      //Zum Test
    //return reading_D;
  }
  if (DummySensorActive) {
    dummyHoehe += 10;
    if (dummyHoehe > maxHeightOfTank) {
      dummyHoehe = 50;
    }
    reading_D = dummyHoehe;
  }
  else {
    reading_D = hoehe_D.get();
  }
  return reading_D;
}


//Subroutine zur Ermittlung Höhe und Umrechnung in Prozent Füllung
void Berechnung(unsigned int h) {
  //dbg("Berechnung",h);  
  int Prozent = (h / maxHeightOfTank) * 100; //Testrechnung muss an Tank angepasst werden. 400 mm als Gesamthöhe Tank zum Test
  //dbg("Berechnung",Prozent );  
  Fuell = Prozent;
  dbg("Berechnung",Fuell);
}


//Create NMEA String XDR

void NMEA_XDR(SafeString& p_Fuell, SafeString& p_RefToSS) {
  //dbg("NMEA_XDR 1:",p_Fuell);
  nmea = "$IIXDR,V,";
  nmea += p_Fuell;// Val, DEC;
  nmea += ",P,FUEL*"; 
  dbg("NMEA_XDR 2:",nmea);
  //nmea += testsum(nmea), HEX;
  nmea.print(testsum(nmea), HEX);
  //dbg("NMEA_XDR 3:",nmea);
  XDR1 = nmea;
  //dbg("NMEA_XDR 4:",XDR1);
}

//Calculates the checksum for the NMEA String
int testsum(SafeString& strN) {
  int i;
  int XOR;
  int c;
  // Calculate testsum ignoring any $'s in the string
  for (XOR = 0, i = 0; i < 80; i++) {                                    // strlen(strN)
    c = (unsigned char)strN[i];
    if (c == '*') break;
    if (c != '$') XOR ^= c;
  }
  return XOR;
}

And here as a pre-compiled *.bin-file
OpenBoat-WireLess-Tank-Sensor-007-SafeString.ino.generic.zip (251.9 KB)

best regards Stefan

1 Like

@StefanL38 was this reply intended to be made on a different forum topic?

1 Like

Hi @in0
Thank you for asking and giving a heads-up.

Yes indeed. I posted in the wrong thread. I privately flagged this posting to be moved to the thread it should belong to

best regards Stefan

1 Like

Hi Stefan,

thanks so much, especially for the adjustment of the tankheight to 720mm.
It works now in OpenCPN when I try it with the Tupperware!
When the boat is back in the water we can do the real life test.

Thank you very much, you also got a personal message,

best regards,

Franziska

Hi Franziska,

the sensor is sending a new measuring every two seconds.
So an update once every two seconds is the maximum the sensor gives

Would this be fast enough for refueling?
I'm interested in testing = measuring the max-frequency. This would mean flashing a different firmware just for this measuring. If you are interested I can do this.
If you think this is too much additional work that's OK too.

Watching the fuel-level the engine-dashboard in OPenCPN of course works.
I'm thinking about sending an UDP-message to an additional ESP8622 (or ESP32) that could act as a remote display.

Some kind of alert if the sensor does not send measuring anymore makes sense.
If you or Norbert can give me some info to setup a second UDP-message for the alert I can write the code for this.

I have read a little bit about that the sensor can measure surely through plastic or through through fiberglass or through thin metal. There seems to be a limit for the thickness. Did you do a first test with the real tank and it worked?

I will do some tests with increasing the number of measurings for the moving-avarage how much additional RAM-usage a bigger sample-number in the moving-average creates.

Whenever you encounter a problem or a question arises. Just post the question here.

best regards Stefan

Hallo Franziska,

ich habe einen bug im Programm entdeckt. Ich hatte zum Testen einen DummySensor hinzugefügt. Die Programmzeile die den gleitenden Durchschnitt bedient wurde nur mit DummySensorAktiv aufgerufen. Mit richtigem Sensor nicht. Das ist in der neuen Programmversion korrigiert.
Bitte mal testen ob der Sensor jetzt vernünftige Werte liefert.
Hier der Source-Code

#define CodeVersion "Code-Version 008"
// Absolutwert der Höhe der in der Umrechnung in Prozent benutzt wird
float maxHeightOfTank = 720;

const boolean DummySensorActive = false;//true;
unsigned int dummyHoehe = 50;
/*
  Es werden Sensorwerte vom Ultraschallsensor DS1603L erfasst und mittels WiFi als NMEA Stream per UDP in das Netzwerk gesendet.
  Die Übertragung erfolgt auf die örtliche Broadcastadresse xxx.xxx.xxx.255 an den Port 50000.

  Nach dem erstmaligen Start oder bei Start in einem Netzwerk welches noch nicht bekannt ist erfolgt der
  Start mit einer Konfigurationsseite und Timeout
  Nach Ablauf von Timeout wird das WiFi-Modem für 3 Minuten abgeschaltet und dann erfolgt reset Wemos und Neustart mit Konfigurationsseite

  Geht die WiFi-Verbindung verloren wird die sichere Anmeldeseite aufgerufen, nach Timeout erfolgt reset Wemos und die normale Anmeldeschleife wie zuvor beschrieben startet.

  Automatische Wiederverbindung bei Wiederkehr WiFi ohne Neuverbindung

  Die Übertragung der Daten erfolgt über NMEA per UDP an BroadcastIP port 50000.
  Es werden 4 Pakete gesendet damit die auch wirklich nach dem aufwachen ankommen.

  Sensoreinbindung
  Die Sensorwerte werden in der Unterroutine alle 5 Sekunden auslesen.
  Die Sensorwerte werden in ein Schieberegister zum gleitenden Durchschnitt gegeben, jeweils 10 Werte bilden Durchschnitt, Register als FIFO (first In / first out)
  Es wird ein einfacher gleitender Durchschnitt gebildet.

  Der Datenstrom kann in OpenCPN eingelesen werden.
  Dazu unter Verbindungen eine neue Netzwerkverbindung einrichten, die Adresse ist die Broadcastadresse des Netzwerks (letzten drei Stellen .255) und der Port ist 50000
  Es wird eine XDR-Sequenz ausgegeben, die dann mit dem Enginedashboard-Plugin in Opencpn ausgelesen werden kann. Verschiedene Tanktypen können durch Anpassen von "FUEL" in der Unterroutine erfasst werden.
  Dazu bitte die Dokumentation vom EngineDashboard-Plugin lesen.

  adapted from Ethernet library examples
*/

#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);

#include <ESP8266WiFi.h>          //https://github.com/esp8266/Arduino
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <WiFiUdp.h>
#include <DS1603L.h>              //https://github.com/wvmarle/Arduino_DS1603L
#include <SoftwareSerial.h>
#include <MovingAveragePlus.h>
#include <SafeString.h>

createSafeString(Fuell, 256);
createSafeString(XDR1,  256);
createSafeString(str,   256);
createSafeString(nmea,  256);
createSafeString(strN,  256);


//Einstellungen für broadcasting
unsigned int portBroadcast = 50000;      // localer port an den gesendet wird
unsigned int broadCast = 0;

//Variablen für Timer um Sensorwerte zu lesen
unsigned long Timer_RX = 0;
long Timeout_RX = 5000;                // Intervall in ms hier 5000ms = 5s

// Angabe wie der Sensor angeschlossen ist. Nutze Dx statt x für Pin-Angabe wenn Wemos benutzt wird
//#define D3 0
//#define D4 2
const byte txPin = 0;// D3;                               // On Wemos D1 mini D3 is GPIO 0 tx of the Wemos to rx of the sensor
const byte rxPin = 2;// D4;                               // On Wemos D1 mini D4 is GPIO 2 rx of the Wemos to tx of the sensor
SoftwareSerial sensorSerial(rxPin, txPin);

// If your sensor is connected to Serial, Serial1, Serial2, AltSoftSerial, etc. pass that object to the sensor constructor.
DS1603L sensor(sensorSerial);

//WiFiUDP
WiFiUDP Udp;

//WiFiManager
WiFiManager wifiManager;

//Variable für Ergebnis Sensorwert
unsigned int hoehe = 0;


// Definition eines Arrays von 10 Feldern für gleitenden Durchschnitt
MovingAveragePlus<unsigned> hoehe_D(10);    //Gleitender Durchschnitt aus 10 Werten

// Zum Senden des NMEA-Strings nötig Um die richtige Variable-Form zu bilden
char XDR;

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__) );
  Serial.print( F("  compiled ") );
  Serial.print( F(__DATE__) );
  Serial.print( F(" ") );
  Serial.println( F(__TIME__) );
  Serial.println( F(CodeVersion) );
}


// easy to use helper-function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &startOfPeriod, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - startOfPeriod >= TimePeriod ) {
    // more time than TimePeriod has elapsed since last time if-condition was true
    startOfPeriod = currentMillis; // a new period starts right here so set new starttime
    return true;
  }
  else return false;            // actual TimePeriod is NOT yet over
}

unsigned long MyTestConnectionTimer = 0;                   // Timer-variables MUST be of type unsigned long
unsigned long MyMeasuringIntervallTimer = 0;                   // Timer-variables MUST be of type unsigned long

void setup() {
  //Für Berechnungen Prozentwert als Ausgabe
  Fuell = "0";

  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  PrintFileNameDateTime();

  sensorSerial.begin(9600);                         // Sensor transmits its data at 9600 bps.
  sensor.begin();                                   // Initialise the sensor library.

  //reset settings - zum Testen
  //wifiManager.resetSettings();

  //Timeout in sek., nach Ablauf wird die Setup-Seite ausgeschaltet
  wifiManager.setTimeout(120);
  Serial.println("wifiManager.setTimeout(120)");

  //Automatische Startseite und nach Timeout (wifimanager.setTimeout) erfolgt reset
  if (!wifiManager.autoConnect("SSID", "Password")) {  //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
    Serial.println("failed to connect, shut down WiFi-Modem for 3 Minutes then reset Wemos");
    //Ausschalten WiFi-Modem
    WiFi.forceSleepBegin();
    delay(1);
    delay(180000);             //Warte 3 Minuten, in dieser Zeit ist das WiFi-Modul abgeschaltet
    ESP.reset();
  }


  //if you get here you have connected to the WiFi
  Serial.println("connected...yeey :)");
  Serial.println("local ip");
  Serial.println(WiFi.localIP());
}

void loop() {
  if ( TimePeriodIsOver(MyMeasuringIntervallTimer, 1000) ) {
    hoehe = Sensor();  //Aufruf Subroutine Sensor für Sensorwerte
    dbg("1:", hoehe);                //Zum Test ggf. auskommentieren
    Berechnung(hoehe); //Sprung Unterroutine Umrechnung Höhe in % Füllgrad Tank

    //Setze Broadcastadresse
    IPAddress broadCast = WiFi.localIP();
    broadCast[3] = 255;

    //Erstelle Datenstring zum Senden
    //XDR1 = NMEA_XDR(Fuell.c_str() );

    NMEA_XDR(Fuell, nmea); // erzeuge aus "Fuell" die nmea-daten

    // Wandle den String fürs Senden um
    //String str = XDR1;
    str = XDR1;
    //Length (with one extra character for the null terminator)
    int str_len = str.length() + 1;
    // Prepare the character array (the buffer)
    char XDR[str_len];
    // Copy it over
    for (int i = 0; i < str_len; i++) {
      XDR[i] = str[i];
    }
    XDR[str_len] = 0; // append terminating zero
    //str.toCharArray(XDR, str_len);
    delay(50);

    //Sendeschleife Sende vier Pakete
    for (int i = 0; i < 4; i++) {
      Udp.beginPacket(broadCast, portBroadcast); // send UDP to Port 50000 and BroadcastIP
      Udp.write(XDR);
      Udp.endPacket();

      Serial.print("   Paket Nr ");
      Serial.print(i);
      Serial.print(" #");
      Serial.print(XDR);
      Serial.print("# ");
    }
    Serial.print("UDP-Send to ");
    Serial.print(WiFi.localIP());
    Serial.print(" Port ");
    Serial.println(portBroadcast);

    Serial.println();
  }

  if ( TimePeriodIsOver(MyTestConnectionTimer, 10000) ) {
    //Überprüfe ob Verbindung zum Netzwerk steht oder starte Setup-Seite
    if (!wifiManager.autoConnect("SSID", "Password")) {   //Gebe eine SSID vor sowie ein Password. Password muss mindestens 7 Zeichen lang sein
      Serial.println("WiFi lost, reset Wemos");
      delay(3000);
      ESP.reset();
    }
  }

}


//Subroutine um Sensorwerte zu bekommen. Sensorwerte werden in ein Schieberegister geschrieben um aus 10 Werten den gleitenden Durchschnitt zu bekommen
unsigned int Sensor () {
  static unsigned int reading_D;
  reading_D = hoehe_D.get();

  if (millis() - Timer_RX > Timeout_RX) {
    Timer_RX = millis ();
    Serial.println(F("Starting reading."));
    unsigned int reading = sensor.readSensor();       // Call this as often or as little as you want - the sensor transmits every 1-2 seconds.
    dbg("Sensor()", reading);
    if (reading > maxHeightOfTank) { // if value not valid set to max
      reading = maxHeightOfTank;
    }
    byte sensorStatus = sensor.getStatus();           // Check the status of the sensor (not detected; checksum failed; reading success).
    dbg("Sensor()", sensorStatus);
    switch (sensorStatus) {                           // For possible values see DS1603L.h
      case DS1603L_NO_SENSOR_DETECTED:                // No sensor detected: no valid transmission received for >10 seconds.
        if (DummySensorActive) {
          Serial.println( F("DummySensor active") );
        }
        else {
          Serial.println(F("No sensor detected (yet). If no sensor after 1 second, check whether your connections are good."));
        }
        break;

      case DS1603L_READING_SUCCESS:                   // Latest reading was valid and received successfully.
        Serial.println(F("Reading success."));
        Serial.println(reading);
        Serial.println(F(" mm."));
        break;

      case DS1603L_READING_CHECKSUM_FAIL:             // Checksum of the latest transmission failed.
        Serial.print(F("Data received; checksum failed. Latest level reading: "));
        break;
    } // END-OF-SWITCH
    
    if (!DummySensorActive) {
      dbg("DummySensorActive Sensor", reading);
    }
    hoehe_D.push(reading); // Sonntag 29.05.2022 push(reading) eingefügt
    //unsigned int reading_D = hoehe_D.get();
    //Serial.println("Durchschnittshöhe:");           //Zum Test
    //Serial.println(reading_D);                      //Zum Test
    //return reading_D;
  }
  if (DummySensorActive) {
    dummyHoehe += 10;
    if (dummyHoehe > maxHeightOfTank) {
      dummyHoehe = 50;
    }
    reading_D = dummyHoehe;
  }
  else {
    reading_D = hoehe_D.get();
  }
  return reading_D;
}


//Subroutine zur Ermittlung Höhe und Umrechnung in Prozent Füllung
void Berechnung(unsigned int h) {
  //dbg("Berechnung",h);
  unsigned int Prozent = (h / maxHeightOfTank) * 100; //Testrechnung muss an Tank angepasst werden. 400 mm als Gesamthöhe Tank zum Test
  //dbg("Berechnung",Prozent );
  Fuell = Prozent;
  dbg("Berechnung", Fuell);
}


//Create NMEA String XDR

void NMEA_XDR(SafeString& p_Fuell, SafeString& p_RefToSS) {
  //dbg("NMEA_XDR 1:",p_Fuell);
  nmea = "$IIXDR,V,";
  nmea += p_Fuell;// Val, DEC;
  nmea += ",P,FUEL*";
  dbg("NMEA_XDR 2:", nmea);
  //nmea += testsum(nmea), HEX;
  nmea.print(testsum(nmea), HEX);
  //dbg("NMEA_XDR 3:",nmea);
  XDR1 = nmea;
  //dbg("NMEA_XDR 4:",XDR1);
}

//Calculates the checksum for the NMEA String
int testsum(SafeString& strN) {
  int i;
  int XOR;
  int c;
  // Calculate testsum ignoring any $'s in the string
  for (XOR = 0, i = 0; i < 80; i++) {                                    // strlen(strN)
    c = (unsigned char)strN[i];
    if (c == '*') break;
    if (c != '$') XOR ^= c;
  }
  return XOR;
}

und als ZIP-Datei die compilierte Firmware
OpenBoat-WireLess-Tank-Sensor-008-SafeString.ino.bin.zip (252 KB)

viele Grüße Stefan

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.