# Enhanced weather station for logging weather data **Author:** Joakim Thor (jt222xr) ## Overview This is a tutorial on how to create a cheap portable WiFi weather station with some added neat features and how to set up a **TIG-stack** on a server for storing, tracking and visualizing weather data. **Time requirement:** 8-10 h ## Project objective This project sprung out of the idea of creating a device that could monitor environmental parameters, such as temperature and humidity, in a room where birds are kept, and being able to access the data remotely. The idea grew into creating a portable weather station that could not only send data to be viewed remotely, but also be read with a quick glance at the device itself, like a regular thermometer. The main objective of this project is to learn how to create such a device, and how one could store and display the data in an efficient way, having no prior knowledge about IoT. Creating this device should give some great insights as to how an IoT device works in practice, and what one could do with the data gathered with it. ## Material **List of materials** *Click on an item to see where it's been bought.* | Item | Description | Cost | | ------------- | ----------- | ----------- | | [ESP32-WROOM-32](https://sizable.se/P.CE9S1/ESP32)| Microcontroller | 94 SEK | | [DHT22](https://sizable.se/P.JN41E/DHT22-AM2302-Temperatur-och-luftfuktighetssensor) | Digital temperature and humidity sensor | 61 SEK| | [BMP280](https://sizable.se/P.73E2B/-BMP280-3.3V-Temperatur-och-trycksensor)* | Digital temperature and air pressure sensor | 34 SEK | | [I2C OLED screen](https://sizable.se/P.9PS2P/0.91-I2C-OLED-Vit)* | 0.91" OLED (128x32 px) | 40 SEK | | [Breadboard](https://sizable.se/P.SFGYT/Kopplingsdack-med-400-punkter) | 400-point breadboard | 34 SEK | | [Miscellaneous component kit](https://sizable.se/P.6UMD3/Komponentkit) | | 74 SEK | | 3 x 1kΩ resistors | Included in miscellaneous components kit | | 3 x LED | Included in miscellaneous components kit | | [Jumper wires (Male-Male)](https://sizable.se/P.K2HUW/Dupont-kopplingskablar-Hane-Hane-40-pin) | | 24 SEK | | [USB Cable (USB-A to Micro-USB)](https://www.kjell.com/se/produkter/mobilt/ladda-koppla/kablar-adaptrar/micro-usb-kablar/linocell-micro-usb-kabel-med-bojskydd-2-m-p97282?gclid=CjwKCAjw_ISWBhBkEiwAdqxb9p4o7JKTNbShZ5UCY_0fLoRNmfoHWEWZWNMoqmbgTbvhjYKOMb3VvhoCkV4QAvD_BwE&gclsrc=aw.ds) | **Data capable** USB cable | 90 SEK | |||**451 SEK**| > **_NOTE:_** Items marked with an asterisk(*) needed soldering, however, these parts can also be ordered pre-soldered for a very small fee. **Optional** I mark this as optional, as I used an old Raspberry Pi as a server for this project. However, one could use one's own computer, a VPS or some other kind of server solution. |Item | Description | Cost | |--|--|--| | [Raspberry Pi 3 Model B+](https://www.dustinhome.se/product/5011072876/3-model-b-14ghz-1gb-ram-wifi-acbt?ssel=true&gclid=CjwKCAjw_ISWBhBkEiwAdqxb9ir5Hob1FYLmA6fWBz41bIzoSF6zyc8E4Gj96a5r3oygrq8MEWXMZxoCzJ0QAvD_BwE) | Server solution | 599 SEK | | [8 GB Micro-SD](https://www.24.se/ljud-bild/minneskort/microsd/microsdhc/microsdhc-8gb?gclid=CjwKCAjw_ISWBhBkEiwAdqxb9umSUbUv_aSs05h8g7z_-o7lNV2aFpjiQMG2DGlVuS_Q0jCI9NLfCBoCaJEQAvD_BwE) | | 39 SEK | Going into this project, I was looking for cheap, but also readily available and supported parts. The ESP32 came out on top, being a widely used microcontroller, with support for MicroPython. The DHT22 is also a widely used sensor, and the library for it comes with the MicroPython firmware by default. The OLED and the BMP280 sensor libraries does not come by default with the MicroPython firmware, but I was able to find great libraries for these on GitHub. More on this later. ## Computer setup This project was developed on a laptop with [Ubuntu 18.04](https://releases.ubuntu.com/18.04/), thus following steps might differ somewhat from other operating systems installation processes. A Raspberry Pi running [Raspbian](https://www.raspbian.org/) was used as a server and mosquitto broker for the TIG-stack. I used [Thonny](https://thonny.org/), a simple, beginner-friendly Python IDE, exclusively for this project, for its ease of use, built-in support for flashing firmwares and having a terminal that communicates directly with the microcontroller. Here follows two lists of software that needs to be installed for setting up the development environment and server: **Development software** - Python3.7 or newer - Esptool - Thonny **Server software** - Mosquitto - Docker (for the TIG-stack) - TIG-stack (Telegraf, Influxdb and Grafana) ### ESP32 microcontroller setup The ESP32 microcontroller must be flashed with a new firmware in order to work with MicroPython. Found [here](https://micropython.org/download/esp32/). **Step 1: Install Esptool** I flashed the firmware using esptool. Installation guide can be found [here](https://docs.espressif.com/projects/esptool/en/latest/esp32/). `$ pip install esptool` Sometimes this command has to be run as root: `$ sudo pip install esptool` **Step 2: Flash firmware** :::danger Before running these commands, one must set ownership of /dev/ttyUSB0/ The simplest way of doing this is by running `$ sudo chown USERNAME /dev/ttyUSB0/` when the device has been plugged in, replacing USERNAME with your own username. **This must be done every time the device is connected to the computer.** ::: Before the ESP32 can be flashed, the old firmware must be erased: `$ esptool.py --port /dev/ttyUSB0 erase_flash` To flash the new firmware, run this command, replacing FIRMWARE.bin with the filename of your downloaded firmware: `$ esptool.py --chip esp32 --port /dev/ttyUSB0 write_flash -z 0x1000 FIRMWARE.bin` **Step 3: Uploading code** Uploading code to the microcontroller is as easy as opening Thonny and saving the code to the microcontroller, after it has been plugged in and the `chown` command mentioned above has been executed. ![](https://i.imgur.com/Ug7DlB0.png) After the code has been uploaded to the microcontroller, simply press F5 or hit the green play button in the upper left corner to run the code. ### Server setup On the server, in my case the Raspberry Pi, the TIG-stack and Mosquitto must be installed. **Step 1: Installing Mosquitto** Mosquitto is available in Raspbians repositories: `$ sudo apt update` `$ sudo apt install mosquitto` Enable Mosquitto: `$ sudo systemctl start mosquitto` **Step 2: Install Docker** Installation guide can be found [here](https://docs.docker.com/engine/install/ubuntu/). Prepare for Docker installation: `$ sudo apt update` `$ sudo apt install ca-certificates curl gnupg lsb-release` Add Docker GPG key: `$ sudo mkdir -p /etc/apt/keyrings` `$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg` Add Docker repository: `$ echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null` Update apt and install Docker: `$ sudo apt update` `$ sudo apt install docker-ce docker-ce-cli containerd.io docker-compose-plugin` `$ sudo apt install docker-compose` **Step 3: Installing the TIG-stack** Install Telegraf, InfluxDB and Grafana using Docker: `$ docker pull grafana/grafana telegraf influxdb` ## Putting everything together ### Circuit diagram :::warning **This circuit diagram is merely an illutration.** The microcontroller in the diagram is an older version with a different pinout, therefore the pins DO NOT match the code in this project. **USE CAUTIOUSLY!** ::: :::warning This wiring and setup is strictly for development purposes. ::: This is how I've wired everything up. Each LED has a 1kΩ resistor between the data pin on the microcontroller and the LED's positive pin, resulting in a lesser, but adequately glowing LED. As both the OLED and the BMP280 use the serial protocol I2C, they've been connected to the same pins, and their respective library take care of the addressing. ![](https://i.imgur.com/v4ZBVdQ.png) As stated above, the pinout does not match exactly with the ESP32 microcontroller, however, the wiring is identical. ### Power consumption :::warning I could not get hold of the LEDs specification. Therefore, I chose not to include them in the power consumption table. They are not used in battery mode. ::: *Click on respective item to be redirected to the specification sheets I've used for making this table.* | Item | Standby | Operating | Deep sleep | | -----| ------- | --------- | ------ | | [ESP32](https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf) | 100 mA | 160-260 mA | 10 μA | | [DHT22](https://www.sparkfun.com/datasheets/Sensors/Temperature/DHT22.pdf) | 40-50 μA | 1-1.5 mA | | [BMP280](https://www.bosch-sensortec.com/products/environmental-sensors/pressure-sensors/bmp280/#technical) | 0.1 μA | 3.4 μA @ 1 Hz | | [OLED screen](https://core-electronics.com.au/monochrome-0-91-128x32-i2c-oled-display-with-chip-pad.html) | 2 mA | 25 mA | **Estimated battery life in battery mode on a 2000 mAh battery:** :::danger This is based on maximum theoretical loads and is to be seen as very rough estimates. This has not been tested. ::: :::info I'm purposely leaving out calculations for the deep sleep period, as the power draw during that period is less than 1% (~0.03%) of the total power consumption, therefore making a negligable difference in battery life. ::: 260 mA + 1.5 mA + 0.034 mA + 25 mA = 286.534 mA total power draw when active and operating. 2 (Ah) / 0.286534 (A) ≈ **7 hours** of continous load. The device activates and draws power for less than a minute before going into deep sleep again for 10 minutes, thus one can roughly multiply the battery life by 10. For a rough estimate, being active for a minute every ten minutes should give a battery life of at least **70 hours**. ## Platform As previously mentioned, I chose the TIG-stack hosted on a Raspberry Pi on my local network as my platform. I chose this because I wanted to learn how to use the TIG-stack, as it seemed like a great streamlined solution to sending, storing and visualizing data coming from the weather station. Besides, it's also free, and open-source! When having learnt how to set it all up, I figured it'd be pretty easy setting it up on a public server later on. The reason for not directly installing it on a public server, or using port forwarding initially, is of course security. I wanted to be sure I knew how to do it properly in a local environment before hosting it publicly. I feel this platform should last me quite a long time, even with several weather stations or other devices, as it's not really a taxing project, per se. ### Mosquitto A message broker that is used for subscribing to, and publishing MQTT messages to, MQTT topics. It lets the weather station send MQTT messages to the server. More can be read [here](https://mosquitto.org/). ### Telegraf Telegraf is a server-based agent for collecting and sending data from IoT sensors to, for instance, a database. It uses the MQTT protocol to receive messages from the weather station. More can be read [here](https://www.influxdata.com/time-series-platform/telegraf/). ### InfluxDB InfluxDB is a NoSQL database built especially for handling large time series data, suitable for sensor data from an IoT-device. More can be read [here](https://www.influxdata.com/products/influxdb-overview/). ### Grafana Grafana is a dashboard software used to visualize and display data from a data source, like InfluxDB. It's highly customizable and lets you present data in a number of ways. More can be read [here](https://grafana.com/). ## The code :::info The code can be viewed in it entirety **[here](https://github.com/FranzThor/weather-station)** ::: For this project, I settled for quite a simple program. I used these libraries: ``` python= import machine import dht import time import ssd1306 from umqttsimple import MQTTClient import network import ubinascii import bmp280 ``` - [This](https://github.com/micropython-Chinese-Community/mpy-lib/tree/master/sensor/bmp280) is the BMP280 library I used. - [This](https://github.com/micropython/micropython/blob/master/drivers/display/ssd1306.py) is the ssd1306 library I used. For connecting to the WiFi and the MQTT broker, I wrote this: ``` python= """ Internet and connectivity """ # WIFI ssid = "WIFI SSID" password = "WIFI PASSWORD" def conn_wifi(): sta = network.WLAN(network.STA_IF) sta.active(True) sta.connect(ssid, password) while sta.isconnected() == False: pass # MQTT mqtt_server = "192.168.68.123" client_id = ubinascii.hexlify(machine.unique_id()) mqtt_user = "USER" mqtt_pwd = "MQTT USER PASSWORD" # MQTT topics topic_pub_temp = b'esp/dht22/temp' topic_pub_hum = b'esp/dht22/hum' topic_pub_press = b'esp/bmp280/press' def conn_mqtt(): """ Connect to selected MQTT broker """ global client_id, mqtt_server, mqtt_user, mqtt_pwd client = MQTTClient(client_id, mqtt_server, user = mqtt_user, password = mqtt_pwd) client.connect() print("Connected to %s MQTT broker' % (mqtt_server)") return client ``` Other than that, there's not really much to it. I used some simple logic in the main loop to light up one of the three LEDs according to current temperature, making it easy for me to get a grasp of the temperature without having to walk up to the weather station: ``` python= if tem < 25 and tem > 15: led_color(green) elif tem > 25 and tem < 30: led_color(yellow) elif tem > 30: led_color(red) ``` I made a function that lights up one LED, turning off the other two LEDs, passing pre-made arrays as argument to the function, for simplicity: ``` python= # LED colors red = [1, 0 ,0] green = [0, 1, 0] yellow = [0, 0, 1] def led_color(color): led_red(color[0]) led_green.value(color[1]) led_yellow(color[2]) ``` As I wanted the weather station to be portable, and possibly battery driven, I made a very basic "battery mode", which ultimately disables the LEDs and the OLED, and making the weather station go into deep sleep for 10 minutes between the sensor readings: ``` python= """ Battery related """ # EXPERIMENTAL: # Set to True when running on battery power # Set to False for when plugged in or when debugging on battery battery = False ``` An example of how it's used can be seen here: ``` python= try: client.publish(topic_pub_temp, str(tem)) client.publish(topic_pub_hum, str(hum)) client.publish(topic_pub_press, str(press)) time.sleep(2) except OSError as e: if battery == False: led_color(red) print("Couldn't publish to MQTT broker!") oled.text("Could not pub", 10, 5) oled.text("MQTT to broker", 10, 20) oled.show() oled.fill(0) reboot() if (battery == True): machine.deepsleep(600000) # 10 minutes deep sleep else: time.sleep(300) ``` **Debugging** I made use of simple error handling throughout the program, letting me use the OLED and LEDs for simple debugging. The OLED will display errors, and a red LED will light up, letting me know whether it's a problem with WiFi or the broker connection, and also if there's been a failed MQTT transmit. An example of this can be seen in the code above. ## Transmitting the data For sending the data from the weather station to InfluxDB, and ultimately to Grafana, I'm using WiFi, sending the data through the MQTT protocol. As I'm only going to use this weather station at my home or summer house, I figured WiFi would be sufficient, as there's always coverage. There is some configuring that needs to be done on the server, the Raspberry Pi, for the TIG-stack to work, letting the weather station send its sensor data to the database. **Mosquitto config** I created a text file called `pwd.txt` in `/etc/mosquitto/`, where I created a user on the form `USER:PASSWORD`: `$ echo USER:PASSWORD | sudo tee pwd.txt` Then I hash the password in the text file: `$ sudo mosquitto -H pwd.txt` In the config file, I'm going to deny anonymous users, giving only myself access to the MQTT broker. In `/etc/mosquitto/conf.d`, I made a file called `custom.conf`: ``` bash= include_dir /etc/mosquitto/conf.d listener 1883 allow_anonymous false password_file /etc/mosquitto/pwd.txt ``` In the local config file, located in `/etc/mosquitto/mosquitto.conf`, this custom config file is imported: ``` bash= # Place your local configuration in /etc/mosquitto/conf.d/ # # A full description of the configuration file is at # /usr/share/doc/mosquitto/examples/mosquitto.conf.example pid_file /run/mosquitto/mosquitto.pid persistence true persistence_location /var/lib/mosquitto/ log_dest file /var/log/mosquitto/mosquitto.log include_dir /etc/mosquitto/conf.d ``` **TIG-stack config** For the TIG-stack, I created a folder called `tig-stack`, serving as my working directory for this project. I put this folder in my home folder. `$ mkdir tig-stack` In this folder, I created two files, `docker-compose.yml` and `telegraf.conf`: `$ sudo touch docker-compose.yml telegraf.conf` In the `docker-compose.yml` file, I wrote this config: :::info {USERNAME}, {PASSWORD} and {BUCKETNAME} are parameters to be altered according to one's own chosen usernames and passwords, and one's own database name. ::: ``` bash= #version: '3.9' networks: tig-net: driver: bridge volumes: tig-data: services: influxdb: image: influxdb:latest container_name: influxdb ports: - 8086:8086 environment: INFLUXDB_HTTP_AUTH_ENABLED: "true" INFLUXDB_DB: "{BUCKETNAME}" INFLUXDB_ADMIN_USER: "{USERNAME}" INFLUXDB_ADMIN_PASSWORD: "{PASSWORD}" networks: - tig-net volumes: - tig-data:/var/lib/influxdb grafana: image: grafana/grafana:latest container_name: grafana ports: - 3000:3000 environment: GF_SECURITY_ADMIN_USER: {USERNAME} GF_SECURITY_ADMIN_PASSWORD: {PASSWORD} networks: - tig-net volumes: - tig-data:/var/lib/grafana restart: always telegraf: image: telegraf:latest depends_on: - "influxdb" environment: HOST_NAME: "telegraf" INFLUXDB_HOST: "influxdb" INFLUXDB_PORT: "8086" DATABASE: "{BUCKETNAME}" volumes: - ./telegraf.conf:/etc/telegraf/telegraf.conf tty: true networks: - tig-net privileged: true ``` In `telegraf.conf` I wrote this: :::info {USERNAME}, {PASSWORD}, {DEVICE IP}, {ORGANIZATION}, {DATABASE} and {TOKEN} are parameters to be altered according to one's own chosen usernames and passwords, IP adress, InfluxDB organization, database name, and one's own generated API-key in InfluxDB. ::: ```bash= [agent] flush_interval = "30s" interval = "30s" [[inputs.mqtt_consumer]] name_override = "RPi" servers = ["tcp://{DEVICE IP}:1883"] qos = 0 connection_timeout = "30s" topics = [ "esp/dht22/temp", "esp/dht22/hum", "esp/bmp280/press" ] username = "{USERNAME}" password = "{PASSWORD}" data_format = "value" data_type = "float" [[outputs.influxdb_v2]] bucket = "{DATABASE}" urls = [ "http://influxdb:8086" ] token = "{TOKEN}" organization = "{ORGANIZATION}" ``` When these configs were created, I created a Docker container for the TIG-stack: `$ sudo docker compose up` When this command is executed and finished, I can navigate to InfluxDB on my server using its IP adress and the port InfluxDB is using: ![](https://i.imgur.com/Xpa0rYU.png) When navigating to this page the first time, you'll be prompted to create an account, which must match with what you put as username and password for InfluxDB in the config files. After doing this, you'll be able to log in with your credentials, and Telegraf will be able to use your credentials to send data to the database. When logging in to InfluxDB for the first time, you'll want to create what's called a bucket: ![](https://i.imgur.com/BZjozP3.png) And get your API-token to put in your telegraf config file: ![](https://i.imgur.com/9lbkaii.png) Once this is done, you have to stop your Docker container to add the API-token to the config file: `$ sudo docker compose stop` Then you start it again: `$ sudo docker compose up` Now, when you power on your weather station, it should be able to send data to InfluxDB. Lastly, you'll want to setup Grafana. You start by typing your server's IP adress in a web browser, along with the port Grafana is using, in this case port 3000, then you'll want to log in using your credentials, and set up a data source: ![](https://i.imgur.com/3brYJxI.png) ![](https://i.imgur.com/vJ69hZd.png) **Transmitting rate** In battery mode, the weather station transmits data to the server every 10 minutes, and in debug and stationary mode, it transmits every five minutes, while keeping the display on, displaying current temperature. This is enough for my use case, and it lets me choose between high accuracy and a higher power draw, and less accuracy with lesser power draw. ## Presenting the data Using Grafana is straight forward, with lots of customizability options. Since I opted to use InfluxDB V2, I had to learn some of their new language Flux for the queries to the database. The documentation is however splendid, so it wasn't really hard or time consuming, especially for my use cases. The reason for choosing InfluxDB V2 instead of V1.x was the GUI, which it lacks in previous versions. In InfluxDB V2, you can see the data in the database in graphs, in addition to usual tables: ![](https://i.imgur.com/KBUDR1p.png) This is what it looks like customizing Grafana: ![](https://i.imgur.com/SifiApQ.png) I keep the data in the database for a week, then it gets deleted. This is however very easy to change in InfluxDB's interface, should one want to keep the data for longer. Here's a couple of pictures of my finished dashboard: ![](https://i.imgur.com/NDT8YCu.png) ![](https://i.imgur.com/eaDplEV.png) ## Finalizing the design ![](https://i.imgur.com/qoG508A.jpg) ![](https://i.imgur.com/G5p0aYQ.jpg) ### Thoughts and conclusion All in all, I'm happy with this project. The weather station does everything I need it to do, and everything just works. Of course, I would have liked to 3D print my own case for the weather station, and perhaps get rid of the breadboard, soldering everything and having it fitted snug inside the case. However, it all fits pretty snuggly on the breadboard already, and with some better cable management, I think it'd look pretty cool as is. In retrospect, I would have liked to use some other connectivity protocol, alongside WiFi, like LTE or LoRa, just for getting some knowledge about it. However, I did get some knowledge from the lectures and workshops. **Good:** - The usage of the screen and LED lights turned out great, letting me debug easily. - The experimental battery mode turned out to work just fine, even though the logic behind it is awful. - The dashboard is just how I wanted it. - Everything works. **Bad:** - The code is somewhat primitive, basic and dull. - I did not pay any extra attention to product design, leaving me with a fragile device. - I should probably have included some deep sleep logic even in stationary mode, even though I don't necessarily care about power consumption in that mode. With that said, this has been a fun project, and a great stumble into the world of IoT!