Photobioreaktor Code-Dokumentation


Das ist die Dokumentation des Arduino-Codes der den Photobioreaktor steuert. Die Dokumentation enthält auf der linken Seite den vollständigen Code und rechts eine zugehörige Beschreibung des jeweiligen Code-Abschnittes. Einige der Beschreibungen lassen sich durch Anklicken aufklappen, um noch detailliertere Informationen zu erhalten.

Die ArduinoIDE welche zum Hochladen des Codes auf den ESP32 benötigt wird, kann hier heruntergeladen werden.

Die Arduino-Webseite bietet ein Glossar zur Programmiersprache in der Arduino-Programme geschrieben werden. Hier werden alle Funktionen, Variablen und Strukturbefehle die ein Arduino-Programm enthalten kann ausführlich erklärt: Arduino-Glossar

#include <Arduino.h>

Stellt grundlegende Arduino-Funktionen und -Definitionen bereit. Sollte in jedem Arduino-Sketch enthalten sein.

#include <ArduinoJson.h>
#include <ArduinoJson.hpp>

Ermöglicht JSON-Daten zu verarbeiten.

#include <Wire.h>

Ermöglicht die Kommunikation über I2C-Protokoll.

#include <WiFiManager.h>
WiFiManager wfm;

Verwaltet die WLAN-Verbindung und konfiguriert sie. Erstellen eines Objektes vom Typ WiFiManager mit dem Namen wfm. Dadurch kann man die Funktionen der WiFiManager-Bibliothek mit "wfm.Funktion" aufrufen.

#include <WiFi.h>

Ermöglicht die Verbindung zum drahtlosen Netzwerk.

#include <InfluxDbClient.h>

Ermöglicht die Kommunikation mit einer InfluxDB-Datenbank.

#include <InfluxDbCloud.h>

Ermöglicht die Kommunikation mit InfluxDB Cloud.

#include <HTTPClient.h>
HTTPClient http;

Ermöglicht das Senden von HTTP-Anfragen.

#include <Preferences.h>
Preferences pref

Ermöglicht die Verwendung von dauerhaften Speichereinstellungen.

#include <ezTime.h>
Timezone myTZ

Ermöglicht die einfache Verwendung von Zeit- und Datumsservices.

//Photobioreactor Library
#include <Photobioreactor.h>
Photobioreactor pb

Enthält viele wichtige, eigens für den Reaktor definierte Hilfsfunktionen.

#ifdef ARDUINO_SAMD_VARIANT_COMPLIANCE
#define SERIAL SerialUSB
#else
#define SERIAL Serial
#endif

Definition eines Makro SERIAL, das auf Serial oder SerialUSB je nach Board-Architektur (SAMD oder nicht) verweist. Dieses Makro wird später im Code verwendet, um auf ein Objekt für die serielle Kommunikation zu verweisen. Ziel ist, die Auswahl zwischen Serial und SerialUSB je nach Board-Architektur zu ermöglichen..

//TEMP

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

Ermöglicht die Kommunikation mit Temperatursensoren der Dallas-Reihe.

//Define InfluxDB

#include "Config.h" //You can change your InfluxDB Instance here

Sie enthält Konfigurationsvariablen für die Verbindung zur InfluxDB-Datenbank.

InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert);
Point sensor("SampleData");
  • Die InfluxDB-Instanz wird verwendet um eine Verbindung zur InfluxDB-Datenbank herzustellen und Daten in sie zu schreiben. Die Parameter in der Klammer werden aus der Config.h Datei geladen.
  • Die Point-Instanz wird verwendet, um Datenpunkte zu repräsentieren, die in die InfluxDB-Datenbank geschrieben werden sollen. "SampleData" ist der Name des Datenpunkts, der in der Datenbank erstellt und gespeichert wird.
//TDS

const int tdsSensorPin = 35;
const int VREF = 3.3; // analog reference voltage(Volt) of the ADC
int analogBuffer[0]; // store the analog value in the array, read from ADC

  • Definieren des Pin 35 an dem die Daten des TDS-Sensors ausgelesen werden.
  • Festlegen der Referenz-Spannung des Analog-Digital-Umsetzers des TDS-Sensors zur Berechnung des Messwertes.
  • Erstellen eines Arrays zur Zwischenspeicherung des analogen Messwertes des TDS-Sensors.

//Relay
const int heatPin = 14;
const int stirrerPin = 13;
const int lightPin = 12;

//Water Level I2C
const int I2C_SDA_PIN = 27;
const int I2C_SCL_PIN = 25;
int lowData[8] = {0};
int highData[12] = {0};
const byte NO_TOUCH = 0xFE;
const int THRESHOLD = 100;
const int ATTINY1_HIGH_ADDR = 0x78;
const int ATTINY2_LOW_ADDR = 0x77;
const int statusLED = 33;
int reconnectAttempt = 0;

  • Pins für die Daten- und Taktleitung des I2C-Busses (SDA und SCL).
  • Arrays der Daten, die vom Wasserstandssensor für den unteren und oberen Teil des Behälters gelesen werden.
  • Schwellenwerte ab dem ein Wasserstands-Messabschnitt als solcher gemessen wird.
  • Adressen der zwei ATTINY-Mikrocontrollern, die zur Steuerung des Wasserstandssensors verwendet werden.
  • Setzen der StatusLED auf Pin 33.
  • Variable für die Anzahl der Verbindungsversuche.

//TEMP
#define ONE_WIRE_BUS 32
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

  • Definieren der Pinnummer an die der Datenpin des DS18B20-Temperatursensors angeschlossen ist.
  • Erstellen einer DallasTemperature-Instanz mit der zuvor erstellten OneWire-Instanz zur Kommunikation des Sensors über den zuvor definierten Pin des OneWire Bus.

//Trigger for Reset
const int RESET_TRIGGER = 26;
int buttonState = 0;
int lastButtonState = 0;
int startPressed = 0;
int endPressed = 0;
int holdTime = 0;
int idleTime = 0;

  • Setzen der Pinnummer des Reset-Knopfs auf 12.
  • Variablen welche die Zeitpunkte speichern, an denen der Taster gedrückt und losgelassen wird (start & end).
  • Variable welche die Zeitdauer speichert, für die der Taster gedrückt wurde ("hold").
  • Variable welche die Zeitdauer speichert, für die der Taster nicht gedrückt wurde ("idle").

//Time Intervall for Data Transfer
bool dataSent = false;
int previousMinute = -1;

  • Die erste Variable speichert den Zustand (true/false), ob Daten an den Server gesendet wurden. Initial auf "false" gesetzt.
  • Die zweite Variable speichert den Zeitstempel der vorherige Minute. Initial -1 damit beim ersten Durchlauf des Codes Daten unabhängig von der tatsächlichen aktuellen Minute gesendet werden können.

//HalfHourly Time Sync
unsigned long previousMillisSync = 0;
unsigned long previousMillisPrint = 0;
const unsigned long intervalSync = 1800000;
const unsigned long intervalPrint = 60000;

  • Variable für Zeitpunkt des letzten Synchronisierungsvorgangs.
  • Variable für Zeitpunkt des letzten Druckvorgangs im seriellen Monitor.
  • Intervall zwischen zwei Synchronisierungsvorgängen.
  • Intervall zwischen zwei Druckvorgängen.

// Box für Zieltemperatur
WiFiManagerParameter("MaxTemp_Text", "Maximale Temperatur", "32", 2);
// Box für Minimaltemperatur
WiFiManagerParameter("MinTemp_Text", "Minimale Temperatur", "25", 2);
// Box für Abstand zwischen Rühren
WiFiManagerParameter("IntervalStirrer_Text", "Zeit zwischen den Rührvorgängen in Minuten", "30", 4);
// Box für Rührdauer>
WiFiManagerParameter("DurationStirrer_Text", "Dauer eines Rührvorgangs in Minuten", "1", 4);
// Box für Licht an
WiFiManagerParameter("LightOn_Text", "Uhrzeit (0-23) zum anschalten des Lichts", "8", 2);
// Box für Licht aus
WiFiManagerParameter("LightOff_Text", "Uhrzeit (0-23) zum abschalten des Lichts", "20", 2);

void.setup() {

Das void.setup() ist eine spezielle Funktion in Arduino-Programmen. Sie wird automatisch einmal ausgeführt, wenn das Arduino-Board eingeschaltet oder zurückgesetzt wird.

Serial.begin(115200);

Die Baud-Rate gibt die Übertragungsgeschwindigkeit für die serielle Kommunikation an. Die Einheit für die Baudrate ist Baud, was die Anzahl der Signalwechsel pro Sekunde bedeutet.

pref.begin("Prefs",false);
if (pref.getBool("namebool") == false)
{
int reactorNumber = (int)random(100000, 999999);
String reactorNameGenerated = String("ESP32_" + String(reactorNumber));
pref.putString("reactorNameUser", reactorNameGenerated);
pref.putBool("namebool",true);
Serial.println("Reaktorname: ");
Serial.println(reactorNameGenerated);
}

Der Reaktorname wird aus "ESP32_" und einer zufällig generierten Zahl erzeugt. Anschließend wird der Status des Namen "namebool" mit der Preference-Library auf true gesetzt. Zuletzt wird der generierte Reaktorname im seriellen Monitor ausgegeben.

WiFiManagerParameter Token_Text_Box("Token_Text", "Name des Reaktors", pref.getString("reactorNameUser").c_str(), 20);
Wire.begin(I2C_SDA_PIN, I2C_SCL_PIN);
pinMode(heatPin, OUTPUT);
pinMode(stirrerPin, OUTPUT);
pinMode(lightPin, OUTPUT);
pinMode(RESET_TRIGGER, INPUT_PULLUP);
pinMode(2, OUTPUT);
pinMode(statusLED, OUTPUT);
pinMode(tdsSensorPin, OUTPUT);
//initialise TEMPsensor
sensors.begin();
wfm.addParameter(&Token_Text_Box);
wfm.addParameter(&MaxTemp_Text_Box);
wfm.addParameter(&MinTemp_Text_Box);
wfm.addParameter(&IntervalStirrer_Text_Box);
wfm.addParameter(&DurationStirrer_Text_Box);
wfm.addParameter(&LightOn_Text_Box);
wfm.addParameter(&LightOff_Text_Box);
if (digitalRead(RESET_TRIGGER) == LOW)
{
Serial.println("HARDRESET");
pref.putBool("setupbool", false);
pref.putBool("namebool", false);
pref.clear();
wfm.resetSettings();
pb.blinkLEDs(1, 500);
ESP.deepSleep(10000000*10000000); //Sleep until Reset
}

Falls der Reset-Button gedrückt wurde, wird ein harter Reset ausgeführt mit Rücksetzen der Reaktornamen- und WifiManager-Einstellungen. Anschließend wird der ESP32 in den Tiefschlafmodus versetzt. Er stoppt die Ausführung des Codes und verbraucht sehr wenig Strom. Nach Ablauf des Zeitintervalls wird der Mikrokontroller wieder aktiviert und beginnt die Ausführung des Codes.

//Try to connect with Wifi network
wfm.setWiFiAutoReconnect(true);
wfm.setConnectRetries(10);
wfm.setConfigPortalTimeout(300);

  • Automatisches Wiederverbinden mit Wifi aktivieren.
  • Festlegen der Verbindungsversuche auf 10.
  • Festlegen des Timeout aus dem Konfigurationsportal auf 5 Minuten.

bool res = wfm.autoConnect(pref.getString("reactorNameUser").c_str());

Hierbei wird der zuvor erstellte "reactorNameUser" aus der Preference-Library als Schlüssel benutzt und der Zustand (true/false) in der Variable res abgespeichert.

if (!res)
{
waitForSync();
myTZ.setLocation("Europe/Berlin");
if (pref.getBool("resetbool") == false)
{
pref.putString("MinTemp",MinTemp_Text_Box.getValue());
pref.putString("MaxTemp", MaxTemp_Text_Box.getValue());
pref.putString("IntervalStirrer", IntervalStirrer_Text_Box.getValue());
pref.putString("DurationStirrer", DurationStirrer_Text_Box.getValue());
pref.putString("LightOn", LightOn_Text_Box.getValue());
pref.putString("LightOff", LightOff_Text_Box.getValue());
pref.putBool("resetbool", true);
}

Hierbei wird der zuvor erstellte "reactorNameUser" aus der Preference-Library als Schlüssel benutzt und der Zustand (true/false) in der Variable res abgespeichert.

if (pref.getBool("setupbool") == false)
{
pref.putString("reactorNameUser", Token_Text_Box.getValue());
pref.putBool("setupbool", true);
pb.blinkLEDs(10, 20);
}
}
String reactorNameUser = pref.getString("reactorNameUser");
const char* Reaktorname = reactorNameUser.c_str();
Serial.print("Your Reactor is called: ");
Serial.println(Reaktorname);
sensor.addTag("Reaktorname", Reaktorname);
//Write one initial Point for initial User Feedback
pb.addSensorData();
pb.writeSensorData();
}
void.loop() {

Die void.loop() ist ein spezielle Funktion in Arduino-Programmen. Der Code innerhalb der Funktion wird kontinuierlich wiederholt solange das Board mit Strom versorgt wird.

if (Wifi.status() == WL_CONNECTED)
{
Serial.println("Connected with Wifi!");
pb.setStatusLEDsHigh();
pb.checkResetButton(); // Check Button State and initialize Reset
pb.halfHourlySyncDeviceTime(); // Update Current Time
pb.printValues();

  • Kurzes Aufblinken der Status-LED.
  • Überprüfen des Status des Reset-Buttons.
  • Aktualisiert die Gerätezeit (alle 30 Minuten).
  • Ausgabe der aktuellen Sensormesswerte und Parameter.

// Control the Parameters
pb.controlWaterTemp();
pb.controlStirring();
pb.controlLight();
//Write Points
int currentHour = myTZ.hour();
int currentMinute = myTZ.minute();

if(currentMinute != previousMinute){
dataSent = false;
previousMinute = currentMinute;
}

delay(10);
if(((currentHour == 0 && currentMinute == 0) ||
(currentHour == 6 && currentMinute == 0) ||
(currentHour == 14 && currentMinute == 15) ||
(currentHour == 18 && currentMinute == 0)) &&
(!dataSent))
{
pb.addSensorData();
pb.writeSensorData();

// Set flag to true to indicate that the data has been sent
dataSent = true;
previousMinute = myTZ.minute();
delay(10);
}
}
else
{

  • Abrufen und Schreiben der aktuellen Sensordaten in die InfluxDB.
  • Sendestatus auf true setzen.
  • Aktualisieren der vergangenen Minute.

pb.setStatusLEDsLow();
wfm.disconnect();
if(wfm.getWiFiIsSaved()) wfm.setEnableConfigPortal(false);
wfm.autoConnect("AP");
wfm.autoConnect(pref.getString("reactorNameUser").c_str());

Serial.println("No WiFi");
}
Serial.print(".");
delay(100);
}

  • Verbindung zum WLAN wird getrennt.
  • Falls Wifi-Daten gespeichert sind, wird das Konfigurationsportal deaktiviert.
  • Versuch mit einem AccessPoint zu verbinden.
  • Erneuter Versuch sich mit dem zuvor gespeicherte WLAN zu verbinden.
  • Ausgabe der Meldung "No Wifi." im seriellen Monitor.