Polarfuchs            

von Nicolaus Cüppers              

Elektronik-Labor  Literatur  Projekte  Lernpakete  Kalender-Contest                      





Der Conrad-Adventskalender „Internet of Things“ war für mich der Einstieg, mich einmal mit dem Thema Arduino zu befassen. Herausgekommen ist das Projekt „Polarfuchs“. Der Name ist angelehnt an das kleinste mir bekannte deutsche Forschungsschiff. Es handelt sich um einen Versuchsaufbau, der ein unbemanntes „Schiff“ nachbildet, was einige Umgebungsdaten erfasst und weiterleitet und parallel dazu seinen eigenen Zustand überwacht.



Es wurde nur der Kalender und zusätzlich eine Stromquelle verwendet. Daher sind die Messmethoden auch nicht unbedingt praxisnahe. Normalerweise würde man für die einzelnen Werte passende Sensoren einsetzen.  Das war allerdings nicht die Aufgabe. Vielmehr ging es ja darum, aus einer Hand voll vorhandener Bauelemente das Maximale herauszuholen. Als Schiffsrumpf wird daher der Kalender selbst verwendet. Er bildet durch seinen Plastikkörper mit zahlreichen Einsenkungen eine ideale Schwimmplattform. Es wurden jedoch die üblichen konfektionierten Steckkabel verwendet. Das dem Kalender beiliegende Kabel hätte aber auch gereicht, um die Schaltung aufzubauen. Dann hätte man nur alles etwas enger am Board platzieren müssen.





Folgende Umweltwerte sammelt „Polarfuchs“ ein, um sie auf einer eigenen Webseite darzustellen:
Wassertmperatur mittels NTC
Bedeckungsgrad des Himmels mittels Photodiode
Regen mittels Spannungsabfall



Außerdem überwacht Polarfuchs sich selbst auf Lecks (Wassereinbruch aka Bilgenalarm) mittels Spannungsabfall und gibt auf seiner Webseite Auskunft über seine Betriebsspannung. Die Überwachung von Wasser beruht darauf, dass Wasser etwas Strom leitet ( und zwar nicht nur Leitungswasser haahaaa... ).  Taucht man zwei Kontakte, an denen eine Spannung anliegt, in Wasser, fließt ein geringer Strom, wodurch die Spannung abfällt. Diesen Spannungsabfall kann man messen. Im Falle des Leckalarms (Bilgenalarm) platziert man einfach innenbords zwei Drähte an eine Stelle, die normalerweise trocken ist. An diese Drähte legt man eine Spannung an, die man wiederum mit einem Analogeingang überwacht. Solange alles so ist wie es sein soll, fließt kein Strom, da die beiden Drahtenden nur durch Luft verbunden sind.  Gelangt jetzt Wasser an die überwachte Stelle, fließt ein geringer Strom, was sich am angeschlossenen Analogeingang durch eine geringere Spannung bemerkbar macht. Man muss dann nur noch empirisch einen Schwellenwert definieren, und fertig ist der Wassersensor.

Die Überwachung auf Regen funktioniert nach dem gleichen Prinzip. Hier hat man allerdings das Problem, dass man das Regenwasser nicht sammeln möchte, also erst einmal keinen Ort hat, wo man es abgreifen kann. Eine kleine Hilfskonstruktion soll das Problem lösen: Eine geneigte Ebene als Sammelfläche für Wasser, die einen zentralen Auslaufpunkt hat. Dort platziert man die beiden Drähte so eng es nur irgendwie geht aneinander, ohne dass sie sich berühren. Dafür wird die Rückseite des Kalenders verwendet, die dafür passend gefaltet auf die Oberseite des Kalenders gesteckt wird und so gleichzeitig ein Dach über der Elektronik bildet. Die Messkontakte werden einfach am tiefsten Punkt dicht nebeneinander von unten durch die Pappe gesteckt.



Regnet es jetzt auf die Sammelfläche, fließt das Wasser durch die Schwerkraft zusammen und in Richtung des Auslaufs, wo sich ab einer gewissen Regenintensität ein kleines Rinnsal bildet, was wiederum über die Drähte fließt und diese so verbindet. Solange also Wasser fließt, ist ein Spannungsabfall messbar. Hört es auf zu regnen, versiegt das Rinnsal, und der NanoESP weiß, das es nicht mehr regnet. Im Badewannen-Versuch erwies sich das als der Schwachpunkt des Systems, denn die Pappe sog sich voll Wasser, weshalb „Polarfuchs“ auch nach dem Begießen mit Wasser erstmal weiterhin Regen anzeigte. Das ändert aber nichts an der Funktionsfähigkeit des Konzepts.

Die Temperatur wird über einen NTC gemessen, der ebenfalls dem Kalender beilag. Er kann durch das Poti mit einer externen Referenztemperatur „geeicht“ werden.  

Für den Bedeckungsgrad des Himmels kann man den Photowiderstand des Kalenders verwenden. So wird das Umgebungslicht gemessen und anhand empirisch vordefinierter Bereiche einem von drei Zuständen zugeordnet: sonnig, bedeckt, Nacht. Das ist zwar zugegeben nicht sehr genau, aber andererseits nicht unzuverlässig.  Wir haben also einen Wetterbericht mit Temperatur, Himmel und Niederschlag. Dies wird per WLAN auf einer Webseite zur Verfügung gestellt. Natürlich wird hier vorausgesetzt, dass das Schiff Internetzugang hat und auch erreichbar ist.

Neben der Webseite verfügt das „Schiff“ noch über ein Toplicht (LED), was bei Dunkelheit automatisch leuchtet und einen Buzzer, der im Notfall (Wassereinbruch) zusammen mit der LED SOS signalisiert. Der Versuchsaufbau wurde tatsächlich erfolgreich in der Badewanne getestet!

 Ein Video gibt es hier: https://youtu.be/2W1TGGviBBs

Download: PolarfuchsFinal.zip
 

/*
Forschungsschiff "Polarfuchs"
N. Cueppers
www.nicnet.de/wordpress
Stellt Wetterdaten über einen Browser zur Verfuegung:
Wassertemperatur mittels Thermistor
Himmel mittels Photodiode
Regen mittels Spannungsabfall
Ausserdem ueberwacht Polarfuchs sich selbst auf Lecks (Wassereinbruch aka Bilgenalarm) mittels Spannungsabfall
und gibt auf der Webseite Auskunft über seine Betriebsspannung.
Ist der Wert kritisch, sendet Polarfuchs das Notsignal SOS per Toplicht (LED) und Horn (Buzzer).
Bei Dunkelheit schaltet Polarfuchs das Toplicht ein. Da es keinen eigenen Atrieb hat, genuegt dafuer tatsaechlich ein weisses Rundumlicht.
(§ 3.13 Abs. 5 BinSchStrO)
Der Name ist angelehnt an das kleinste mir bekannte deutsche Forschungsschiff: http://www.deutsche-meeresforschung.de/de/polarfuchs
*/

#define SSID "SSID"
#define PASSWORD "PASSWORD"

#define LED_WLAN 13
#define DEBUG true

//Pins I/O
#define THERMO A0
#define PHOTO A1
#define vRegen_pin 4    
#define vBilge_pin 5
#define LED 9
#define buzzer 8

#include <SoftwareSerial.h>

SoftwareSerial esp8266(11, 12); // RX, TX

const byte site[] PROGMEM = {
60,72,84,77,76,62,60,72,69,65,68,62,13,10,60,108,105,110,107,32,114,101,108,61,34,105,99,111,110,34,32,104,114,101,102,61,34,100,97,116,97,58,59,98,97,115,101,54,52,44,105,86,66,79,82,119,48,75,71,103,111,61,34,62,13,10,60,109,101,116,97,32,110,97,109,101,61,34,118,105,101,119,112,111,114,116,34,32,99,111,110,116,101,110,116,61,34,119,105,100,116,104,61,100,101,118,105,99,101,45,119,105,100,116,104,44,32,105,110,105,116,105,97,108,45,115,99,97,108,101,61,50,46,48,44,32,117,115,101,114,45,115,99,97,108,97,98,108,101,61,121,101,115,34,62,13,10,60,109,101,116,97,32,104,116,116,112,45,101,113,117,105,118,61,34,114,101,102,114,101,115,104,34,32,99,111,110,116,101,110,116,61,34,51,34,62,13,10,60,116,105,116,108,101,62,13,10,80,111,108,97,114,102,117,99,104,115,13,10,60,47,116,105,116,108,101,62,13,10,60,47,72,69,65,68,62,13,10,60,66,79,68,89,32,98,103,99,111,108,111,114,61,34,35,70,70,70,70,70,70,34,32,116,101,120,116,61,34,35,48,48,48,48,48,48,34,62,13,10,60,70,79,78,84,32,115,105,122,101,61,34,51,34,32,70,65,67,69,61,34,86,101,114,100,97,110,97,34,62,13,10,60,98,62,80,79,76,65,82,70,85,67,72,83,60,47,98,62,13,10,60,47,70,79,78,84,62,13,10,60,70,79,78,84,32,115,105,122,101,61,34,50,34,32,70,65,67,69,61,34,86,101,114,100,97,110,97,34,62,13,10,60,66,82,62,13,10,42,98,114,105,103,104,116,42,13,10,60,47,102,111,110,116,62,13,10,60,47,72,84,77,76,62,13,10
};

void setup() {

pinMode(vRegen_pin, INPUT);
digitalWrite(vRegen_pin, HIGH);        
pinMode(vBilge_pin, INPUT);
digitalWrite(vBilge_pin, HIGH);  
pinMode(LED, OUTPUT);
digitalWrite(LED, LOW);
pinMode(buzzer, OUTPUT);
digitalWrite(buzzer, LOW);  
 
 Serial.begin(19200);
 esp8266.begin(19200);

 if (!espConfig()) serialDebug();
 else digitalWrite(LED_WLAN, HIGH);

 if (configTCPServer())  debug("Server Aktiv"); else debug("Server Error");
}

void loop() {

 if (analogRead(PHOTO) > 100)
      digitalWrite(LED, LOW);
 else digitalWrite(LED, HIGH);

 int bilge=analogRead(A4);

 if ((bilge < 800) || (readVcc() < 4200 )) //Achtung, EasterEgg: Mit wenigen weiteren Widerständen könnte man über Spannungsteiler SINNVOLL die Batteriespannung überwachen. Dann gibt es auch ein Notsignal, wenn die Spannung zu niedrig wird!
 {

 digitalWrite(LED, LOW);

 delay(600); did(); did(); did(); da(); da(); da(); did(); did(); did(); delay(600);
 }

 String xBuffer;
 if (esp8266.available()) // check if the esp is sending a message
 {
   if (esp8266.find("+IPD,"))
   {
     debug("Incomming Request");
     int connectionId = esp8266.parseInt();

     if (sendWebsite(connectionId, createWebsite())) debug("Website send OK"); else debug("Website send Error");
   }

 }
}

boolean sendWebsite(int connectionId, String webpage)
{
 boolean succes = true;

 if (sendCom("AT+CIPSEND=" + String(connectionId) + "," + String(webpage.length()), ">"))
 {
   esp8266.print(webpage);
   esp8266.find("SEND OK");
   succes &= sendCom("AT+CIPCLOSE=" + String(connectionId), "OK");
 }
 else
 {
   succes = false;
 }
 return succes;
}

String createWebsite()
{
 String xBuffer;
 
 double vTemp = Thermistor(analogRead(THERMO));
 
 String br = "<br>";
 int vcc = readVcc();
 String stringOne = "<br><b>Wetterdaten:</b><p> ";
       
 int webregen = analogRead(A5);
 String esregnet;
 if (webregen > 900)
 esregnet = "trocken";
 else esregnet ="Regen";

 int licht = analogRead(PHOTO);
 String webhimmel;
 if (licht >= 100 & licht <= 700)
 webhimmel = "bedeckt";
 else if (licht > 700) webhimmel = "sonnig";
 else webhimmel ="Nacht";

 int bilgenwasser = analogRead(A4);
 String webbilge;
 if (bilgenwasser > 900)
 webbilge = "trocken";
 else webbilge ="Wassereinruch";

 
 String stringThree = stringOne + vTemp + " Grad " + br + webhimmel + br + esregnet + br + "<p><b>Schiffsstatus</b>"+ br + br + "Bordspannung: " + vcc + " mV" + br + "Bilge: " + webbilge;

 for (int i = 0; i <= sizeof(site); i++)
 {
   char myChar = pgm_read_byte_near(site + i);
   xBuffer += myChar;
 }

 xBuffer.replace("*bright*", stringThree);

 return xBuffer;


}


//-----------------------------------------Config ESP8266------------------------------------

boolean espConfig()
{
 boolean succes = true;
 esp8266.setTimeout(5000);
 succes &= sendCom("AT+RST", "ready");
 esp8266.setTimeout(1000);
 if (configStation(SSID, PASSWORD)) {
   succes &= true;
   debug("WLAN Connected");
   debug("My IP is:");
   debug(sendCom("AT+CIFSR"));
 }
 else
 {
   succes &= false;
 }
 //shorter Timeout for faster wrong UPD-Comands handling
 succes &= sendCom("AT+CIPMODE=0", "OK");  
 succes &= sendCom("AT+CIPMUX=0", "OK");

 return succes;
}

boolean configTCPServer()
{
 boolean succes = true;

 succes &= (sendCom("AT+CIPMUX=1", "OK"));
 succes &= (sendCom("AT+CIPSERVER=1,80", "OK"));

 return succes;

}

boolean configTCPClient()
{
 boolean succes = true;

 succes &= (sendCom("AT+CIPMUX=0", "OK"));
 //succes &= (sendCom("AT+CIPSERVER=1,80", "OK"));

 return succes;

}


boolean configStation(String vSSID, String vPASSWORT)
{
 boolean succes = true;
 succes &= (sendCom("AT+CWMODE=1", "OK"));
 esp8266.setTimeout(20000);
 succes &= (sendCom("AT+CWJAP=\"" + String(vSSID) + "\",\"" + String(vPASSWORT) + "\"", "OK"));
 esp8266.setTimeout(1000);
 return succes;
}

boolean configAP()
{
 boolean succes = true;

 succes &= (sendCom("AT+CWMODE=2", "OK"));
 succes &= (sendCom("AT+CWSAP=\"NanoESP\",\"\",5,0", "OK"));

 return succes;
}

boolean configUDP()
{
 boolean succes = true;

 succes &= (sendCom("AT+CIPMODE=0", "OK"));
 succes &= (sendCom("AT+CIPMUX=0", "OK"));
 succes &= sendCom("AT+CIPSTART=\"UDP\",\"192.168.255.255\",90,91,2", "OK"); //Importand Boradcast...Reconnect IP
 return succes;
}

//-----------------------------------------------Controll ESP-----------------------------------------------------

boolean sendUDP(String Msg)
{
 boolean succes = true;

 succes &= sendCom("AT+CIPSEND=" + String(Msg.length() + 2), ">");    //+",\"192.168.4.2\",90", ">");
 if (succes)
 {
   succes &= sendCom(Msg, "OK");
 }
 return succes;
}


boolean sendCom(String command, char respond[])
{
 esp8266.println(command);
 if (esp8266.findUntil(respond, "ERROR"))
 {
   return true;
 }
 else
 {
   debug("ESP SEND ERROR: " + command);
   return false;
 }
}

String sendCom(String command)
{
 esp8266.println(command);
 return esp8266.readString();
}



//-------------------------------------------------Debug Functions------------------------------------------------------
void serialDebug() {
 while (true)
 {
   if (esp8266.available())
     Serial.write(esp8266.read());
   if (Serial.available())
     esp8266.write(Serial.read());
 }
}

void debug(String Msg)
{
 if (DEBUG)
 {
   Serial.println(Msg);
 }
}

//Spannungsmessung gefunden auf
//https://code.google.com/p/tinkerit/wiki/SecretVoltmeter

long readVcc() {
 long result;
 // Read 1.1V reference against AVcc
 ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
 delay(5); // Wait for Vref to settle
 ADCSRA |= _BV(ADSC); // Convert
 while (bit_is_set(ADCSRA,ADSC));
 result = ADCL;
 result |= ADCH<<8;
 result = 1126400L / result; // Back-calculate AVcc in mV
 return result;
}

//EasterEgg2: Interne Temperatur des Boards könnte man auch noch ausgeben! :)

long readTemp() {
 // Read temperature sensor against 1.1V reference
 #if defined(__AVR_ATmega32U4__)
   ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0);
   ADCSRB = _BV(MUX5); // the MUX5 bit is in the ADCSRB register
 #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
   ADMUX = _BV(REFS1) | _BV(MUX5) | _BV(MUX1);
 #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
   ADMUX = _BV(REFS1) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0);
 #else
   ADMUX = _BV(REFS1) | _BV(REFS0) | _BV(MUX3);
 #endif

 delay(5); // Wait for ADMUX setting to settle
 ADCSRA |= _BV(ADSC); // Start conversion
 while (bit_is_set(ADCSRA,ADSC)); // measuring

 uint8_t low = ADCL; // must read ADCL first - it then locks ADCH
 uint8_t high = ADCH; // unlocks both
 long result = (high << 8) | low; // combine the two

 return result;
}

double Thermistor(int RawADC) {
 //Source: http://playground.arduino.cc/ComponentLib/Thermistor2
 double Temp;
 Temp = log(10000.0 * ((1024.0 / RawADC - 1)));
 //         =log(10000.0/(1024.0/RawADC-1)) // for pull-up configuration
 Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * Temp * Temp )) * Temp );
 Temp = Temp - 273.15;            // Convert Kelvin to Celcius
 // Temp = (Temp * 9.0)/ 4.7 + 32.0; // Convert Celcius to Fahrenheit
 return Temp;
}

void did() {

   tone(buzzer, 440, 100);
   digitalWrite(LED, HIGH);
   delay(200);
   noTone(buzzer);
   digitalWrite(LED, LOW);   
   delay(200);
   
}

void da() {
   
   tone(buzzer, 440, 600);
   digitalWrite(LED, HIGH);
   delay(600);
   noTone(buzzer);
   digitalWrite(LED, LOW);
   delay(200);
   
}




Elektronik-Labor  Literatur  Projekte  Lernpakete  Kalender-Contest