Observatory weather station

Temperature, Humidity and Dewpoint

The AOSONG DHT22 (aka AM2302) relative humidity and temperature sensors are both cheap and mostly sufficient. Do not get them totally wet from rain as the humidity sensing part then no longer works, even when the sensor has dried up later completely.

Dewpoint is calculated from their measurements as follows:

my $gamma = ( (17.27 * $temperature) / (237.7 + $temperature) ) + log (($humidity + 0.001)/ 100);
my $dewpoint = sprintf("%0.2f", (237.7 * $gamma) / (17.27 - $gamma));
The 0.001 offset to the humidity in the formula is to prevent the log from exploding when the sensor gives a false reading of 0 humidity :)

This code gets a reading from a sensor given a pin on a RPI. Example output is :

pin 16 Humidity = 42.70 % Temperature = 17.40 *C
I use a few of these sensors; one in the observatory, one outside and one in the all sky camera enclosure.

Reading them out with this code which applies a correcting offset to both the temperature and the humidity and prepares the output for rrdtool. The results are stored in /dev/shm/ which does not stress the RPI SD card. They're later picked up by the script and sent to the database server.

Example results :

temp small now humidit small now dew small now

The jagged PiCamera humidity and the PiBucketCamera temperature lines are because of the anti-dew heating system which is set to 10 degrees Celsius.


The Hydreon RG-11 rainsensor can be configured to detect a single drop of rain, and is mostly maintenance free. When in rain-drop mode it lets an internal relay pulse, and the duration of the pulse characterizes how large the drop was, or how many drops it detected per polling interval.

RG11 rain sensor rain sensor pulse

I wrote this code which continuously polls the status of the relay from a RPI. It stores the number of counted drops of the last 60 seconds in /dev/shm/value_raindrop_sum, and the number of drops counted for the last 60 minutes in /dev/shm/value_raindrop_history.

Example output of a dry hour :

rpi:/dev/shm/ cat value_raindrop_sum
rpi:/dev/shm/ cat value_raindrop_history
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
On heavy rain I've seen these numbers rise to about 250.

Example result of a day that had some rain :

rain small now

Cloud coverage

I use both a wide angle Melexis MLX90614-BAA and a narrow angle MLX90614-BCC infrared thermometer. These devices effectively measure both the sky temperature as well as the device temperature. The difference between these two values van be used to measure cloud coverage.

Here's the MLX90614-BAA and MLX90614-BCC sensors when they were still new :

20150820-Melexis-MLX90614-BAA-BCC.jpg 20150830-Melexis-MLX90614-BAA-BCC-prep.jpg

And here's how they look like after being outside for over 8 years :


These two sensors are exposed naked to the sky. The -BAA model sensing window can be cleaned, the BCC model sensing window is a few mm deep in a narrow cavity. I cannot reach it and thus cannot clean it. This limits the use of the BCC sensor until I find a shield that is transparent to far-infrared.

These sensors are to be read with the i2c protocol. Each device has an address on a bus where multiple devices can be attached to. Neat. I wrote some code to read out the MLX sensors on a RPI and store the results in /dev/shm/ files.

Example output of the BAA sensor :

rpi:/dev/shm/ cat value_skytemperature-BAA_object
rpi:/dev/shm/ cat value_skytemperature-BAA_sensor

Example graphs :

skytemp small now 1d a skytemp small now 1d

On the first graph the BAA sensor was dry and producing fair readings most of the day. The BCC sensor was wet the whole day which is why it was the same as the device temperature readings. On the second graph both sensors were dry the whole day and the readings from 18:00-20:30 show nice clear skies.

Sky brightness

The AMS TSL237 light to frequency converter for night readings was the most challenging device to read from a RPI because its signal frequency goes up to 625 kHz according to specs, I found it saturates at around 400 kHz. Still not something you want to attach an interrupt handler to. So I used polling and the pigpio library on a RPI to handle this.

The sky brightness can then be calculated with 22.0 - 2.5*log10(hz) and reports in mag/arcsec^2.

Calibration. My first reading comparison in 2015 was a measurement of 18.77 against what my sqm-l produced: 18.75 mag/arcsec^2. Very good. After a few years of continuous use and outdoor exposure however I found I needed to add an offset to these readings. I now use :

rpi:~/ cat sqm_offset.conf
# sqm offset
offset = -2.975
The code that I wrote maintains its results in /dev/shm/value_sqm_sqm and is called from here which sends all /dev/shm results on to the database server.

For daylight readings the AMS TSL237 sensor quickly saturates. The ROHM BH1750FVI ambient light sensor takes over at these times. It is an i2c device, so is read together with the MLX sensors here. Its results are given in lx.

Example graphs :

sqm small now luminosity small now

Data bus

I wanted a very lightweight system to send data from the various RPI over the network to a database. When the database is down or cannot be reached over the network the sending of the measurement data need not queue, it can just be dropped. The measurement data is very short in length. The initial target was just to generate rrdtool graphs, later when I wrote a weather safety service the sensor data was written to a MySQL database as well. All the values that are written to /dev/shm/ also come with an rrdtool argument line in another file in /dev/shm/ which all start with the rrdupdate string as identifier. A loop iterates over everything it finds in /dev/shm/rrdupdate* and sends that to a listener service at the database server.

Example output :

rpi:/dev/shm/ cat rrdupdate_luminosity
update luminosity.rrd -t luminosity N:0.00

rpi:/dev/shm/ cat rrdupdate_skytemperature-BAA
update skytemperature-BAA.rrd -t BAA_sensor:BAA_sky N:6.60:-10.64

rpi:/dev/shm/ cat rrdupdate_tempandhum-observatory
update tempandhum-observatory.rrd -t temperature:humidity:dewpoint N:16.60:58.30:8.38


A listener service receives all the data bus updates and calls another script to write the sensor values to the MySQL database and also calls rrdtool to update the rrdtool databases.

The database script uses 1 row per minute. It gathers all sensor data in 1 row and when a minute has passed when a new sensor reading arrives it creates a new row.

MySQL Database

The MySQL database is nice and small, it has grown to 60 MiB in 2 years and is updated several times per minute, creating a new row per minute. The listener and the MySQL and rrdtool databases live in a LXD container.

The database schema for the sensors is :

mysql   SHOW CREATE TABLE sensors\G
*************************** 1. row ***************************
       Table: sensors
Create Table: CREATE TABLE `sensors` (
  `sensors_id` int(11) NOT NULL AUTO_INCREMENT,
  `create_time` datetime NOT NULL,
  `observatory_temperature1` float(5,2) DEFAULT NULL,
  `observatory_humidity1` float(5,2) DEFAULT NULL,
  `observatory_dewpoint1` float(5,2) DEFAULT NULL,
  `outside_temperature1` float(5,2) DEFAULT NULL,
  `outside_humidity1` float(5,2) DEFAULT NULL,
  `outside_dewpoint1` float(5,2) DEFAULT NULL,
  `BAA1_temperature_sky` float(5,2) DEFAULT NULL,
  `BAA1_temperature_sensor` float(5,2) DEFAULT NULL,
  `BCC1_temperature_sky` float(5,2) DEFAULT NULL,
  `BCC1_temperature_sensor` float(5,2) DEFAULT NULL,
  `sqm1_luminosity` float(7,2) DEFAULT NULL,
  `sqm1_frequency` int(11) DEFAULT NULL,
  `sqm1_sqm` float(4,2) DEFAULT NULL,
  `rainsensor1_pulses` int(11) DEFAULT NULL,
  `rainsensor1_drops` int(11) DEFAULT NULL,
  `allskycam1_temperature1` float(5,2) DEFAULT NULL,
  `allskycam1_humidity1` float(5,2) DEFAULT NULL,
  `allskycam1_dewpoint1` float(5,2) DEFAULT NULL,
  `allskycam1_stars` int(11) DEFAULT NULL,
  `ups1_status` tinyint(1) DEFAULT NULL,
  `ups1_linev` float(5,2) DEFAULT NULL,
  `ups1_loadpct` float(5,2) DEFAULT NULL,
  `ups1_bcharge` float(5,2) DEFAULT NULL,
  `ups1_timeleft` float(5,2) DEFAULT NULL,
  `ups1_itemp` float(5,2) DEFAULT NULL,
  `ups1_battv` float(5,2) DEFAULT NULL,
  `ups1_linefreq` float(5,2) DEFAULT NULL,
  PRIMARY KEY (`sensors_id`),
  KEY `create_time` (`create_time`)
1 row in set (0.00 sec)
The last complete minute as of this writing as an example:
mysql   SELECT * FROM sensors ORDER BY sensors_id DESC LIMIT 2\G
*************************** 1. row ***************************
              sensors_id: 741497
             create_time: 2020-10-13 19:58:32
observatory_temperature1: 16.40
   observatory_humidity1: 58.20
   observatory_dewpoint1: 8.16
    outside_temperature1: NULL
       outside_humidity1: NULL
       outside_dewpoint1: NULL
    BAA1_temperature_sky: 2.08
 BAA1_temperature_sensor: 8.36
    BCC1_temperature_sky: 4.36
 BCC1_temperature_sensor: 8.00
         sqm1_luminosity: 0.00
          sqm1_frequency: 6
                sqm1_sqm: 17.12
      rainsensor1_pulses: 0
       rainsensor1_drops: 0
 allskycam1_temperature1: 11.00
    allskycam1_humidity1: 56.00
    allskycam1_dewpoint1: 2.56
        allskycam1_stars: 0
             ups1_status: 1
              ups1_linev: 230.40
            ups1_loadpct: 0.00
            ups1_bcharge: 100.00
           ups1_timeleft: 270.00
              ups1_itemp: 30.60
              ups1_battv: 27.40
           ups1_linefreq: 50.00

Rrdtool Databases

Creation of the databases :
rrdtool create tempandhum-observatory.rrd -s 60
 # 1d at 60s resolution
 # 1w at 10m resolution
 # 1m at 1h resolution
 # 1y at 6h resolution

rrdtool create rainsensor.rrd -s 60

rrdtool create skytemperature-BAA.rrd -s 60

rrdtool create sqm.rrd -s 60
Updating is done in the listener.


From cron every minute a graph script is called which generates the graphs from the rrdtool databases. The graphs are live here.

Weather Safety Service

The objective of the Weather Safety Service is to produce a safe-to-open or a must-close signal from all the weather station sensor data, augmented with other safety parameters like the status of the UPS. Every minute the sensors_to_database.py script calls query_sky_and_obsy_conditions.py which implements the Weather Safety Service.

It looks at sensor readings of the last completed minute, as well as those of the last hour to implement hysteresis to prevent flapping. The result is stored in another table in the database :

mysql   SHOW CREATE TABLE roof\G
*************************** 1. row ***************************
       Table: roof
Create Table: CREATE TABLE `roof` (
  `roof_id` int(11) NOT NULL AUTO_INCREMENT,
  `create_time` datetime NOT NULL,
  `sensors_id` int(11) DEFAULT NULL,
  `open_ok` tinyint(1) DEFAULT NULL,
  `reasons` varchar(128) DEFAULT NULL,
  PRIMARY KEY (`roof_id`)
1 row in set (0.00 sec)
From which data mining can be done. For instance here are the reasons why the observatory ever had to close while in robotic mode:
mysql   SELECT count(*), reason FROM events WHERE event = 'closing' GROUP BY reason;
| count(*) | reason                  |
|      138 | Not dark enough anymore |
|      178 | Too cloudy              |
|        3 | UPS on battery          |
3 rows in set (0.01 sec)

Weather Safety Proxy

The above roof table can be queried by the INDI Weather Safety Proxy device driver.

Last page update : 2023-10-30T16:41Z -- Hans Lambermont