# 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!