---
# System prepended metadata

title: A basic IoT weather station

---

# A basic IoT weather station
**By:** Theodor Björk (tb222yb)
**Operating System:** MacOS Mojave 10.14.6

The goal of this project is to show how to set up a home measurement system which can measure temperature and humidity in a room, much like a weather station. By following this tutorial, you could  in approximately 3-4 hours, create your own device to measure temperature and humidity at home :-)

---

**Objective**
The reason behind this project is to be able to create my own IoT weather station that I can use. Also to aquire a basic understanding in... 
* ... how to create an IoT-device.
* ... how to connect sensors which the device can communicate with.
* ... how to present the data that is obtained from the sensors on a platform.

---
**Material**
The materials used in this project were:

| Material            | From       | Price |
| ------------------- | ---------- | ----- |
| Lopy4               | [Pycom](https://pycom.io/product/lopy4/)       | €38.45|
| Expansion Board 3.0 | [Pycom](https://pycom.io/product/expansion-board-3-0/)      | €17.60| 
| Breadboard *(Optional)*         | [Electrokit](https://www.electrokit.com/en/product/solderless-breadboard-400-tie-points/) | 59 SEK|
| Wires (If using breadboard)              | [Electrokit](https://www.electrokit.com/en/product/jumper-wires-1-pin-male-male-150mm-10-pack/) | 29 SEK   |
|Wires (If not using a breadboard)|[Electrokit](https://www.electrokit.com/en/product/jumper-wires-20-pin-30cm-female-male/)|29 SEK|
| DHT11 sensor        | [Electrokit](https://www.electrokit.com/en/product/digital-temperature-and-humidity-sensor-dht11/) | 49 SEK|
| Micro USB Cable     | [Electrokit](https://www.electrokit.com/en/product/usb-cable-a-male-microb-male-1-8m/) | 39 SEK|
| **Total:**    |       |**754 SEK**|

I did, however, buy the **Lopy4 and sensors bundle** (949 SEK) as well as the **Sensor kit - 25 Modules** (299 SEK) from Electrokit. This was due to not really knowing what I wanted to do as a project in the beginning. It is a much more economical advantage to try to figure out what you would like to make as a project BEFORE buying anything. 

**Lopy4**
"The LoPy4 is Micropython-programmable quadruple bearer board. It works with LoRa, Sigfox, WiFi and Bluetooth, making it an exceptional enterprise-grade IoT platform." -- PyCom.
![Lopy4](https://i.imgur.com/6YCZ3Ka.png) 

**Expansion Board 3.0**
The Expansion Board makes it possible to connect your Lopy4 device with an USB to your computer. The expansion board is the intermediator between the computer, the Lopy4 and potential sensors.
![Expansion Board 3.0](https://i.imgur.com/sxUeQPL.png)

**Breadboard and wires**
The breadboard, which is optional in this project, can be used to make the wiring of your project look more clean. The wiring itself is used to connect your sensors and your device, enabling communication between them.
![Breadboard and Wires](https://i.imgur.com/WM2mnix.jpg)


**DHT11 Sensor**
A Digital Humidity and Temperature sensor. This sensor has a humidity range between 20% to 90% Relative Humidity (RH). The accuracy is ±5% RH. The temperature range for this sensor is between 0ºC to 50ºC [32ºF to 122ºF] with an accuracy of ±2ºC. You can read more about this sensor in the following [Datasheet](https://www.electrokit.com/uploads/productfile/41015/41015728_-_Digital_Temperature_Humidity_Sensor.pdf).
![DHT11](https://i.imgur.com/LTIsZGT.jpg)

**Micro USB Cable**
A micro USB cable to connect my PyCom Lopy4 device with my computer. The USB-A goes to the computer, while the Micro USB-B cable goes to the PyCom device.
![USB Cable](https://i.imgur.com/CFQBn1E.jpg)


---
**Computer setup**
I am using a MacBook Pro (2019) for my project. The MacBook Pro I am using does not have any USB-ports. Instead, it is equipped with two Thunderbolt 3 ports. Thus, in order to connect my MacBook Pro with the PyCom device, I needed an USB-C adapter that was equipped with an USB-A port. I had [this](https://www.inet.se/produkt/8906774/satechi-usb-c-dockningsstation-slim-6-portar-60-w-rymdgra?gclid=Cj0KCQjw3f6HBhDHARIsAD_i3D_h4PbRDZdnk---XpFTKPC8cNS-k6IY0A54WlpBcES9ktwTpEPQ-CsaAkRREALw_wcB&gclsrc=aw.ds) in my drawer. 

When choosing an IDE for my project, I was in the choice of either Visual Code Studio or Atom. For my project I decided to use Atom as the IDE since I have used Visual Code Studio before, and I wanted to try Atom which I had never heard of. Atom also seemed to be the right choice to use due to the PyMakr add-on.

If you would like to use Atom yourself, you can download it from [here.](https://atom.io) The PyMakr add-on allows for usage of the programming language MicroPython, which is an excellent choice when working with microcontrollers, such as Lopy4. The PyMakr plugin were installed within the Atom software, as seen in the picture below. If you wish to install PyMakr, open up Atom on your Mac and hold command (⌘) and comma (,) at the same time. You'll then get to the **Settings** menu in Atom. From there you click **Packages** and then search for **Pymakr** and click install.
![PyMakr installation](https://i.imgur.com/EqkAzVp.png)
*Installation of PyMakr on Atom*

If you have a new expansion board, you don't need to update the firmware. If you've got an old one however, you probably should. To do so, you can follow this [guide](https://docs.pycom.io/updatefirmware/expansionboard/). It is also strongly recommended to update your device firmware to the latest version. Here's a [guide](https://docs.pycom.io/updatefirmware/device/) for that as well. Once you've updated the firmware of your devices, you are good to go and start connecting to Pybytes! But first, you should make sure you have setup your device to read values from a sensor (or more if you wish).

---
**Putting everything together** 
When I connected my Pycom device with the DHT11 sensor, I used a breadboard to give the wiring a more neat look. I've connected my Pycom device like in picture 1 below. The far most left pin on the DHT11 sensor is the Signal pin. On the DHT11 sensor you can also see that there's a *S* on the lower left end of the sensor. The signal pin should be connected to a specific pin (P), in this case P23. The middle pin is the power line, which should connect to the 3V3 on the Expansion board. The far most right pin (*-*), is the ground pin, and should be connected the GND on the expansion board.

The image (Picure 1) depictures the DHT11 sensor connected to a breadboard and a Pycom device with wires connected to the breadboard as well. You could just use jumper wires connected to the sensor directly, like in picture 2
![Picture 1](https://i.imgur.com/uHYOAOS.jpg)
*Picture 1 - DHT11 on a breadboard*

![Picture 2](https://i.imgur.com/n71YCU8.jpg)
*Picture 2 - DHT11 without a breadboard*

A mistake I made early on was that I placed my Lopy4-microcontroller on the wrong side of the expansion board. The first picture shows an invalid placement of the Lopy4 ontop of the expansion board, while the correct placement can be seen in the second picture. Make sure that text *Pycom* on the Expansion board is faced in the same way as *Pycom* on the Lopy4.

![Invalid placement](https://i.imgur.com/v81PziS.jpg)
*Picture 1 - Invalid placement*

![Correct placement](https://i.imgur.com/rfgjV2s.jpg)

*Picture 2 - Correct placement*

---
**Platform**
The platform I chose were Pybytes. Pybytes is Pycom's device management platform which makes it possible for you to control all of your Pycom devices. Pybytes allows you to visualise your gathered sensor data on customisable dashboards, distribute firmware updates over the air and track your devices' locations through Google Maps APIs. The reason I chose Pybytes as my platform for this project is that it fulfilled all that I needed, which were to visualise gathered sensor values and control my device. And it was free :-) 

If you have other needs than me, you could choose other platforms that provide more features, such as [Datacake](https://datacake.co/) or [Ubidots](https://ubidots.com/), amongst others. 
Pybytes also offers a good tutorial for beginners at their website, which I myself followed when starting my project. Here's the link if you're interested: https://docs.pycom.io/pybytes/gettingstarted/ 

---
**The Code** 

The code is not made by myself. Both the Main.py and the DHT.py codes below are gathered from this [Github](https://github.com/JurassicPork/DHT_PyCom?fbclid=IwAR3d9yBt3wKGlWVsrLcW07yEDaR3brhzt-pwvWX5PnA20yXAO8hHutj69Vw). I've changed the Pin number to fit my solution which is Pin 23 (P23 in main.py). If you want to make use of this code yourself, make sure you change the Pin-number to the one you're going to connect your sensor to.

I've also added the function *pybytes.send_signal(signal_number, value)* to send my data to Pybytes. The *signal_number* defines which channel you will use for sending your message. The *value* represents what you want to send, for example a measured value from a sensor. 

Main.py
```
import pycom
import time
from machine import Pin
from dht import DTH

th = DTH(Pin('P23', mode=Pin.OPEN_DRAIN), 0) 
time.sleep(2)

while True:  # A continuous loop which never ends.
    result = th.read() # The variable result is reading the value from the DHT sensor.
    while not result.is_valid():
        time.sleep(.5)
        result = th.read()
    print('Temp: ', result.temperature, ' C') # Prints the temperature in the REPL-interface.
    print('RH: ', result.humidity, ' %') # Prints the humidity in the REPL-interface.
    pybytes.send_signal(1,result.temperature) # Sends the temperature to Pybytes.
    pybytes.send_signal(2,result.humidity) # Sends the humidity to Pybytes.

    time.sleep(60) # Repeat every 60 seconds.

```
DHT.py
```
import time
from machine import enable_irq, disable_irq,  Pin


class DTHResult:
    'DHT sensor result returned by DHT.read() method'

    ERR_NO_ERROR = 0
    ERR_MISSING_DATA = 1
    ERR_CRC = 2

    error_code = ERR_NO_ERROR
    temperature = -1
    humidity = -1

    def __init__(self, error_code, temperature, humidity):
        self.error_code = error_code
        self.temperature = temperature
        self.humidity = humidity

    def is_valid(self):
        return self.error_code == DTHResult.ERR_NO_ERROR


class DTH:
    'DHT sensor (dht11, dht21,dht22) reader class for Pycom'

    #__pin = Pin('P3', mode=Pin.OPEN_DRAIN)
    __dhttype = 0

    def __init__(self, pin, sensor=0):
        self.__pin = pin
        self.__dhttype = sensor
        self.__pin(1)
        time.sleep(1.0)


    def read(self):
        #time.sleep(1)

        # send initial high
        #self.__send_and_sleep(1, 0.025)

        # pull down to low
        self.__send_and_sleep(0, 0.019)

        # collect data into an array
        data = self.__collect_input()
        #print(data)
        # parse lengths of all data pull up periods
        pull_up_lengths = self.__parse_data_pull_up_lengths(data)
        # if bit count mismatch, return error (4 byte data + 1 byte checksum)
        #print(pull_up_lengths)
        #print(len(pull_up_lengths))
        if len(pull_up_lengths) != 40:
            return DTHResult(DTHResult.ERR_MISSING_DATA, 0, 0)

        # calculate bits from lengths of the pull up periods
        bits = self.__calculate_bits(pull_up_lengths)

        # we have the bits, calculate bytes
        the_bytes = self.__bits_to_bytes(bits)
        #print(the_bytes)
        # calculate checksum and check
        checksum = self.__calculate_checksum(the_bytes)
        if the_bytes[4] != checksum:
            return DTHResult(DTHResult.ERR_CRC, 0, 0)

        # ok, we have valid data, return it
        [int_rh, dec_rh, int_t, dec_t, csum] = the_bytes
        if self.__dhttype==0:		#dht11
        	rh = int_rh 		#dht11 20% ~ 90%
        	t = int_t 	#dht11 0..50°C
        else:			#dht21,dht22
        	rh = ((int_rh * 256) + dec_rh)/10
        	t = (((int_t & 0x7F) * 256) + dec_t)/10
        	if (int_t & 0x80) > 0:
        		t *= -1
        return DTHResult(DTHResult.ERR_NO_ERROR, t, rh)

    def __send_and_sleep(self, output, mysleep):
        self.__pin(output)
        time.sleep(mysleep)

    def __collect_input(self):
        # collect the data while unchanged found
        unchanged_count = 0
        # this is used to determine where is the end of the data
        max_unchanged_count = 100
        last = -1
        data = []
        m = bytearray(800)        # needs long sample size to grab all the bits from the DHT
        irqf = disable_irq()
        self.__pin(1)
        for i in range(len(m)):
            m[i] = self.__pin()      ## sample input and store value
        enable_irq(irqf)
        for i in range(len(m)):
            current = m[i]
            data.append(current)
            if last != current:
                unchanged_count = 0
                last = current
            else:
                unchanged_count += 1
                if unchanged_count > max_unchanged_count:
                    break
        #print(data)
        return data

    def __parse_data_pull_up_lengths(self, data):
        STATE_INIT_PULL_DOWN = 1
        STATE_INIT_PULL_UP = 2
        STATE_DATA_FIRST_PULL_DOWN = 3
        STATE_DATA_PULL_UP = 4
        STATE_DATA_PULL_DOWN = 5

        state = STATE_INIT_PULL_UP

        lengths = [] # will contain the lengths of data pull up periods
        current_length = 0 # will contain the length of the previous period

        for i in range(len(data)):

            current = data[i]
            current_length += 1

            if state == STATE_INIT_PULL_DOWN:
                if current == 0:
                    # ok, we got the initial pull down
                    state = STATE_INIT_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_INIT_PULL_UP:
                if current == 1:
                    # ok, we got the initial pull up
                    state = STATE_DATA_FIRST_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_FIRST_PULL_DOWN:
                if current == 0:
                    # we have the initial pull down, the next will be the data pull up
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_UP:
                if current == 1:
                    # data pulled up, the length of this pull up will determine whether it is 0 or 1
                    current_length = 0
                    state = STATE_DATA_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_DOWN:
                if current == 0:
                    # pulled down, we store the length of the previous pull up period
                    lengths.append(current_length)
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue

        return lengths

    def __calculate_bits(self, pull_up_lengths):
        # find shortest and longest period
        shortest_pull_up = 1000
        longest_pull_up = 0

        for i in range(0, len(pull_up_lengths)):
            length = pull_up_lengths[i]
            if length < shortest_pull_up:
                shortest_pull_up = length
            if length > longest_pull_up:
                longest_pull_up = length

        # use the halfway to determine whether the period it is long or short
        halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
        bits = []

        for i in range(0, len(pull_up_lengths)):
            bit = False
            if pull_up_lengths[i] > halfway:
                bit = True
            bits.append(bit)

        return bits

    def __bits_to_bytes(self, bits):
        the_bytes = []
        byte = 0

        for i in range(0, len(bits)):
            byte = byte << 1
            if (bits[i]):
                byte = byte | 1
            else:
                byte = byte | 0
            if ((i + 1) % 8 == 0):
                the_bytes.append(byte)
                byte = 0
        #print(the_bytes)
        return the_bytes

    def __calculate_checksum(self, the_bytes):
        return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255

```

---
**Transmitting the data / Connectivity** 
In order to connect to the Internet I used Wi-Fi. MQTT was used to transfer the data, obtained from the DHT11 sensor, to Pybytes' servers. In order to get this to work, you have to create an account on Pybytes and add the device from the device menu. From there, you select the appropiate device, for example Lopy4 and include the SSID and password for you Wi-Fi network. Once that is done, you can send data to Pybytes servers! There's a [guide](https://docs.pycom.io/pybytes/gettingstarted/) on Pybytes' website.

I was interesting in testing Sigfox and LoRa, but when checking them out I noticed that I didn't have any gateways near me. If you would like to check if you've got a gateway near you, you can check these links for either [LoRa](https://www.thethingsnetwork.org/map) or [Sigfox](https://www.sigfox.com/en/coverage). 

As seen in my Main.py within the While-loop, the code is executed every minute (60 seconds) with the following line: *time.sleep(60)*. 
Within the while-loop, I am sending the measured data to Pybytes with the function *pybytes.send_signal(signal_number, value)* as mentioned earlier.

---
**Presenting the data**
As mentioned, my device sends data to Pybytes every minute. Below is an image of my dashboard on Pybytes. I haven't really made much of a change of the standard dashboard, but the dashboard can be customized in several ways. My dashboard only presents the data recieved for the most recent hour, as well as data recieved in one day. When using the free version of Pybytes, you can save your data in their database for one month! You can however setup integrations with other applications to maintain your data for a longer period of time. 

![Pybytes dashboard](https://i.imgur.com/wqJw67e.png)
*Pybytes dashboard* 

---
**Finalizing the project**
The project have taught me more than I first thought. I have never really tinkered with anything similar before, and I really enjoyed it! 

Due to lack of time, I had to minimize my initial project design. At first, I thought of making a project that utilized a fan which would have turned on if the temperature rose above average in the room the device was present. The next step of my project will be to facilitate the project I just described. Another thing that I could've added to my project was to add a solution to power the device with battery. I will look that up as well.

A fellow classmate, and friend of mine, created a case for my Pycom device and the breadboard using his 3D-printer. If you have a 3D-printer and are interesting in doing this as well, here is the link to the blueprint of the case: [3D-printed case.](https://github.com/HedvigBring/Pycom-Breadboard-Case)

Here's a picture of my device placed in the case mentioned above. 
![](https://i.imgur.com/jLaNBgJ.jpg)

