---
# System prepended metadata

title: Tutorial on measuring Soil and Air temperature with LoPy4

---

# Tutorial on measuring Soil and Air temperature with LoPy4


## Oliver Olsson oo222ew

### Short overview of project
In this project, you are going to learn how to read the temperature in both the soil of a flower pot and in the air nearby. We are also going to take into account the amount of sunlight that is shining on the pot to see if there are any differences between the soil and air temperature.
The data that we are collecting are then going to be send to the pybytes-platform and displayed in nice charts.

#### Estimated time for the project
- Computer and pybytes setup: 1 hour
- Wiring and coding: 2 hours
- 3D-prints: 15-20 hours

### Objective
This project was an eximination of the course "Tillämpad (Applied) Internet of Things, Introduktion (Introduction)" given at Linnéuniversitetet. We had free hands to do what ever we wanted, as long as it had to do with "Internet of Things".
Therefore I chose to do something with my plants at home, and I found it interesting to examine how the soil temperature is affacted by the surrounding air temperature, and the intensity of the sun light on the plants and soil.


### Materials

| Unit       | Picture |Specification|Price (euros)| Link |
| ---------- |:------|--------|---|-----|
|LoPy4|![](https://i.imgur.com/1Q9blFe.png)|MicroPython board with LoRa, Sigfox, Wifi and bluetooth|34.95|https://pycom.io/product/lopy4/|
|Expansion board 3.0|![](https://i.imgur.com/QppCz0E.png)|LoPy4 compatabile expansion board|16.00|https://pycom.io/product/expansion-board-3-0/|
|USB-kabel A-male – micro B 5p male 1m|![](https://i.imgur.com/tXvO6Sk.jpg)|USB connection between LoPy4 and computer|~3.7|https://www.electrokit.com/produkt/usb-kabel-a-hane-micro-b-5p-hane-1m/|
|DS18B20|![](https://i.imgur.com/uURABVZ.jpg)|Waterproof 1-wire temperature sensor|~9.5|https://www.electrokit.com/produkt/temperatursensor-vattentat-metallholje-ds18b20/|
|MCP9700 TO-92|![](https://i.imgur.com/dw1fbI9.jpg)|IC temperature sensor with linear output voltage depending on temperature|~0.76|https://www.electrokit.com/produkt/mcp9700-e-to-to-92-temperaturgivare/|
|Breadbord| ![](https://i.imgur.com/85paDpi.jpg)||~5.6|https://www.electrokit.com/produkt/kopplingsdack-400-anslutningar/|
|Resistor 330 ohm|![](https://i.imgur.com/0WavlEs.jpg)||~0.1|https://www.electrokit.com/produkt/motstand-kolfilm-0-25w-330ohm-330r/|
|Photoresistor 2-5 kohm|![](https://i.imgur.com/npKlOm4.jpg)|Lightsensitiv resistor that changes resistans depending on light|~0.8|https://www.electrokit.com/produkt/fotomotstand-cds-2-5-kohm/|
 

### Computer setup

To start, you have to download an IDE and I recommend Atom as it works well with the LoPy4 and is quite an easy IDE to learn.

You can download Atom here:
https://atom.io/

When you have downloaded Atom, you will have to download the package "pymakr".
You can find a complete guide on how to this here:
https://docs.pycom.io/pymakr/installation/atom/

The next step will be to flash the firmware and connect your unit to Pybytes.
To do this you first need to download the Pycom Firmware Update which can be found here:
https://docs.pycom.io/gettingstarted/installation/firmwaretool/

Next, go to https://pybytes.pycom.io/ and create an account, and then add a Wifi network by pressing:

- Configure networks :arrow_right: ADD WIFI

Lastly you are going to need to add your device to Pybytes and then provision your unit.
Guides on how to do this can be found here:

1. https://docs.pycom.io/pybytes/connect/
2. https://docs.pycom.io/pybytes/connect/quick/ 

When all this is done, you can download the files from the device into your project folder in Atom. Do this by pressing "Download from device" at the left side in pymakr window:
![](https://i.imgur.com/4oqehpj.png)
This will give you three different files:
main.py 
boot.py
pybytes_config.json

The main.py is empty now but should be filled with the code that is furter down below.

When you are finnished with all the files, which we will come to later, and want to upload the project to the device, just press "Upload project to device" at the left in pymakr window.


### Putting everything together

#### 3D files
I have designed a small box to hold the LoPy4 with the expansionbord and the breadbord.
Just a neat way of storing part of your project.
Pictures of the box with the components can be found at the end of this tutorial.

The files can be found here:
https://www.thingiverse.com/thing:4514952/files

#### Circute
![](https://i.imgur.com/S7t78xp.jpg)

Waterproof temperature sensor:
- Red to 3.3 V
- Black to GND
- Yellow to pin 10

Photoresistor:
- Black to 330 ohm resistor -> resistor to GND
- Red to 3.3 V
- Yellow between black on photoresistor and 330 ohm resistor to pin 14

IC temperature sensor:
- Red to 3.3 V
- Black to GND
- Yellow to pin 15
IMPORTANT: The legs of the sensor are not colored, so see the picture above to see the orientation of the sensor in correlation to the drawn wires!

##### Fun fact: Why the 330 ohm resistor?
We know from **Kirchhoff voltage** law that:

*“In any closed loop network, the total voltage around the loop is equal to the sum of all the voltage drops within the same loop”*

This means that if the only resistans that we have in the closed loop with the photoresistor, is the photoresistor it self. All voltage that the LoPy4 is delivering (3.3 V), is going to end up over the photoresistor, no matter the resistans due to the light shining on the photoresistor. The current is going to change, but as the ADC (Analog to Digital converter) in the LoPy4, which we are using in the setup, is measuring voltage, the ADC value will be constant despite the amount of sunlight.

So, what the 330 ohm resistor is doing in the circute, is making the voltage divide between the photoresistor and the 330 ohm resistor.
Without getting too tecnical, this means that now the voltage across each resistor will change in conjunction to the amount of sunlight that is reaching the photoresistor as more light on the photoresistor means lower resistans in the photoresistor which means lower voltage ends up over it, and less light means higher resistans, which means that more voltage ends up over it.

All we now have to do is measure the voltage across one of the resistors (in our case the 330 ohm resistor), and we will get different values depending on the amount of sunlight!

#### Form circuit to plant pot
Now all you have to do is to put the waterproof temperature sensor in the soil of the pot and point the photoresistor out the window to sense the sunlight hitting the plant/pot.
The IC tempsensor can be put on the breadbord as it only going to read the air temperature around the pot.

### Platform
The platform that I used was Pybytes with Wifi connection.
Pybytes gives you a simple way of transmitting and displaying your data on pybytes.pycom.io.
How to setup pybytes with charts you can check out this guide:
https://docs.pycom.io/pybytes/dashboard/
### The code
You will need the following two files: 

#### onewire.py
Lirbrary for the waterproof 1-wire sensor.
This library can be found here: 
https://docs.pycom.io/tutorials/all/owd/#app
```python=1
"""
OneWire library for MicroPython
"""

import time
import machine

class OneWire:
    CMD_SEARCHROM = const(0xf0)
    CMD_READROM = const(0x33)
    CMD_MATCHROM = const(0x55)
    CMD_SKIPROM = const(0xcc)

    def __init__(self, pin):
        self.pin = pin
        self.pin.init(pin.OPEN_DRAIN, pin.PULL_UP)

    def reset(self):
        """
        Perform the onewire reset function.
        Returns True if a device asserted a presence pulse, False otherwise.
        """
        sleep_us = time.sleep_us
        disable_irq = machine.disable_irq
        enable_irq = machine.enable_irq
        pin = self.pin

        pin(0)
        sleep_us(480)
        i = disable_irq()
        pin(1)
        sleep_us(60)
        status = not pin()
        enable_irq(i)
        sleep_us(420)
        return status

    def read_bit(self):
        sleep_us = time.sleep_us
        enable_irq = machine.enable_irq
        pin = self.pin

        pin(1) # half of the devices don't match CRC without this line
        i = machine.disable_irq()
        pin(0)
        sleep_us(1)
        pin(1)
        sleep_us(1)
        value = pin()
        enable_irq(i)
        sleep_us(40)
        return value

    def read_byte(self):
        value = 0
        for i in range(8):
            value |= self.read_bit() << i
        return value

    def read_bytes(self, count):
        buf = bytearray(count)
        for i in range(count):
            buf[i] = self.read_byte()
        return buf

    def write_bit(self, value):
        sleep_us = time.sleep_us
        pin = self.pin

        i = machine.disable_irq()
        pin(0)
        sleep_us(1)
        pin(value)
        sleep_us(60)
        pin(1)
        sleep_us(1)
        machine.enable_irq(i)

    def write_byte(self, value):
        for i in range(8):
            self.write_bit(value & 1)
            value >>= 1

    def write_bytes(self, buf):
        for b in buf:
            self.write_byte(b)

    def select_rom(self, rom):
        """
        Select a specific device to talk to. Pass in rom as a bytearray (8 bytes).
        """
        self.reset()
        self.write_byte(CMD_MATCHROM)
        self.write_bytes(rom)

    def crc8(self, data):
        """
        Compute CRC
        """
        crc = 0
        for i in range(len(data)):
            byte = data[i]
            for b in range(8):
                fb_bit = (crc ^ byte) & 0x01
                if fb_bit == 0x01:
                    crc = crc ^ 0x18
                crc = (crc >> 1) & 0x7f
                if fb_bit == 0x01:
                    crc = crc | 0x80
                byte = byte >> 1
        return crc

    def scan(self):
        """
        Return a list of ROMs for all attached devices.
        Each ROM is returned as a bytes object of 8 bytes.
        """
        devices = []
        diff = 65
        rom = False
        for i in range(0xff):
            rom, diff = self._search_rom(rom, diff)
            if rom:
                devices += [rom]
            if diff == 0:
                break
        return devices

    def _search_rom(self, l_rom, diff):
        if not self.reset():
            return None, 0
        self.write_byte(CMD_SEARCHROM)
        if not l_rom:
            l_rom = bytearray(8)
        rom = bytearray(8)
        next_diff = 0
        i = 64
        for byte in range(8):
            r_b = 0
            for bit in range(8):
                b = self.read_bit()
                if self.read_bit():
                    if b: # there are no devices or there is an error on the bus
                        return None, 0
                else:
                    if not b: # collision, two devices with different bit meaning
                        if diff > i or ((l_rom[byte] & (1 << bit)) and diff != i):
                            b = 1
                            next_diff = i
                self.write_bit(b)
                if b:
                    r_b |= 1 << bit
                i -= 1
            rom[byte] = r_b
        return rom, next_diff

class DS18X20(object):
    def __init__(self, onewire):
        self.ow = onewire
        self.roms = [rom for rom in self.ow.scan() if rom[0] == 0x10 or rom[0] == 0x28]
        self.fp = True
        try:
            1/1
        except TypeError:
            self.fp = False # floatingpoint not supported

    def isbusy(self):
        """
        Checks wether one of the DS18x20 devices on the bus is busy
        performing a temperature convertion
        """
        return not self.ow.read_bit()

    def start_conversion(self, rom=None):
        """
        Start the temp conversion on one DS18x20 device.
        Pass the 8-byte bytes object with the ROM of the specific device you want to read.
        If only one DS18x20 device is attached to the bus you may omit the rom parameter.
        """
        if (rom==None) and (len(self.roms)>0):
            rom=self.roms[0]
        if rom!=None:
            rom = rom or self.roms[0]
            ow = self.ow
            ow.reset()
            ow.select_rom(rom)
            ow.write_byte(0x44)  # Convert Temp

    def read_temp_async(self, rom=None):
        """
        Read the temperature of one DS18x20 device if the convertion is complete,
        otherwise return None.
        """
        if self.isbusy():
            return None
        if (rom==None) and (len(self.roms)>0):
            rom=self.roms[0]
        if rom==None:
            return None
        else:
            ow = self.ow
            ow.reset()
            ow.select_rom(rom)
            ow.write_byte(0xbe)  # Read scratch
            data = ow.read_bytes(9)
            return self.convert_temp(rom[0], data)

    def convert_temp(self, rom0, data):
        """
        Convert the raw temperature data into degrees celsius and return as a fixed point with 2 decimal places.
        """
        temp_lsb = data[0]
        temp_msb = data[1]
        if rom0 == 0x10:
            if temp_msb != 0:
                # convert negative number
                temp_read = temp_lsb >> 1 | 0x80  # truncate bit 0 by shifting, fill high bit with 1.
                temp_read = -((~temp_read + 1) & 0xff) # now convert from two's complement
            else:
                temp_read = temp_lsb >> 1  # truncate bit 0 by shifting
            count_remain = data[6]
            count_per_c = data[7]
            if self.fp:
                return temp_read - 25 + (count_per_c - count_remain) / count_per_c
            else:
                return 100 * temp_read - 25 + (count_per_c - count_remain) // count_per_c
        elif rom0 == 0x28:
            temp = None
            if self.fp:
                temp = (temp_msb << 8 | temp_lsb) / 16
            else:
                temp = (temp_msb << 8 | temp_lsb) * 100 // 16
            if (temp_msb & 0xf8) == 0xf8: # for negative temperature
                temp -= 0x1000
            return temp
        else:
            assert False

```
#### main.py
```python=1
import time
import machine
from machine import Pin
from onewire import DS18X20
from onewire import OneWire
import pycom

#Variables
airTemp = 0.0
soilTemp = 0.0
moist = 0.0
light = 0.0

#Enable DS18B20 TEMPSENSOR on pin 10
ow = OneWire(Pin('P10'))
temp = DS18X20(ow)


#Enable LIGHTSENSOR on pin 14
adc = machine.ADC()
bpin = adc.channel(pin='P14', attn = machine.ADC.ATTN_11DB)

#Enable ROOMTEMP SENSOR on pin 15
adc = machine.ADC()
cpin = adc.channel(pin='P15')

while True:

    #===========================================================================
    #Take the average of 10 readings
    for i in range(10):
        temp.start_conversion()
        time.sleep(0.5)
        airTemp += (cpin.voltage()-500.0)/10.0
        soilTemp += (temp.read_temp_async()+0.0)
        light += ((bpin.voltage()-110.0)/2400.0)*100 #Gives light in percent

    airTemp /= 10.0
    soilTemp /= 10.0
    light /= 10.0
    #===========================================================================

    #Send data to pybytes
    pybytes.send_signal(1, airTemp)
    pybytes.send_signal(2, soilTemp)
    pybytes.send_signal(3, light)

    #Sleep for 15 minutes (900 seconds) before next reading
    time.sleep(900)

```
Just copy and paste them into two files, main.py and onewire.py, and save into your project folder.


### Transmitting the data / connectivity

Data is sent, over Wifi, once every 15 minutes as the temperature and amount of sunlight changes quite slow. So sending data every second isn't going to give better information. But sending data four times an hour seemed to be a good interval.

The data is sent to pybytes platform using MQTT, which is a lightweight messaging protocol that works great when you want to send small package of data.

### Presenting the data
In Pybytes you can create some nice charts displaying your data. The data is then saved for one month. The data is saved every 15 minutes.

This is how it can look at the Pybytes platform when displaying data:

![](https://i.imgur.com/kWiZZgq.png)


### Finalizing the design
Now you are all set and done if you want to recreate the project!

This project has great potential if you want to keep building and expanding the project. 
The next step could be to add an soil moisture sensor and maybe connect a water pump that will water the plants automatically when soil moisture drops below a certain percentage.

Another potential for development could be to send the data via LoRa or Sigfox if you want to monitor your plants in your summer cabin that has no Wifi.

Here are some picture of the final project (note: the antenna isn't needed for wifi connection):

![](https://i.imgur.com/zlXZbnD.jpg)

![](https://i.imgur.com/bXP94Jp.jpg)

![](https://i.imgur.com/sJZ1NCu.jpg)

Thanx for checking out my project and I hope you found it intresting enough to try it out your self! 