Introduction: NaTaLia Weather Station: Arduino Solar Powered Weather Station Done the Right Way

About: Never stops thinking.

After 1 year of successful operation on 2 different locations I am sharing my solar powered weather station project plans and explaining how did it evolve into a system which can really survive over long time periods from solar power. If you follow my instructions and using the exact same materials as listed, you can build a solar powered weather station which will run for many years. Actually the only factor limiting how long it will run is the lifespan of the battery what you are using.

This project has been superseded by my new Cyclone Weather Station. I strongly recommend building on that as it using the latest sensors and can collect much more telemetry.

Step 1: Weather Station Operation

1, Transmitter: Outdoor mounted box with solar panel which sends weather telemetry (Temperature, Humidity, Heat Index, Solar Strenght) periodically to the indoor receiver unit.

2, Receiver: Indoor unit made from a Raspberry PI 2 + Arduino Mega having a 433 Mhz RF Receiver connected for data reception. In my setup this unit does not have any local LCD display functionality. It runs heedlessly. A main C program takes care of receiving the incoming data from the Arduino through the serial, then logging the data into a text file and making the last received data available through telnet for other devices to query it.

The station is controlling lights in my home by the reading of photoresistor( which determines whether it is day or night outside). The receiver is headless in my case but you can easily modify the project to add an LCD display. One of the device using, parsing and displaying the weather data from the station is my other project: Ironforge the NetBSD Toaster.

Step 2: First Versions

There are a lot of solar projects on the net but many of them commits the common mistake that the system takes out more energy from the battery over time what the solar panel could replenish, especially during the cloudy, dark winter months.

When you design a solar powered system the only thing matters is POWER CONSUMPTION, on all components: mcu, radio transmitter, voltage regulator etc.

Using a large computer like a raspberry pi or power hungry wifi device like the ESP just to collect and transport couple of bits weather data would be an overkill but as I will show it in this tutorial even a small Arduino board is.

The best is always measuring current during your build process with a meter or with a scope (useful when you try to measure small spikes in usage during the operation in very short timespans (milliseconds)).

On the first picture you can see my first (Arduino Nano Based) station and the second Arduino Barebone Atmega 328P board.

The first version, although it worked perfectly (monitoring environment and sending data via radio) had too high power consumption ~ 46mA and drained down the battery in a few weeks.

All the versions were using the following battery:

18650 6000mAh Protected Li-ion Rechargeable Battery Built-in Protection Board

UPDATE on these ScamFire batteries. Although this is a fairly old Instructable I still felt compelled to correct it due to this bogus battery. Do NOT buy the mentioned battery, do your own research about other LION/LIPO batteries, all 3.7V batteries will work with this project.

Finally I had time to debunk the ScamFire battery to see what it's real capacity is. Therefore we will run 2 calculations side by side with the real and the "advertised" capacities.

First of all that's one thing that this battery is fake and nothing what they claim about it is true, the new versions are even worse they copied the fake with leaving out the 2 cent protection circuit so nothing will stop them discharging down to zero.

A small article on LION/LIPO batteries:


What this means is that the maximum voltage of the cell is 4.2v and that the "nominal" (average) voltage is 3.7V.

For example, here is a profile of the voltage for a 'classic' 3.7V/4.2V battery.
The voltage starts at 4.2 maximum and quickly drops down to about 3.7V for the majority of the battery life. Once you hit 3.4V the battery is dead and at 3.0V the cutoff circuitry disconnects the battery.

My measurements using a dummy load:

Battery charged: 4.1V

Cutoff set to: 3.4V

Load simulation: 0.15A (my device had a bit of a problem with going lower than this.)

Measured capacity: 0.77Ah give it a gratuitous 0.8 Ah which is 800mAh instead of the advertised 6000mAh!

Since this battery did not even have the protection circuit I could freely go lower but at 3.4V after 10 minutes it already crashes down to 3.0V.

Therefore with simple calculations the battery is providing:


Battery voltage =3.7V

Power =3.7x6000= 22000 mWh


Battery voltage =3.7V
Power =3.7x800= 2960 mWh


Even with the LowPower library an Arduino nano consumes ~ 16 mA (in sleep mode) -> FAIL.


Pavg=VxIavg =5Vx16mA= 80 mW

Battery life = 22000/80 =275 hours = 11 days approximately

Pavg=VxIavg =5Vx16mA= 80 mW

Battery life = 800/80 =10 hours

Version: 0.2 Atmega 328P Barebone

The power consumed by an ATmega328 depends a lot on what you are doing with it. Just sitting there in a default state, it can use 16mA @ 5V while running at 16MHz.

When the ATmega328P is in Active Mode, it will continuously execute several million instructions per second. Further, the On-Board Peripherals Analog to Digital Converter (ADC), Serial Peripheral Interface (SPI), Timer 0,1,2, Two Wire Interface (I2C), USART, Watchdog Timer (WDT), and the Brown-out Detection (BOD) consume power.

To save power, the ATmega328P MCU supports a number of sleep modes and unused peripherals can be turned off. The sleep modes differ in what parts remain active, by the sleep duration and the time needed to wake-up (wake-up period). The sleep mode and active peripherals can be controlled with the AVR sleep and power libraries or, more concisely, with the excellent Low-Power library.

The Low-Power library is simple to use but very powerful. The statement LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF); puts the MCU in SLEEP_MODE_PWR_DOWN for 16 ms to 8 s, depending on the first argument. It disables the ADC and the BOD. Power-down sleep means that all chip functions are disabled till the next interrupt. Further, the external oscillator is stopped. Only level interrupts on INT1 and INT2, pin change interrupts, TWI/I2C address match, or the WDT, if enabled, can wake the MCU up. So with the single statement, you will minimize energy consumption. For a 3.3 V Pro Mini without power LED and without regulator (see below) that is running the statement, the energy consumption is 4.5 μA. That is very close to what is mentioned in the ATmega328P datasheet for power-down sleep with WDT enabled of 4.2 μA (datasheet linked in sources). Therefore, I am quite confident, that the powerDown function shuts down everything that is reasonably possible. With the statement LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);, the WDT will be disabled and you would not wake up until an interrupt is triggered.

So with the barebone setup we can put the chip into sleep mode for 5 minutes, while it is consuming very little amount of energy (0.04 mA without peripherals). However this is only the Atmega 328P chip with the crystal oscillator and nothing else, the voltage booster used in this configuration to boost the battery voltage from 3.7V -> 5.0 V also consumes 0.01 mA.

One constant voltage drain was the added photo resistor bumping up the consumption in sleep mode to an overall 1 mA (this includes all the components).

The formula for calculating the precise consumption for the device in both sleep and wakeup mode is:

Iavg = (Ton*Ion + Tsleep*Isleep ) / (Ton +Tsleep)

Ion = 13mA

This is mostly coming from the RF433 Mhz transmitter:

Transmitter :

Working voltage: 3V - 12V fo max. power use 12V
Working current: max Less than 40mA max , and min 9mA
Resonance mode: (SAW)
Modulation mode: ASK
Working frequency: Eve 315MHz Or 433MHz
Transmission power: 25mW (315MHz at 12V)
Frequency error: +150kHz (max)
Velocity : less than 10Kbps

Isleep = 1mA

Would be significantly less without the photoresistor.

Trunon time Ton=250 mS = 0.25s

Sleep time Tsleep= 5 min = 300s

Iavg = (Ton*Ion + Tsleep*Isleep ) / (Ton +Tsleep)

Iavg = (0.25s*13mA + 300s*1mA ) / (0.25s+300s)


Pavg=VxIavg =5Vx1.26mA=6 mW


Battery life = 22000mWh/6mW = 3666 hours = 152 days approximately


Battery life = 800mWh/6mW = 133 hours = 5.5 days approximately

Although these were still a better UltraFire series what I used initially you could see that without the solar panel or the low 1mA consumption this project would not survive long.

Feel free to build the station and write down your findings and calculations to the comments and I will update the article. I would also appreciate results with different MCUs and boost converters.

Step 3: Building a Successful Weather Station

Although it is the first successful version, it contains a little bit of fail on the pictures and I can’t remake those because the stations are already deployed. The two voltage boosters shown on the picture are obtainable at the time of writing for aero-modelling and other applications. When I redesigned my station I was thinking on getting a smaller and more efficient voltage stepup board, however smaller in size definitely does not mean that it is more efficient.

The new small module on the picture which does not even have an indicator led actually drained 3mA (*FAIL*) by itself, so I stayed with my old board:

PFM Control DC-DC USB 0.9V-5V to 5V dc Boost Step-up Power Supply Module

At the time of writing this module is still available on Ebay for 99 cent but if you decide to use another booster, always check the standby power consumption. With a good quality booster it should not be more than mine (0.01 mA), although the small LED on board had to be de-soldered.

Step 4: Hardware List

  • 18650 6000mAh Protected Li-ion Rechargeable Battery Built-in Protection Board
  • Atmega 328P16M 5V with bootloader
  • Adafruit DC Boarduino (Arduino compatible) Kit (w/ATmega328) < this is going to be a good investment if you are doing future barebone projects
  • Photo Light Sensitive Resistor Photoresistor Optoresistor 5mm GL5539
  • 1A 1000V Diode 1N4007 IN4007 DO-41 Rectifier Diodes
  • PFM Control DC-DC USB 0.9V-5V to 5V dc Boost Step-up Power Supply Module
  • 1.6W 5.5V 266mA Mini Solar Panel Module System Epoxy Cell Charger DIY
  • TP405 5V Mini USB 1A Lithium Battery Charging Board Charger Module
  • 433Mhz RF transmitter and receiver link kit for Arduino/ARM/MC remote control < Kit, contains both the transmitter and reveiver
  • IP65 Switch Protector Junction Box Outdoor Waterproof Enclosure 150x110x70mm
  • New DHT22 Temperature and Relative Humidity Sensor Module for Arduino
  • 1x220 Ohm, 2x10KOhm,1xLED,1xMini Switch,1x1N4007diode
  • Adafruit 16 MHz Ceramic Resonator / Oscillator [ADA1873]
  • Arduino UNO/Mega etc for receiver station + Raspberry PI 1/2/3
  • Clear Acrylic Plastic Box (optional)

You can find all of these on Ebay, I do not want to promote any sellers by linking to their pages and the links will become dead in the future anyway.

Notes for the hardware list:

Just in case you brick the Atmega somehow with programming buy more of them, same goes for the voltage booster and solar charge controller.

The solar charger contains 2 little color LEDs which are only turned on in case of solar charging and indicate (red-> charging, blue-> fully charged states). These can be unsoldered as well. It rather gives a little more extra juice to the battery during charging.

As you see there are no battery holders on my list. Why? Because they are unreliable. I had countless of occasions when the battery moved out from it’s holder and lost connection. Especially if your setup is mounted up on a high dish pole like mine, open for any harsh weather conditions. I even zipped the battery into the holder with 2 zippers and it still managed to move out. Don’t do it, just remove the external coating from the battery and solder the wires directly into the bottom of the battery, containing the overcharge protection circuit (do not bypass the protection). A battery holder can be used for only holding the battery in place in the device.

TP405 5V Mini USB 1A Lithium Battery Charging Board: unfortunately this board does not include reverse current protection to the solar panel, for this you will need 1 more diode to be placed between one leg of the solar panel and charging circuit to stop the current trying to flow back into the solar panel at nights.

Step 5: Assembly

This board contains relatively few components and the markers on the board are fairly simple.

Make sure that you DO NOT insert the Atmega328P on the wrong way (that can heat up and brick the chip, might destroys the voltage booster too).

In this setup the chip faces down (little U hole marking PIN1). All the other components should be obvious.

Use shielded cable (eg.: Audio Cable from CDrom will do fine) for the LDR. In some cases (over many weeks of test) it turned out that it is interfering with the radio signal transmission. This was one of those bugs difficult to troubleshoot so if you don’t want trouble just use a shielded cable, end of story.

LED: The LED on the bottom of the box was originally added to blink when there is outgoing radio transmission but later on I have considered it as waste of power and it only blinks 3 times at the bootup process.

TP: is test point for measuring the current for the overall circuit.

DHT22: Don’t buy the cheap DHT11, spend 50 cents more to get the white DHT22 which can measure negative temperatures as well.

Step 6: Case Design

Although it’s a bit of an overkill, a 3D printed cube (weather_cube) was made to hold the DHT22 temperature sensor in place. The cube is glued to the bottom of the IP box, featuring only 1 hole for the air to reach the sensor. I have added a net at the hole against bees, wasps and other small flies.

An external box can be used optionally to make the station more waterproof in case you are mounting it on a dish pole on the open.

Idea for 1 useful feature: adding a big metal roof plate 1-2cms on the top of the box providing shadow from the sun during the summer, although this could also take away our useful sunlight from the panel. You can come up with a design which separates the panel and the box (leaving the panel on the sun, the box in shadow).

On the pictures: one of the stations removed from working environment after 1 year, battery voltage is on stunning 3.9V still, no water damage to any part of the box although the net I glued at the bottom of the cube was torn apart. The reason the station needed to be serviced is connection fault on the LDR connector, although the jumper cable seemed to be still in place, the connection was broken therefore the pin was sometimes floating providing bad LDR analog readouts. Suggestion: if you use standard PC jumpercables, hotglue them all after the station is working perfectly to avoid this.

Step 7: Software

The software code will require 3 external libraries (LowPower, DHT, VirtualWire). I had problem finding some of them easily online lately so I attaching them in a separate ZIP file. Regardless what OS are you using Linux/Windows, just find your Arduino IDE’s library folder and extract them there.

Just a note, regardless that I already advise against buying the DHT11, if you use the wrong type of DHT sensor the program will just hang forever at the beginning at the initialization section (you will not even see the startup led blink 3 times).

The main loop code is very simple, first it reads the environment values (temperature, heat index, humidity, solar), sends them through radio then it uses the lowpower library to put the Arduino in sleep for 5 minutes.

I have found that lowering the baudrate will increase the stability of the radio transmissions. The station is sending a very small amount of data, 300 bps is more than enough. Also don’t forget that the transmitter is only operating from approx. 4.8V, in the future 3.3V version this might leads to even worse transmission quality (sending data through walls and other obstacles). I run into an issue with using an Arduino Mega attached to a Raspberry PI 2 powering the Mega from the PI, that I did not receive any transmission. Solution was to power the Mega from a separate external 12V supply.

Step 8: Version 2 (ESP32 Based)

Everything which can break will break to quote good old Murphy and eventually after years the stations failed on mysterious ways. One started sending gibberish solar data which went up to tens of thousands, which is impossible due to: The Arduino board contains a 6 channel (8 channels on the Mini and Nano, 16 on the Mega), 10-bit analog to digital converter. This means that it will map input voltages between 0 and 5 volts into integer values between 0 and 1023. So after replacing the radio, LDR and reprogramming the Atmega 328P multiple times I gave up and decided it's time for innovation. Let's go ESP32.

The board what I used was a: ESP32 WEMOS LOLIN32 Lite V1.0.0 Wifi & Bluetooth Card Rev1 MicroPython 4MB FLASH

Microcontroller    ESP-32
Operating Voltage 3.3V
Digital I/O Pins 19
Analog Input Pins 6
Clock Speed(Max) 240Mhz
Flash 4M bytes
Length 5mm
Width 2.54mm
Weight 4g

Which unlike the pictured one does not have the LOLIN logo (counterfeit from China). My first pleasant surprise was that the pinout printed on the board was matching with the Arduino pinout! After dealing with so many noname boards where I had to look for pinouts all day long dead tired making mistakes finally a board where the pinout is straight forward WoW!

However here is the dark side of the story:

Initially I have connected the LDR to A15 which is pin 12 because it was easier hotgluing the pins together. Then I've got 4095 readouts (which is the max you can get with AnlogRead on the ESP32) which drove me nuts because the whole reason why I rebuilt the station was the broken LDR readouts from the old one (the DHT was still functioning fine). So it turns out that:

The esp 32 integrates two 12-bit ACD registers. ADC1 whit 8 channels attached to GPIOs 32-39 ande ADC2 whit 10 channels in another pins. The thing is the ESP32 uses the ADC2 to manage wifi functions, so if you use Wifi, you can´t use that register.
The ADC driver API supports ADC1 (8 channels, attached to GPIOs 32 - 39), and ADC2 (10 channels, attached to GPIOs 0, 2, 4, 12 - 15 and 25 - 27). However, the usage of ADC2 has some restrictions for the application:

ADC2 is used by the Wi-Fi driver. Therefore the application can only use ADC2 when the Wi-Fi driver has not started. Some of the ADC2 pins are used as strapping pins (GPIO 0, 2, 15) thus cannot be used freely. Such is the case in the following official Development Kits:

So connecting the LDR from pin 12 to A0 which is the VP resolved everything but I don't get it why are they even listing ADC2 pins as available for makers. How many other hobbist wasted tons of time until figuring this out? At least mark the unusable pins with red or something or don't mention it in the manual at all so other makers can only find out about them if they really need them. The whole purpose of the ESP32 is to use it with WIFI, everyone uses it with WIFI.

A good start how to setup the Arduino IDE for this board:

Although I put it in the code here it goes once again:

This code might not compile for other ESP32 models than the Weemos LOLIN 32 !

Build settings:
-Use upload/serial: 115200
-Use CPU/ram: 240Mhz(Wifi|BT)
-Use flash freq: 80 Mhz

There are tons of ESP32 based weather stations on the net, they are way more commons than my version 1 was with the barebone chip because they are easier to setup, you don't need programmer just plugin the device on usb and program it and their deep sleep mode is excellent for long time running from battery. Right off the bat this was the very first thing I tested even before soldering in the breakout pins because as I noted multiple places in this project the MOST important thing is the power consumption and with the current (fake) battery and small solar panel the standby power cannot really go over 1-2mAs otherwise the project will not be able to sustain itself over the long term.

It was a pleasant surprise again that the deep sleep mode works as advertised. During the deep sleep the current was so low that my cheap multi meter couldn't even measure it (works for me).

During sending data the current was around 80mA (which is about 5 times more than when the Atmega 328P was waking up and transmitting), however don't forget that with the V1 there was an avg 1mA power drain on the LDR in sleep mode (which also depended on the light levels and went from 0.5mA - 1mA) that is now gone.

Now that the UltraFire battery is debunked if you use the same battery here is what you can expect:

Iavg = (Ton*Ion + Tsleep*Isleep) / (Ton +Tsleep)

Iavg = (2s*80mA + 300s*0.01mA) / (2s+300s) Iavg = 0.5mA

Pavg=VxIavg =5Vx0.5mA=2.5 mW


Battery life = 22000mWh/2.5mW = 8800 hours = 366 days approximately


Battery life = 800mWh/2.5mW = 320 hours = 13 days approximately

I did not have a scope to measure precisely the turn on time, but with my tweaks it tops off around 2 seconds.

I didn't want to spend the afternoon on custom coding everything so I looked for some other weather stations on Instructables based on ESP32 to see what they do for data storage. Sadly noticed that they are using inflexible and limited sites such as weathercloud. As I'm not a fan of the "cloud" and their code long broke because the site have changed it's API since then, I have took my 10 minutes to make a custom solution because it is not as hard as one might thinks. Let's get started!

First of all there is no circuit board pic separately for this project, because it uses the exact same components (sorry for that soldered in ugly breadboard picture) as the V1 with the difference that everything runs off 3.3V. The DHT hooked up with a pullup to VCC, the LDR pulled down with a 10k. The problem one might see with the 18650 batteries like my Chinese fake (6500 mAh ultra sun fire lol :D) is that they start the discharge curve off from around 4.1V new age and go until their cutoff circuitry kicks in to stop cell damage (those which are lucky enough to have it). This is nowhere good for us as 3.3V input. Although this LOLIN board has a lithium battery connector and charging circuit in this project I wanted to refurbish most what I could from the old station so with the old 18650 you CANNOT use this built in charger. The solution was dead simple: I cut off a micro USB cable soldered into the 5V out of the old voltage booster and voila problem solved, since the board on the microUSB has regulator.

So the difference between the old and new version that in the old battery provides 3.7V -> boosted to 5V -> ardu runs on 5V -> all components run on 5V.

In the new one: battery provides 3.7V -> boosted to 5V -> regulated through onboard reg on ESP32 -> all the components run on 3.3V.

Software wise we will need another DHT library as well, the Arduino's DHT is not compatible with the ESP's. What we need is called DHT ESP.

I started basing my code around the DHT example this code provided. The working of the code is:

1, Get the environmental data from the DHT + Solar data from the photocell

2, Connect to wifi with static IP

3, POST the data to a php script

4, Go to sleep for 10 minutes

As you will notice I tuned the code for efficiency to absolutely minimize the wake up time since it is draining 5 times of the power than the old project did when it is turned on. How did I do this? First of all if there is ANY kind of error the getTemperature() function will return with false (which means 10 mins sleep again). This can be like the DHT sensor cannot be initiated or the wifi connection is not available. As you notice the usual while() loop for keep trying the wifi association forever was also removed but a 1 sec delay needed to be left in there otherwise it will not always connect and it's also depends on the AP type, load etc how fast it will happen, with 0.5s I got inconsistent behaviour (sometimes it could not connect). If anyone knows a better way for doing this please leave it in the comments. Only when the DHT data is read AND the wifi connection is up will it try to post the data to the script on the webserver. All kind of time waster functions like Serial.println()'s are disabled in normal operational mode as well. As server I also using IP to avoid an unnecessary DNS lookup, in my code both the default gateway and dns server set to

I don't understand why is it so hard to create your own API when all it takes is:

  sprintf(response, "temp=%d&hum=%d&hi=%d&sol=%d", temp, hum, hi, sol);
int httpResponseCode = http.POST(response);

You put this small php code to any raspberry pi and you can do system() tasks right away based on the telemetry like turn on fans or turn on the lights if it gets dark enough.

Some notes about the code:

  WiFi.config(staticIP, gateway, subnet, dns);  // MUST be after Wifi begin how dumb...
WiFi.mode(WIFI_STA); // MUST otherwise it will also create an unwanted AP

Yeah well now you know. Also the order of the IP configs can change through platforms, I tried other examples first where the gateway and subnet values were switched. Why to set static IP? Well it's quite obvious, if you have a dedicated box on your network like a linux server running isc dhcpd, you don't want a hundred million log entries from when the ESP wakes up and get the IP from the DHCP. Routers normally don't log associations so that will go unseen. This is the price of saving power.

V2 was never been able to sustain itself due to the bad quality battery and I have simply put it on an adapter so if you want to build either the V1 or V2 do NOT buy the mentioned battery, do your own research on batteries (any 18650 over 2000mAh advertised capacity on Ebay is a scam with a high probability).