---
# System prepended metadata

title: Measuring data for indoor hydroponic gardening

---

# Measuring data for indoor hydroponic gardening

**Gustav Eriksson** ge222ep

**Short Project Overview**
The purpose of this project is to with the help of a Pycom Microcontroller measure light, temperature and humidity in order to see if my kitchen is a legit area for hydroponic gardening.

**Project Scope**

The scope of the project is set to be finished during five weeks time.

**Objective**

Recently I have been interested in indoor gardening with the focus being on herbs, tomatoes and other edible  plants for cooking. After using the traditional method with pots of soil I discovered hydroponic gardening which does not require any soil at all and could lower the required water usage.

The purpose for introducing sensors for measuring data is to optimize and find the best environment my apartment can provide and in the long round also automate the process. 

Since I have no prior knowledge in any kind of computer science I hope to generate insights in how to write code, connect different sensors etc on a microcontroller and read the data generated at a basic level.

**Material**



| Material | Description | Obtained at, Price|
| -------- | -------- | -------- |
| LoPy4| Microcontroller    | Electrokit.se, Bundle: 995 SEK  |
| Expansion Board v3.1|  Expansion Board for LoPy4  | - // -    |
| Micro USB Cable|  -   | - // -    |
| Breadboard| -     |- // -    |
| Resistor | 4,7kOhm    | - // -     |
| Jumper Cable x8| -    | - // -    |
| LDR Sensor| Sensor for measuring light    | - // -   |
| DHT11 Sensor| Sensor for measuring temperature and humidity    | Elektrokit.se, 39 SEK    |
| Hydroponic Garden | Container with room for water and plants    | IKEA, 85 SEK    |
| Plant capsules| Plug for seeds    | IKEA, 29 SEK    |
| |     | Total Cost: 1148 SEK  |




**Computer Setup**
 
The device programmed using Atom as IDE due to it's convenient and visually pleasing interface. Before writing any code, the LoPy4 is flashed with the latest firmware (currently FW version 1.20.2.RC10). The flashing was made by downloading Pycom's Firmware Updater, connecting the LoPy4 and then running the updater to flash the software on the device. During this process the device was also connected to Pybytes using an activational token generated on the website. The device could then send data to the cloud at the Pybytes website, where also the Wifi is registered.
After the LoPy4 is updated and connected to Pybytes, the PyMakr plugin is installed via the Atom IDE. After this step the device is ready to program. After code is written, the code is uploaded to the device and sends data to Pybytes.

**Putting everything together**


The electronics are connected by giving a current from a 3.3 volts inputs running through a DHT11-sensor with a 4,7kOhm resistor, to a LDR-sensor with a 10kOhm resistor and finally to ground. The two sensors are sending data measured to designated pins on the expansion board. This setup is mainly for development at home-use but can be scaled and further developed with more sensors and could be automated. See figure below for complete circuit diagram. 

![](https://i.imgur.com/dNHJZqR.jpg)
Figure 1: Circuit Diagram




**Platform**

The platform used is Pybytes due to it is very convenient to use with the LoPy4 and is free of charge. Pybytes offers 5MB of cloud service with as many devices as the user wants free of charge. They also offer device management, network management, device monitoring with the possibility to intergrate with different APIs. As a new beginner this seemed like a perfect choice due to all the confusion with different terms and concepts of computer science.

**The code**

The code starts with importing required libraries needed (see dht.py library code below).
The main code then set pin 20 for the DHT11-sensor and pin 13 for the LDR-sensor, since that's the way the sensors are connected to the expansion board. A While-loop then starts reading the data from the pins and finally send the data to Pybytes as signal 1, 2 and 3. It then sleeps for 900 seconds due to it should read data during a cycle of sun that enters my kitchen every 15 minutes.



```
import machine
import time
from machine import Pin
from dht import DHT                                             # Import class from dht.py in lib

#DHT11 Sensor
th = DHT(Pin('P20', mode=Pin.OPEN_DRAIN), 0)                    # Select pin for DHT11. Type 0 = dht11
time.sleep(2)

#LDR Sensor
adc = machine.ADC(bits=10)                                      # create an Analog to Digital Conversion
apin = adc.channel(pin='P13', attn = machine.ADC.ATTN_11DB)     # Select pin 13 for LDR.

while True:                                                     # Create while loop for reading DHT11
    result = th.read()
    while not result.is_valid():
        time.sleep(.5)
        result = th.read()
    print('Temp:', result.temperature)                          # Print Temperature from DHT11
    print('RH:', result.humidity)                               # Print Relative Humidity from DHT11

    light = apin()                                              # Read value from LDR
    print('Light:', light)                                      # Print value from LDR

                                                                # < 100: dark
                                                                # 100-700 average indoor light
                                                                # 700 +: bright daylight

    pybytes.send_signal(1,result.temperature)                   # Send Temperature value from DHT11 to Pybytes via signal 1
    pybytes.send_signal(2,result.humidity)                      # Send Humidity value from DHT11 to Pybytes via signal 2
    pybytes.send_signal(3,light)                                # Send Light value from LDR to Pybytes via signal 3

    time.sleep(900)                                               # Sleep and measure every 900 sec (15 min)
```
The library dht.py used is following:

```

from machine import enable_irq, disable_irq,  Pin


class DHTResult:
    '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 DHT:
    '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**


The data is sent every 15 minutes to Pybytes over Wi-fi since the setup is to be at the same place (in the kitchen) that has a strong wi-fi connection all day around. Via Pybytes the data then is integrated with Webhooks where it can transport the data. 


**Presenting the data**
Describe the presentation part. How is the dashboard built? How long is the data preserved in the

The dashboard used is the dashboard on Pybytes which stores a maximum of 5MB of data, free of charge for 6 months. The visual presentation are line diagrams that show three different graphs of the data gathered from light, humidity and temperature.

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

Figure 2: Data measured visualized at Pybytes

**Finalizing the design**

I am happy with the final result given that I had zero knowlodge of data science before this course. The setup works and measure the light, temperature and humidity and can provide an answer if my kitchen is a legit spot for plants, see figure below.

![](https://i.imgur.com/1l9pGnE.jpg)
Figure 3: Hydroponic garden with sensors attached 



![](https://i.imgur.com/Ljf2SAp.jpg)
Figure 4: Close-up of LoPy4 and sensors.

I would like to improve and scale this setup with sensors e.g for reading water level, pH-value of water and then automate this by a pump that adds fertilizer depending on the levels in the water. But as a very basic start, I think learning the simplest form of connecting sensors to writing code has given me a foundation to learn to setup a more complex system in
time. I would also in the future build my own system with e.g a lot of pipes and a pump that sends the water around server ways and pipes. But that require quite the bit more knowledge from me in order to setup.



Overall I think this course was a bit too hard for me in order to implement aspects like Node-RED, Grafana, LoRa, Sigfox etc. I hard time just understanding how to connect the LoPy4 it self and make it work over wi-fi. I also struggling with how to both find code and also how to write and figure out what was wrong with the code. I spent a lot amont of time to understand how to setup LoRa that in the end was wasted since I gave up. It seem though like more people couldn't make the LoRa work either.

So, in this project I ended up only measuring the data and sending them to Pybytes where I analyze and visualise the data. It seem that getting steps after that was expect in the tutorial but this was all I managed to complete in my capability. 

At least I feel happy about learning the basics and I am excited to scale this and learn more about both coding and connecting circuits.

```
