
Polarfuchs
von Nicolaus Cüppers
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/2W1TGGviBBsDownload:
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);
}