Eine Wetterstation mit Wolkensensor und Internet-Anschluß

Da das Wetter im Augenblick grauenerregend ist suchte ich nach einem neuen Projekt für die regnerischen Tage (wenn ich keine Lust aufs Spiegelschleifen habe). Inspiriert von Matthias Bopps Wolkensensor überlegte ich mir, ob es möglich wäre etwas zu bauen um den Wolkensensor über den Laptop abzulesen.

Die Hardware:
Ich hatte noch einen NSLU2 mit Linux drauf von einem früheren Experiment rumliegen. Aber wie den Sensor anschließen?
NSLU2
Die Lösung ist der Arduino. Mit diesem kleinen Computer auf Atmel AVR ATmega Basis kann man sehr leicht analoge Sensoren auslesen.
Arduino Duemilanove

Als Sensoren werden zwei 22k (mein Händler hatte gerade keinen anderen da - 10k müssten auch gehen) NTCs, die als Spannungsteiler geschaltet sind, verwendet. Die 5V Spannung wird vom Arduino an einem Pin zur Verfügung gestellt.
Wolkensensor Sensor zur Temperaturdifferenzmessung
Ich überlegte erst, ob noch ein Widerstand als Sicherung eingebaut werden müsste. Dem Datenblatt der NTCs habe ich entnommen, das die NTCs selbst bei 60°C noch 8k Widerstand haben. Damit werden die Ports des Arduino auf keinen Fall überlastet. Der "Mittelabgriff" des Spannungsteilers wird einem Analog-Eingang des Arduino zugeführt.
In dieser Beschaltung kann man mit den NTCs keine Absoluttemperaturen bestimmen - mir geht es nur um die Temperaturdifferenz. Dieser Sensor ist sehr empfindlich: wenn man sich einem NTC mit dem Finger auf ca. 5mm nähert bekommt man schon einen deutlichen Ausschlag.
Hardwaremäßig sieht das Ganze dann so aus:
Wolkensensor Gesamtsystem

Zusätzlich habe ich noch einen Luftdrucksensor (Freescale MPXA4100A6U) und einen Temperatur- und Feuchte-Sensor (Sensirion SHT71) angeschlossen. Eine Library um den SHT71 über seine digitale Schnittstelle auszulesen gibt es hier: http://github.com/practicalarduino/SHT1x. Der Arduino liefert alle Meßwerte als CSV an den NSLU2 zur Auswertung.

Die Software:
Die ausgelesenen Werte werden in eine RRD Datenbank geschrieben um eine schöne Historie der Messergebnisse zu haben.

Um die RRD-Datenbank anzulegen habe ich folgendes Kommando verwendet:

rrdtool create weather.rrd DS:tempdiff:GAUGE:120:-512:512 DS:pressure:GAUGE:120:0:1200 DS:temperature:GAUGE:120:-30:60 DS:humidity:GAUGE:120:0:100 RRA:AVERAGE:0.5:1:4000 RRA:AVERAGE:0.5:3:4000 RRA:AVERAGE:0.5:9:4000 RRA:MIN:0.5:1:4000 RRA:MAX:0.5:1:4000 RRA:MIN:0.5:3:4000 RRA:MAX:0.5:3:4000 RRA:MIN:0.5:9:4000 RRA:MAX:0.5:9:4000

Um die Grafiken zu erzeugen verwende ich folgende Einträge in der crontab:

#!/bin/bash

/usr/bin/nice -n 19 /usr/bin/rrdtool graph /var/www/cloudtemp2d.png --start "now-1day" -a PNG -l -20 -u 20 -r -Y -t "Bedeckungsgrad (2 Tage)" -X 0 -w 800 -h 300 DEF:cloudtemp=/root/weather.rrd:tempdiff:AVERAGE DEF:cloudtempmin=/root/weather.rrd:tempdiff:MIN DEF:cloudtempmax=/root/weather.rrd:tempdiff:MAX VDEF:cloudtempacta=cloudtemp,LAST VDEF:cloudtempmina=cloudtempmin,MINIMUM VDEF:cloudtempmaxa=cloudtempmax,MAXIMUM  LINE1:cloudtemp#FF0000:"Bedeckungsgrad" GPRINT:cloudtempmina:"min %6.2lf" GPRINT:cloudtempacta:"act %6.2lf" GPRINT:cloudtempmaxa:"max %6.2lf" 2>&1 > /dev/null

/usr/bin/nice -n 19 /usr/bin/rrdtool graph /var/www/temperature2d.png --start -172800 -a PNG -Y --vertical-label "[C]" -t "Temperatur (2 Tage)" -X 0 -w 800 -h 300 DEF:temp=/root/weather.rrd:temperature:AVERAGE DEF:tempmin=/root/weather.rrd:temperature:MIN DEF:tempmax=/root/weather.rrd:temperature:MAX VDEF:tempacta=temp,LAST VDEF:tempmina=tempmin,MINIMUM VDEF:tempmaxa=tempmax,MAXIMUM  LINE1:temp#FF0000:"Temperatur" GPRINT:tempmina:"min %6.2lf" GPRINT:tempacta:"act %6.2lf" GPRINT:tempmaxa:"max %6.2lf" 2>&1 > /dev/null

/usr/bin/nice -n 19 /usr/bin/rrdtool graph /var/www/humidity2d.png --start -172800 -a PNG -Y --vertical-label "[%]" -t "rel. Feuchte (2 Tage)" -X 0 -w 800 -h 300 DEF:humidity=/root/weather.rrd:humidity:AVERAGE DEF:humiditymin=/root/weather.rrd:humidity:MIN DEF:humiditymax=/root/weather.rrd:humidity:MAX VDEF:humidityacta=humidity,LAST VDEF:humiditymina=humiditymin,MINIMUM VDEF:humiditymaxa=humiditymax,MAXIMUM  LINE1:humidity#FF0000:"rel. Feuchte" GPRINT:humiditymina:"min %6.2lf" GPRINT:humidityacta:"act %6.2lf" GPRINT:humiditymaxa:"max %6.2lf" 2>&1 > /dev/null

/usr/bin/nice -n 19 /usr/bin/rrdtool graph /var/www/pressure2d.png --start "now-4days" -a PNG -l 970 -u 1050 -r -Y --vertical-label "[hPa]" -t "Luftdruck (4 Tage)" -X 0 -w 800 -h 300 DEF:pressure=/root/weather.rrd:pressure:AVERAGE DEF:pressuremin=/root/weather.rrd:pressure:MIN DEF:pressurepmax=/root/weather.rrd:pressure:MAX VDEF:pressureacta=pressure,LAST VDEF:pressuremina=pressure,MINIMUM VDEF:pressuremaxa=pressure,MAXIMUM  LINE1:pressure#FF0000:"Luftdruck (hPa)" GPRINT:pressuremina:"min %6.2lf" GPRINT:pressureacta:"act %6.2lf" GPRINT:pressuremaxa:"max %6.2lf" 2>&1 > /dev/null

Der Code für den Arduino:

#include <EEPROM.h>
#include <SHT1x.h>

#define TEMP_PIN 0
#define PRESSURE_PIN 1
#define LED_PIN 13

#define dataPin  10
#define clockPin 11
SHT1x sht1x(dataPin, clockPin);

 // Höhenkorrektur =EXP(-138/7990) 138 Höhe Aschaffenburg
const float pressure_correction = 0.9828767094;
 
const int numReadings = 20;
int calValue;

int readings[numReadings];
int readingsPressure[numReadings];
float tempReadings[numReadings];
float humidReadings[numReadings];

int index = 0;
int total = 0;
int totalPressure = 0;
float totalTemp = 0.0;
float totalHumid = 0.0;

float average = 0.0;
float averagePressure = 0.0;
float averageTemp = 0.0;
float averageHumid = 0.0;

//This function will write a 2 byte integer to the eeprom at the specified address and address + 1
void EEPROMWriteInt(int p_address, int p_value)
{
        byte lowByte = ((p_value >> 0) & 0xFF);
        byte highByte = ((p_value >> 8) & 0xFF);

        EEPROM.write(p_address, lowByte);
        EEPROM.write(p_address + 1, highByte);
}

//This function will read a 2 byte integer from the eeprom at the specified address and address + 1
unsigned int EEPROMReadInt(int p_address)
{
        byte lowByte = EEPROM.read(p_address);
        byte highByte = EEPROM.read(p_address + 1);

        return ((lowByte << 0) & 0xFF) + ((highByte << 8) & 0xFF00);
}

void setup() {
        Serial.begin(9600);

        // zero out array
        for (int thisReading=0; thisReading<numReadings;++thisReading) {
                readings[thisReading] = 0;
                readingsPressure[thisReading] = 0;
                tempReadings[thisReading] = 0.0;
                humidReadings[thisReading] = 0.0;
        }
 
        // read calibration value from EEPROM
        calValue=EEPROMReadInt(0);

        digitalWrite(LED_PIN,HIGH);
}

void loop() {
        total = total - readings[index];
        totalPressure = totalPressure - readingsPressure[index];
        totalTemp = totalTemp - tempReadings[index];
        totalHumid = totalHumid - humidReadings[index];

        digitalWrite(LED_PIN,LOW);        
        readings[index] = analogRead(TEMP_PIN);
        readingsPressure[index] = analogRead(PRESSURE_PIN);
        tempReadings[index]=sht1x.readTemperatureC();
        humidReadings[index]=sht1x.readHumidity();
        digitalWrite(LED_PIN,HIGH);

        
        total = total + readings[index];
        totalPressure = totalPressure + readingsPressure[index];
        totalTemp = totalTemp + tempReadings[index];
        totalHumid = totalHumid + humidReadings[index];
        
        index++;

        if (index >= numReadings) {
                index=0;
        }

        // Float erzwingen
        average = (0.0+total) / (0.0+numReadings);
        averagePressure = (0.0+totalPressure) / (0.0+numReadings);
        averageTemp = totalTemp / numReadings;
        averageHumid = totalHumid / numReadings;

        if (Serial.available()>0) {
                int c=Serial.read();
                if (c=='c') {
                        // calibrate
                        calValue=total/numReadings;
                        EEPROMWriteInt(0,calValue);
                }
                Serial.print(calValue-average);
                Serial.print(";");
              
                // Formel stammt aus dem Datenblatt des Drucksensors
                float pressure=((averagePressure/1024.0)+0.1518)/(0.001059);
                pressure/=pressure_correction;
                
                Serial.print(pressure);
                Serial.print(";");
                Serial.print(averageTemp);
                Serial.print(";");
                Serial.println(averageHumid);
        }
        delay(10);
}

Der Ruby-Code, der die Updates in die RRD-Datenbank schreibt:

#!/usr/bin/ruby

require "serialport"
require "RRD"

def dewpoint(temp,humidity)
  a=(241.2*Math.log(humidity/100.0)+((4222.03716*temp)/241.2+temp))
  b=(17.5042-Math.log(humidity/100.0)-((17.5043*temp)/241.2+temp))
  a/b
end

def frostpoint(temp,humidity)
  a=(272.186*Math.log(humidity/100.0)+((6107.85384*temp)/(272.186+temp)))
  b=(22.4433-Math.log(humidity/100.0)-(22.4433*temp)/(272.186+temp))
  a/b
end

sp=SerialPort.new "/dev/ttyUSB0", 9600, 8, 1, SerialPort::NONE
sp.read_timeout=2000

# Ignore first readings
SKIP=10
skipcount=0

while true
   sp.putc(' ')
   sleep(1)
   val=sp.gets

   next if !val

   val.chomp!
   values=val.split(';')

   t=Time.now.to_i
   if skipcount>SKIP
       RRD.update("/root/weather.rrd","#{t}:#{values[0]}:#{values[1]}:#{values[2]}:#{values[3]}")
   end
   sleep(30)
   skipcount+=1
end

Post new comment

  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You may post PHP code. You should include <?php ?> tags.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Images can be added to this post.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
Image CAPTCHA
Enter the characters shown in the image.