DEV Community

Cover image for Reading Data from Weather Sensors with Python
Fernando Fornieles
Fernando Fornieles

Posted on

Reading Data from Weather Sensors with Python

Apart from building a weather station, another thing I had been wanting to do for a long time was to learn Python. With this Weather Station project I satisfied both :-)

Check the source code of this project in Codeberg.

Learning the Pimoroni's WeatherHAT library

The first thing I did, after having everything installed, was to learn how the Pimoroni's WeatherHAT library works in order to:

  • Read the weather data from the sensors
  • Display the weather data in the WeatherHAT's screen

So I opened an SSH connection and started playing!

Apart from being my first steps with Python what I learned about the library was:

  • It is necessary to call the "update" method of the sensor before getting the weather readings
  • The first reading is "crap", it is necessary to perform an initial update and wait a few seconds before getting good reading values.
  • There are two attributes for temperature: device_temperature and temperature
  • The temperature attribute is calculated by applying a default offset of 7.5ΒΊC to the device_temperature reading.

Calibrating the Temperature

The first thing I focused on was to properly calibrate the temperature. For this I spent some days gathering temperature data from the sensor and from another nearby weather station at the same time, taking readings every hour.

What I noticed was that the difference between both sensors wasn't a fixed value. The readings from the WeatherHAT's sensor were always between 12ΒΊC and 16ΒΊC higher than those from the nearby station.

So instead of applying a fixed offset I tried a linear regression. I created a class to calculate the offset based on a given device_temperature value.

In the future I could find a better way to calculate the offset. That is why I created a TemperatureOffsetInterface.

class LinearRegressionOffset(TemperatureOffsetInterface):
    def __init__(self, config):
        self.a = 0
        self.b = 0
        self.config = config
        self.doLinearRegression()        

    def getOffset(self, x):
        offset = (self.b * x) + self.a        
        return offset

    def doLinearRegression(self):
        x = self.config['data']['x'].split(",")
        y = self.config['data']['y'].split(",")        

        x = list(map(float, x))
        y = list(map(float, y))       

        medianX = statistics.median(x)
        medianY = statistics.median(y)
        numSum = 0
        denSum = 0

        for index, xValue in enumerate(x):
            numSum = numSum + (xValue - medianX) * (y[index] - medianY)
            denSum = denSum + ((xValue - medianX) * (xValue - medianX))

        self.b = numSum / denSum
        self.a = medianY - (self.b * medianX)

Enter fullscreen mode Exit fullscreen mode

I could have used a Python library to perform the linear regression but, remember, the goal of this project was to learn Python ;-).

Decoupling From The Hardware

Having solved the calibration of the temperature sensor it was time to start coding the reading system.

The process should run periodically, for example every 5 minutes. On each iteration the system should read the data from the sensor, apply the temperature offset, display the data on the screen and send it to an external service.

I work on my own PC, as it is more comfortable than coding directly in the Pi. That is why I created some mocks to simulate access to the WeatherHAT hardware.

The interfaces and implementations to mock the access to the WeatherHAT hardware

This way I could code the main algorithm decoupled from the hardware.

sensor = factory.createSensor()
display = factory.createDisplay()
Enter fullscreen mode Exit fullscreen mode

Once the main algorithm was done I simply had to code the hardware-specific parts. I had to upload each change to the Pi but using the examples provided by Pimoroni it was more or less easy to get the data from the sensor and show it in the WeatherHAT screen.

Sending The Data To An External Service

I wanted to see the current temperature from anywhere and also store the readings in order to generate statistic from the weather data.

So the next step was to send the data to an external service. As I hadn't yet implemented my Weather Data Webapp, I started sending the data to Adafruit. I created a dashboard to see current data and also a graph to easily compare the temperature readings with the nearby station. This way I could check the quality of the linear regression.

I created an ConnectorInterface to first create the Adafruit connector and later the implementation to send the weather data to my Weather Data Webapp.

class ConnectorInterface:
    def send(self, WeatherData):
        pass
Enter fullscreen mode Exit fullscreen mode

I also created a MockConnector to avoid sending bad data while working on the main flow of the algorithm.

Implementing An Abstract Factory

Everything was working fine, but changing which implementations to use while testing the system locally (mock) and in "production" (on the Pi) was a bit messy.

To simplify the mode selection I applied the Abstract Factory Pattern. This way, with a single configuration setting, I could load the right set of components (Sensor, Display and Connector).

class SystemFactoryInterface:

    def createDisplay(self) -> DisplayInterface:
        pass

    def createConnector(self, config) -> ConnectorInterface:
        pass

    def createSensor(self, config) -> SensorInterface:
        pass     
Enter fullscreen mode Exit fullscreen mode

Conclusion

I enjoyed this part a lot. Learn Python, read the data from the sensor, calibrate the temperature, apply some design patterns, ... It brought back the feeling I had when I first started learning to program.

Maybe because it is a project that is really different to the ones I'm more used to develop, maybe because it mixes different things or maybe both :-).

In the next article I'll try to explain the implementation of the Weather Data Webapp. This time with PHP and Symfony, a language and a framework that I'm more used to work with.

Top comments (2)

Collapse
 
annavi11arrea1 profile image
Anna Villarreal

πŸ¦„

I like how you have segmented this all into parts.

Collapse
 
nandofm profile image
Fernando Fornieles

Thanks Anna! Actually, divide & conquer always works! πŸ˜ƒ