Gnome Forecaster

Our Gnome becomes a weather forecaster with an Arduino Nano Every and a pressure sensor. Get your local forecast without the internet.

Components and Supplies

Necessary Tools and Machines

  • 3D Printer (generic)

About This Project

The Inspiration

Some of you might know the color changing little statues that are sold as souvenirs in many European cities. These white statues are painted with a specific chemical substance that reacts to air humidity and change the color from light blue to pink, going through other hues in a continuous change. They are supposed to forecast the local weather in the short term. They actually show the current situation but the forecasting powers are very limited and inaccurate.

Let these little statues tell you the weather forecast with their color.
Let these little statues tell you the weather forecast with their color.

Let these little statues tell you the weather forecast with their color.
Let these little statues tell you the weather forecast with their color.

Let these little statues tell you the weather forecast with their color.
Let these little statues tell you the weather forecast with their color.

Starting from that we decided to make a Weather Forecasting Gnome, based on the new Arduino Nano Every and the BME280 sensor. The task that that at the beginning seemed easy turned out to be quite a challenge. The plain barometer is something that allows to have a short term forecast as it tells you if the pressure is raising or lowering and according to the relative pressure calculated as the sea level pressure offset by the local altitude it can also give you some “High” and“Low” reading.

A barometer.
A barometer.

To get to a more accurate forecast, also relative humidity and winds are taken in account and the classic barometer is replaced by a more complex forecast device, the “Zambretti Forecaster”.

The Zambretti Forecaster.
The Zambretti Forecaster.

This forecaster has been documented by the authors Zambra end Negretti back in 1920 and it has become the solution for a local weather forecast in many microcontroller based projects. You can find the full details on the forecaster here (http://drkfs.net/zambretti.htm) and we also found the work of Fandonov (https://github.com/fandonov/weatherstation) very useful for his implementation of the Zambretti forecaster in his e-Ink display weather forecaster.

The Components

We are running this project on our Arduino Nano Every plus to which we added the BME280 sensor from Bosch, a real time clock DS3231 and a tiny strip of eight WS2812B smart LEDs.

The BME280 is capable of measuring temperature, pressure, humidity and give also some clues about absolute humidity and altitude, but as we will see further down, the altitude and pressure are bound together and for our purposes we need to find the real altitude of our Gnome Weather Forecaster.

The DS3231 real time clock module is needed because part of the Zambretti calculation is bound to the season and the only way for this device to know about the season is through a real time clock module properly set. It might look as a waste of resources, but you can improve the code we are supplying with features like a brightness control according to the time of the day and other amenities and use more the RTC in the sketch.

The last component, the strip of LEDs is the equivalent of the colored paint of the little statues that change its color. Here we are taking advantage of the RGB colors to represent the weather forecast with a little more detail, even if the representation of the full 26 different conditions of the Zambretti forecast are too much for a simple color display.

The 26 possible weather forecasts provided by the Zambretti Forecaster.
The 26 possible weather forecasts provided by the Zambretti Forecaster.

We decided to simplify it and it is a work in progress you can easily change to a different color coding.

Wiring

The two main modules are both based on I2C and therefore the wiring is really simple.The RTC module we are using has even a pass through for the four connections and this makes our wiring a 10 minutes work. VCC and GND are taken from Arduino Nano Every 3V3 (Red) and GND (Black) pins on the header and soldered to the corresponding pins on one side of the DS3231 module, then the I2C interface is as usual on A4 and A5 of the Arduino. A4 is SDA (blue wire in the pictures) and A5 is SCL (yellow wire in the picture). We take the same four connections from the other side of the RTC module and wire them to the BME280 module just taking care of the order. There is no standard for the VCC / GND / SDL / SCA sequence and each module might have a different order that needs care.

BME280 I2C wiring
BME280 I2C wiring

DS3231 module I2C Wiring with Arduino Nano Every on the right and BM280 on the left
DS3231 module I2C Wiring with Arduino Nano Every on the right and BM280 on the left

Arduino Nano Every connections to the DS3231 I2C module
Arduino Nano Every connections to the DS3231 I2C module

The LED strip has the usual three labels VDC, DIN and GND. This time the power supply will comes from the 5V pin. One reason is that we have the pad free for soldering, the other is that we are feeding the board from USB and 5V is from USB directly, with no stress for the power regulator of the Nano.

The data is fed to the strip on D7 pin, but could be any other one you may find suitable.Just edit the sketch with the pin you chose in the #define LED_PIN 7 line close to the top.

Our LED strip has DIN on one side and DOUT on the other. White wire is for Data IN.
Our LED strip has DIN on one side and DOUT on the other. White wire is for Data IN.

DIN to the LED strip is on D7 with White wire; Red wire is connected to 5V.
DIN to the LED strip is on D7 with White wire; Red wire is connected to 5V.

This is it for the wiring. We will power the circuit via USB because it needs to be continuously on to monitor the environmental parameters and make a forecast. A power bank for a mobile phone might power the circuit for quite some hours, but It would inevitably run out of energy.

Schematics

Complete Circuit.

The Sketch

To get this project working we need several libraries and you find them in the first lines of the sketch as #INCLUDEs.

Wire is necessary for the I2C communication, the Adafruit_Sensors and Adafruit_BME280 allow us to read the data from the pressure, humidity and temperature sensor. The LED strip is managed by the Adafruit_NeoPixel lib that gives us a quick and effective control over each pixel color. Last, but not least important is RTCLib that allows us to read all the information from the DS3231 Real Time Clock.

The constants we use are those for the average sea level pressure and for the Led strip. The value we are using for the average sea level pressure is 1013,25 mbar, equivalent to 101,325 kPa and it is the value recognised internationally.

After the initialisation of the various devices, we define some variables that will be used for the forecast and also for some textual output on the Serial Monitor. If you want to see what’s going on with the weather in depth, you can connect the pc and read the data supplied, otherwise, you might simplify a lot the sketch just avoiding all the string formatting and printing.

We decided to keep these debug information available to keep also some educational value in the project.

The array starts empty and the code is filling it every 10 minutes with a new value so that the last cell of the array contains the newest pressure reading. The value that contains the pressure is seapressure and it is calculated to compensate the height at which the device is placed.

A very important variable is the altitude of the device. We know the sea level pressure and we need to adjust the reading from the sensor so that we can feed into the Zambretti formula the pressure we would read at sea level starting from what we read from the sensor. There is a formula that does this calculation knowing a series of parameters and if you have at hand a GPS (even your smartphone could have one) to read the altitude, you can put that value in the altitude variable. With that information, the formula is

The formula.
The formula.

With the following:

P0 is the relative sea-level pressure;

P is the station pressure in hPa;

h is the altitude in meters and

T is the temperature in Celsius.

With the output of this formula we have the pressure brought at the sea level, so that we can feed it into our array. The array is like a shift register and any time a new value is added, all the former values are shifted one position left (the oldest value is at 0 and the newest is at 9).

With this array of 10 values, spaced in time by 10 minutes, the Zambretti formula finds out the pressure variation tendency (falling, stable or rising) and values on a sliding window of values collected in the last 100 minutes. In the formula alsothe month is taken in account because Zambra and Negretti found it was relevant to get a more accurate forecast.

For the first 100 minutes however, the device is collecting the first complete set of pressure readings, therefore the forecast will be completely wrong. At the completion of this set, the forecast will be adjusted every 10 minutes.

The following lines replicate the physical object to find out the letter corresponding to the forecast using the three windows (falling / steady / rising) and the pressure reading. This means that for the same pressure reading, it is the short term change that makes the difference as well as summer/winter seasons. It is done in the function calc_zambretti that gets as input the oldest three values of pressure averaged, the last three values averaged and finally the month to spot the season.

The long list of if… then… else finally pics the Zambretti output and rearranges the 26 messages in simpler groups that are Sunny, Sunny Cloudy, Worsening, Cloudy, Rainy, telling us if the pressure is raising, stable or falling as well. We kept the original algorithm of Fandonov )because it allows you to understand very well how the forecast works, then we had simplified the output coding the five possible situations into five different LED colors. The usage of a strip allows the slow change from one color into the other, scrolling up one LED at each new forecast; this is also a solution to give you the trend of the forecast.

We implement the scrolling of the led bar with a simple routine that copies the content of each led to the next one starting from the top, so that it shifts everything up, then the new value is defined by the Zambretti algorithm and it is written in the first LED position.

Code

Open this sketch in your Arduino IDE and make sure you have downloaded all the libraries needed.

1#include <Wire.h>
2#include <Adafruit_Sensor.h>
3#include <Adafruit_BME280.h>
4#include <Adafruit_NeoPixel.h>
5#include "RTClib.h"
6
7#define SEALEVELPRESSURE_HPA (1013.25)
8#define LED_PIN 7
9#define NumLEDs 8
10
11RTC_DS3231 rtc; // initialise the RTC DS3231
12Adafruit_BME280 bme; // initialise the BME280 sensor
13Adafruit_NeoPixel strip(NumLEDs, LED_PIN, NEO_GRB + NEO_KHZ800);
14
15unsigned long delayTime; // refresh rate for the readings
16int t_hour = 0;
17int t_minute = 0;
18int oldpos = 0; //Variables to scroll the LED bar
19int newpos = 0;
20
21
22int pressureArray[10] = {0}; // here we store the pressure readings
23byte counter = 0;
24byte delta_time = 0;
25int Z = 0;
26
27char tStr[21];
28char pStr[22];
29char hStr[20];
30char pseaStr[26];
31char timeStr[6];
32char dateStr[12];
33char zambretti[10] = "N/A";
34char pressureHistory[57];
35char pressureHistory2[57];
36
37int temperature;
38int humidity;
39int pressure;
40int altitude = 355; // Here you should put the REAL altitude of the iGnome
41
42
43void setup() {
44 bool status;
45 Serial.begin(57600); // default settings
46 Serial.println("Starting measurements");
47 if (! rtc.begin()) {
48 Serial.println("Couldn't find RTC");
49 while (1);
50 }
51
52 if (rtc.lostPower()) {
53 Serial.println("RTC lost power, lets set the time!");
54 // following line sets the RTC to the date & time this sketch was compiled
55 rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
56
57 }
58 status = bme.begin();
59 if (!status) {
60 while (1);
61 }
62 delayTime = 20000;
63
64 strip.begin(); // INITIALISE the LED strip object
65 strip.show(); // Turn OFF all pixels after init
66 strip.setBrightness(100); // Set BRIGHTNESS to about 2/5 (max = 255)
67}
68
69
70void loop() {
71
72 temperature = (int)bme.readTemperature();
73 humidity = (int)bme.readHumidity();
74 pressure = (int)(bme.readPressure() / 100.0F);
75
76 // Let's see the reading that will determine the forecast
77
78 Serial.print("Measured altitude ");
79 Serial.println((int)bme.readAltitude(SEALEVELPRESSURE_HPA));
80
81 int seapressure = station2sealevel(pressure, altitude, temperature);
82
83 DateTime now = rtc.now();
84 int t_hour2 = now.hour();
85 int t_minute2 = now.minute();
86
87
88 if (t_hour2 != t_hour or t_minute2 != t_minute) {
89 delta_time++;
90 if (delta_time > 10) { // every minute we increment delta_time, then every 10 minutes
91 delta_time = 0; // we store the value in the array
92
93 if (counter == 10) // if we read 10 values and filled up the array, we shift the array content
94 {
95 for (int i = 0; i < 9; i++) { // we shift the array one position to the left
96 pressureArray[i] = pressureArray[i + 1];
97 }
98 pressureArray[counter - 1] = seapressure;
99 }
100 else { // this code fills up the pressure array value by value until is filled up
101 pressureArray[counter] = seapressure;
102 counter++;
103 }
104 }
105
106 Z = calc_zambretti((pressureArray[9] + pressureArray[8] + pressureArray[7]) / 3, (pressureArray[0] + pressureArray[1] + pressureArray[2]) / 3, now.month());
107
108// From here is just for debug purposes on the serial monitor
109
110 sprintf(zambretti, "Z=%d", Z);
111 sprintf(tStr, "%d C", temperature);
112 sprintf(hStr, "%d %%", humidity);
113 sprintf(pStr, "%d hPa", pressure);
114 sprintf(pseaStr, "%d hPa", seapressure);
115 sprintf(dateStr, "%02d.%02d.%d", now.day(), now.month(), now.year());
116 sprintf(timeStr, "%02d:%02d", now.hour(), now.minute());
117 sprintf(pressureHistory, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,", pressureArray[0], pressureArray[1], pressureArray[2],
118 pressureArray[3], pressureArray[4], pressureArray[5], pressureArray[6], pressureArray[7], pressureArray[8], pressureArray[9]);
119
120 Serial.println(zambretti);
121 Serial.println(tStr);
122 Serial.println(hStr);
123 Serial.println(pStr);
124 Serial.println(pseaStr);
125 Serial.println(dateStr);
126 Serial.println(timeStr);
127 Serial.println(pressureHistory);
128 Serial.println(pressureHistory2);
129
130// END of testing purposes
131
132 StripScroll(); // advance the LED colors in the strip preparing for the new entry
133
134 if (pressureArray[9] > 0 and pressureArray[0] > 0) {
135 if (pressureArray[9] + pressureArray[8] + pressureArray[7] - pressureArray[0] - pressureArray[1] - pressureArray[2] >= 3) {
136 //RAISING
137 Serial.println("Raising");
138 if (Z < 3) {
139 Serial.println("Sunny");
140 strip.setPixelColor(0, 255, 127, 0);
141 }
142 else if (Z >= 3 and Z <= 9) {
143 Serial.println("Sunny Cloudy");
144 strip.setPixelColor(0, 0, 255, 255);
145 }
146 else if (Z > 9 and Z <= 17) {
147 Serial.println("Cloudy");
148 strip.setPixelColor(0, 127, 0, 255);
149 }
150 else if (Z > 17) {
151 Serial.println("Rainy");
152 strip.setPixelColor(0, 255, 0, 127);
153 }
154 }
155
156 else if (pressureArray[0] + pressureArray[1] + pressureArray[2] - pressureArray[9] - pressureArray[8] - pressureArray[7] >= 3) {
157 //FALLING
158 Serial.println("Falling");
159 if (Z < 4) {
160 Serial.println("Sunny");
161 strip.setPixelColor(0, 255, 127, 0);
162 }
163 else if (Z >= 4 and Z < 14) {
164 Serial.println("Sunny Cloudy");
165 strip.setPixelColor(0, 0, 255, 255);
166 }
167 else if (Z >= 14 and Z < 19) {
168 Serial.println("Worsening");
169 strip.setPixelColor(0, 192, 0, 255);
170 }
171 else if (Z >= 19 and Z < 21) {
172 Serial.println("Cloudy");
173 strip.setPixelColor(0, 127, 0, 255);
174 }
175 else if (Z >= 21) {
176 Serial.println("Rainy");
177 strip.setPixelColor(0, 255, 0, 127);
178 }
179 }
180 else {
181 //STEADY
182 Serial.println("Steady");
183 if (Z < 5) {
184 Serial.println("Sunny");
185 strip.setPixelColor(0, 255, 127, 0);
186 }
187 else if (Z >= 5 and Z <= 11) {
188 Serial.println("Sunny Cloudy");
189 strip.setPixelColor(0, 0, 255, 255);
190 }
191 else if (Z > 11 and Z < 14) {
192 Serial.println("Cloudy");
193 strip.setPixelColor(0, 127, 0, 255);
194 }
195 else if (Z >= 14 and Z < 19) {
196 Serial.println("Worsening");
197 strip.setPixelColor(0, 192, 0, 255);
198 }
199 else if (Z >= 19) {
200 Serial.println("Rainy");
201 strip.setPixelColor(0, 255, 0, 127);
202 }
203 }
204 }
205 else {
206 if (seapressure < 1005) {
207 Serial.println("Rainy");
208 strip.setPixelColor(0, 255, 0, 127);
209 }
210 else if (seapressure >= 1005 and seapressure <= 1015) {
211 Serial.println("Cloudy");
212 strip.setPixelColor(0, 127, 0, 255);
213 }
214 else if (seapressure > 1015 and seapressure < 1025) {
215 Serial.println("Sunny Cloudy");
216 strip.setPixelColor(0, 0, 255, 255);
217 }
218 else {
219 Serial.println("Rainy");
220 strip.setPixelColor(0, 255, 0, 127);
221 }
222 }
223
224 t_hour = t_hour2;
225 t_minute = t_minute2;
226
227 }
228 strip.show();
229 delay(delayTime);
230
231}
232
233
234int calc_zambretti(int curr_pressure, int prev_pressure, int mon) {
235 if (curr_pressure < prev_pressure) {
236 //FALLING
237 if (mon >= 4 and mon <= 9)
238 //summer
239 {
240 if (curr_pressure >= 1030)
241 return 2;
242 else if (curr_pressure >= 1020 and curr_pressure < 1030)
243 return 8;
244 else if (curr_pressure >= 1010 and curr_pressure < 1020)
245 return 18;
246 else if (curr_pressure >= 1000 and curr_pressure < 1010)
247 return 21;
248 else if (curr_pressure >= 990 and curr_pressure < 1000)
249 return 24;
250 else if (curr_pressure >= 980 and curr_pressure < 990)
251 return 24;
252 else if (curr_pressure >= 970 and curr_pressure < 980)
253 return 26;
254 else if (curr_pressure < 970)
255 return 26;
256 }
257 else {
258 //winter
259 if (curr_pressure >= 1030)
260 return 2;
261 else if (curr_pressure >= 1020 and curr_pressure < 1030)
262 return 8;
263 else if (curr_pressure >= 1010 and curr_pressure < 1020)
264 return 15;
265 else if (curr_pressure >= 1000 and curr_pressure < 1010)
266 return 21;
267 else if (curr_pressure >= 990 and curr_pressure < 1000)
268 return 22;
269 else if (curr_pressure >= 980 and curr_pressure < 990)
270 return 24;
271 else if (curr_pressure >= 970 and curr_pressure < 980)
272 return 26;
273 else if (curr_pressure < 970)
274 return 26;
275 }
276 }
277 else if (curr_pressure > prev_pressure) {
278 //RAISING
279 if (mon >= 4 and mon <= 9) {
280 //summer
281 if (curr_pressure >= 1030)
282 return 1;
283 else if (curr_pressure >= 1020 and curr_pressure < 1030)
284 return 2;
285 else if (curr_pressure >= 1010 and curr_pressure < 1020)
286 return 3;
287 else if (curr_pressure >= 1000 and curr_pressure < 1010)
288 return 7;
289 else if (curr_pressure >= 990 and curr_pressure < 1000)
290 return 9;
291 else if (curr_pressure >= 980 and curr_pressure < 990)
292 return 12;
293 else if (curr_pressure >= 970 and curr_pressure < 980)
294 return 17;
295 else if (curr_pressure < 970)
296 return 17;
297 }
298 else
299 //winter
300 {
301 if (curr_pressure >= 1030)
302 return 1;
303 else if (curr_pressure >= 1020 and curr_pressure < 1030)
304 return 2;
305 else if (curr_pressure >= 1010 and curr_pressure < 1020)
306 return 6;
307 else if (curr_pressure >= 1000 and curr_pressure < 1010)
308 return 7;
309 else if (curr_pressure >= 990 and curr_pressure < 1000)
310 return 10;
311 else if (curr_pressure >= 980 and curr_pressure < 990)
312 return 13;
313 else if (curr_pressure >= 970 and curr_pressure < 980)
314 return 17;
315 else if (curr_pressure < 970)
316 return 17;
317 }
318 }
319 else {
320 if (curr_pressure >= 1030)
321 return 1;
322 else if (curr_pressure >= 1020 and curr_pressure < 1030)
323 return 2;
324 else if (curr_pressure >= 1010 and curr_pressure < 1020)
325 return 11;
326 else if (curr_pressure >= 1000 and curr_pressure < 1010)
327 return 14;
328 else if (curr_pressure >= 990 and curr_pressure < 1000)
329 return 19;
330 else if (curr_pressure >= 980 and curr_pressure < 990)
331 return 23;
332 else if (curr_pressure >= 970 and curr_pressure < 980)
333 return 24;
334 else if (curr_pressure < 970)
335 return 26;
336
337 }
338}
339
340int station2sealevel(int p, int height, int t) { // from pressure at our height to sea level
341 return (double) p * pow(1 - 0.0065 * (double)height / (t + 0.0065 * (double)height + 273.15), -5.275);
342}
343
344void StripScroll() { // scroll up all the LED strip by 1
345 for (int led = 0; led <= (NumLEDs); led++)
346 {
347 oldpos = NumLEDs - led - 2;
348 newpos = oldpos + 1;
349 strip.setPixelColor(newpos, strip.getPixelColor(oldpos));
350 }
351}

Putting Everything Together

We are hosting the circuit in our “standard” 3D-printed Gnome. This time the BME280 is partially out of the back of the Gnome, from his heels, while the USB is accessible at mid height. You can modify the STL files we already shared in our former project Gnome Traveller to let the sensor and the USB port be accessible from the outside.

USB and pressure sensor are left accessible from the back of our Gnome
USB and pressure sensor are left accessible from the back of our Gnome

The inside of our Gnome with the RTC, NANO Every and LED bar visible
The inside of our Gnome with the RTC, NANO Every and LED bar visible

The wiring is really simple and therefore putting everything in the 3D-printed Gnome should be very easy. In our setup the strip is in the upper part of the Gnome so that the head and hat light up with the color of the forecast. Keeping the sensor outside the Gnome is best as it allows it to get air from the environment. The best results require the sensor to be in the open air and not your apartment where humidity and temperature are kept under control.

A solution could be to keep the gnome close to a window and keep the sensor outside with a 4 wires cable link; I2C can tolerate up to one meter and that is more than you need to put the sensor out of the window.

Load the Code and Monitor the Readings

When all the hardware is set up, you can proceed with the sketch. Copy it from here and also check with Library Manager that you have all the needed libraries:

  • Adafruit_Sensor
  • Adafruit_BME280
  • Adafruit_NeoPixel
  • RTClib

The Arduino Nano Every uses the MegaAVR core and you should have that properly installed as well: if you have the board in the list of the available ones it is installed, otherwise you need to use the Board Manager to install it.

Launch the verify of the sketch just to be sure everything is correct and then upload. Open the Serial Monitor and look at the data that slowly will be printed. Every minute you get a pressure reading, every ten minutes the reading is stored.

Get a more detailed forecast looking at the Serial Monitor output and read the values used by the sketch
Get a more detailed forecast looking at the Serial Monitor output and read the values used by the sketch

Take a break and let the array fill up with the real data. Come back after a couple of hours and what you see and read should be meaningful.

Yellow is sunny, azure is cloudy and then it goes to purple and red when the weather worsens up to the rain.

Gnome STL Files

Gnome Lower Part

Gnome Upper Part

Make It Your Own

We kept the text in the sketch so that you can easily change the color for each of the managed weather conditions. The syntax is strip.setPixelColor(LED, R, G, B); with LED that should always be the first in the strip (0) and RGB values from 0 to 255. There is an overall brightness setting that you can use to get a different intensity of the LED throughout the day; look for the strip.setBrightness(100); line and turn it into something related to night and day reading the RTC.

Suggest changes

The content on docs.arduino.cc is facilitated through a public GitHub repository. If you see anything wrong, you can edit this page here.

License

The Arduino documentation is licensed under the Creative Commons Attribution-Share Alike 4.0 license.