# Internet of things summer course 2020
Pontus Folke pf222ic
## Overview
For this project i chose to make a small station that masseurs temperature , air humidity, light and helps you keep track of your plants by masseuring the moisture in the soil. I have worked together with Ludwig Juelsson in this project.
### Time
If you follow this guide the project should be done in about 3-5 h depending on your previous experience.
## Objective
I choose this project because i have had the idea for keeping track of the plants for a while, and thought this was a good time to actually try it out. And it would be helpful to have my plants remind me when they need water. The idea for the other sensors came from if the station was placed somewhere that wasn't your apartment/house. If it where placed in a greenhouse the temperature and air humidity would be interesting to know so the plans don't die because it gets to warm or cold in there. With this you would be a much better overview of the environment so you could take better care of your plants.
### Material
| Item | From | Price (sek) | What it dose | Picture |
| ------------------- |:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----:| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| Pycom LoPy4 | [se.rs](https://se.rs-online.com/web/p/radio-frequency-development-kits/1628047?cm_mmc=SE-PLA-DS3A-_-google-_-PLA_SE_SE_Semiconductors_Whoop-_-(SE:Whoop!)+Radio+Frequency+Development+Kits-_-1628047&matchtype=&pla-323868530598&gclid=Cj0KCQjwoub3BRC6ARIsABGhnyYv5lA87SHpd4iYNz-dP4N0Uk0A0JiALQP1DvOGRP-9nj54YLgJvSgaAnY5EALw_wcB&gclsrc=aw.ds) | 443 | Pycom's LoPy4 is a device, programmable with MicroPython, which is perfect for our needs with several ways to connect to different wireless services. It has both digital and analog input and outputs, allowing a wide variety of sensors. With the Expansionboard V3.1 it is very easy to use with all the wiring (see fig 2). |  |
| Expansionboard V3.1 | [m.nu](https://www.m.nu/pycom/expansion-board-31?gclid=Cj0KCQjwoub3BRC6ARIsABGhnybbZAhS18XPU3KXLwx1xvVMTd60_zFS6KOdmBvIcVlH-FkxDVK-Zg8aApdREALw_wcB) | 269 | To make the use of our LoPy4, we used an Expansion board. This allows us to use all the pins of the LoPy with ease, as it is plug-and-play. This board might need some updating which we will be covering later in the tutorial. |  |
| Breadboard | [cdon.se](https://b2c.cdon.se/hem-tradgard/kopplingsplatta-breadboard-med-400-punkter-p36235354?fbclid=IwAR32gVgkEuN8Uxe_27BafrzeaUL1SfBiVabzD6eFvwCyDHTEeQFd1z5bn0k) | 34 | The breadboard is used so that connecting our sensors together with the expansion board will be easier and dose not require sawdering. |  |
| Photoresistor | [conrad.se](https://www.conrad.se/p/iduino-1485310-ljusmotstand-arduino-1485310?utm_campaign=google+shopping+ads&utm_content=Cj0KCQjwoub3BRC6ARIsABGhnybvWzDDXJNjPjqxFTHxnQvcFc0Mf99zR1BZ5COcps2UbdA-BwP2reIaAvz6EALw_wcB%3B337%3B0&utm_medium=cpc&utm_source=shopello_se&utm_term=övrigt-13&utm_term=1485310&vat=true) | 42 | With this resistor you are able to masseurs the light levels at the station. If it's dark it will return a high value and the brighter it is the lower the value will be. |  |
| Soil Moist Sensor | [electrokit.com](https://www.electrokit.com/produkt/jordfuktighetssensor/) | 29 | This sensor masseurs the moisture in the soil around it. If the soil is wet the connection will be better and it will return a low value and if the soil is dry the connection will be worse and the sensor will return a higher value. |  |
| DHT11 sensor | [electrokit.com](https://www.electrokit.com/produkt/digital-temperatur-och-fuktsensor-dht11/) | 49 | The DHT11 sensor is the sensor that masseurs the temperature and humidity. |  |
| Jumper Cables M/M | [electrokit.com](https://www.electrokit.com/produkt/labbsladdar-100mm-hane-hane-30-pack/) | 33 | Cables for connecting the sensors to the board. |  |
| Resistor 1 kohm | [electrokit.com](https://www.electrokit.com/produkt/motstand-kolfilm-0-25w-1kohm-1k/) | 1 | Had some issues getting the DHT11 sensor to work propperly so had to add a extra resistor to make it work |  |
## Computer Setup
I choose to use Atom as program to write my code and then upload it to the device via USB/serial port. Before you start writing code you need to make sure your LoPy4 and Expansion board unit is updated. To update your LoPy4 unit go to [LoPy update](https://docs.pycom.io/gettingstarted/installation/firmwaretool/) and then download the appropriate one for your OS and follow the instructions. When that is done go to [Board update](https://docs.pycom.io/pytrackpysense/installation/firmware/?fbclid=IwAR2nbXl0aODYXfwF5CnWeX9haQeWy2E6Wj73bS_uNlaigcgZAXQDC0pG9Yc) find the instructions for your OS and follow the instructions. When both units are updated it's time to download your choice of IDE. I chose to use Atom so i will link to that. If you prefer to use something go for it. Go to [Atom.io](https://atom.io/) and download the appropriate one for your OS. When atom is done downloading and installing you need to install the *pymakr* plugin. You do this by pressing "Install a package" in the Welcome window.

There you search for pymakr and install the plugin.

Now when you plug in your Pycom device to your computer via USB it should find and connect with Atom automatically. If it dosen't go to "Connect Device" and chose your Pycom unit. When everything is connected and working you should see ">>>" in the bottom window in Atom.
## Putting everything together

For connecting everything together i'll be using the picture above to try and guide you. All the black lines are cables going to ground (GND) and all the red ones are cables that are connected to 3.3V. Start by connecting the GND on the expansion board and the "-" on the breadboard and then the 3.3V on the expansion board with the "+" on the breadboard. Now i will go from top to bottom of the picture. The first sensor we will connect is the DHT11 sensor. The far left pin is the data pin and will connect to the port called "P23" on the expansion board. The middle one is for voltage and the right one is for ground. Then you will need to add a resistor between the data and voltage. The middle sensor is the sensor for the soil and the left pin (AD) is the data pin that will connect to the port called "P18" on the expansion board. Then we skip the DA pin and connect the GND pin to the "-" on the breadboard. The right pin (VCC) is for voltage and will connect to the "+" on the breadboard. The last sensor is the photoresistor and i couldn't find any good pictures of the one used in this project so i improvised my own. LDR stand for light dependent resistor. The left pin is the data pin and will connect to the port called "P20" on the expansion board. Then VCC and GND will connect to "+" respectively "-" on the breadboard.
I did not have access to a 3D printer for this project otherwise i would be a good idea to print some box/container for this to make sure everything is secure and wont move around to much.
## Platform
For this I chose Ubidots as my platform to present the data from the sensors, I also looked at Pybytes sense that is the platform my partner choose to use. Ubidots is a cloud based service that can easily be used for presenting data from different sources. As long as this is a hobby project the free version will be enough, but if you wanna upscale it and add more station you might need to upgrade to a paid subscription. I found an example code bit online that i then modified to fit into the code but i will talk more about that in later chapter.
## The code
This is the library for the DHT11 sensor:
```python=
import time
import pycom
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 == DHTResult.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(pin, mode=Pin.OPEN_DRAIN)
self.__dhttype = sensor
self.__pin(1)
time.sleep(1.0)
def read(self):
# pull down to low
self.__send_and_sleep(0, 0.019)
data = pycom.pulses_get(self.__pin,100)
self.__pin.init(Pin.OPEN_DRAIN)
self.__pin(1)
#print(data)
bits = []
for a,b in data:
if a ==1 and 18 <= b <= 28:
bits.append(0)
if a ==1 and 65 <= b <= 75:
bits.append(1)
#print("longueur bits : %d " % len(bits))
if len(bits) != 40:
return DHTResult(DHTResult.ERR_MISSING_DATA, 0, 0)
#print(bits)
# we have the bits, calculate bytes
the_bytes = self.__bits_to_bytes(bits)
# calculate checksum and check
checksum = self.__calculate_checksum(the_bytes)
if the_bytes[4] != checksum:
return DHTResult(DHTResult.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 DHTResult(DHTResult.ERR_NO_ERROR, t, rh)
def __send_and_sleep(self, output, mysleep):
self.__pin(output)
time.sleep(mysleep)
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
```
This is the library needed for connecting to Ubidots:
```python=
import usocket
class Response:
def __init__(self, f):
self.raw = f
self.encoding = "utf-8"
self._cached = None
def close(self):
if self.raw:
self.raw.close()
self.raw = None
self._cached = None
@property
def content(self):
if self._cached is None:
try:
self._cached = self.raw.read()
finally:
self.raw.close()
self.raw = None
return self._cached
@property
def text(self):
return str(self.content, self.encoding)
def json(self):
import ujson
return ujson.loads(self.content)
def request(method, url, data=None, json=None, headers={}, stream=None):
try:
proto, dummy, host, path = url.split("/", 3)
except ValueError:
proto, dummy, host = url.split("/", 2)
path = ""
if proto == "http:":
port = 80
elif proto == "https:":
import ussl
port = 443
else:
raise ValueError("Unsupported protocol: " + proto)
if ":" in host:
host, port = host.split(":", 1)
port = int(port)
try:
ai = usocket.getaddrinfo(host, port, 0, usocket.SOCK_STREAM)
except:
ai = usocket.getaddrinfo(host, port)
s = usocket.socket()
ai = ai[0]
try:
s.connect(ai[-1])
if proto == "https:":
s = ussl.wrap_socket(s, server_hostname=host)
s.write(b"%s /%s HTTP/1.0\r\n" % (method, path))
if not "Host" in headers:
s.write(b"Host: %s\r\n" % host)
# Iterate over keys to avoid tuple alloc
for k in headers:
s.write(k)
s.write(b": ")
s.write(headers[k])
s.write(b"\r\n")
if json is not None:
assert data is None
import ujson
data = ujson.dumps(json)
s.write(b"Content-Type: application/json\r\n")
if data:
s.write(b"Content-Length: %d\r\n" % len(data))
s.write(b"\r\n")
if data:
s.write(data)
l = s.readline()
#print(l)
l = l.split(None, 2)
status = int(l[1])
reason = ""
if len(l) > 2:
reason = l[2].rstrip()
while True:
l = s.readline()
if not l or l == b"\r\n":
break
#print(l)
if l.startswith(b"Transfer-Encoding:"):
if b"chunked" in l:
raise ValueError("Unsupported " + l)
elif l.startswith(b"Location:") and not 200 <= status <= 299:
raise NotImplementedError("Redirects not yet supported")
except OSError:
s.close()
raise
resp = Response(s)
resp.status_code = status
resp.reason = reason
return resp
def head(url, **kw):
return request("HEAD", url, **kw)
def get(url, **kw):
return request("GET", url, **kw)
def post(url, **kw):
return request("POST", url, **kw)
def put(url, **kw):
return request("PUT", url, **kw)
def patch(url, **kw):
return request("PATCH", url, **kw)
def delete(url, **kw):
return request("DELETE", url, **kw)
```
This is the main code:
```python=
from network import WLAN
import urequests as requests
import time
from machine import Pin
import machine
import _thread
from dht import DHT
#--------------------UBIDOTS--------------------------
TOKEN = "BBFF-CXTgCfGpjwSMB4cT53xC0ohZUh14mo" #Put here your TOKEN
DELAY = 1 # Delay in seconds
wlan = WLAN(mode=WLAN.STA)
wlan.antenna(WLAN.INT_ANT)
# Assign your Wi-Fi credentials
wlan.connect("wifi_name", auth=(WLAN.WPA2, "wifi_password"), timeout=5000)
while not wlan.isconnected ():
machine.idle()
print("Connected to Wifi\n")
#-------------------^Connections^-------------------
th = DHT(Pin('P23', mode = Pin.OPEN_DRAIN), 0) #set Pin 23 as an input for temperature/humidity
adc1 = machine.ADC()
adc2 = machine.ADC()
inLumPin = adc1.channel(pin ='P20')
inMoistPin = adc2.channel(pin = 'P18')
#set 2 pins as ADC channels for our light sensor and moist sensor
def send_moist_data(): # function to receive & handle data from sensor, sends data
while True:
x = 0
Moist_sum = 0
while x < 100: # iterate the loop 100 times
Moist_data = inMoistPin()
Moist_sum += Moist_data # add new moist_data with previous
x += 1
time.sleep(0.1)
if x == 99: # when x = 99, divide the sum with 100 to get average
Moist_av = Moist_sum / 100
print("Median Soil Moist", Moist_av) # print average
pybytes.send_signal(4, Moist_av) # send data to PyBytes
post_var_soil("pycom", Moist_av) # for sending data to Ubidots
break
elif x < 99:
continue # if the loop is not finished, continue
time.sleep(0.1)
_thread.start_new_thread(send_moist_data, ()) # keeps send_moist_data on a seperate thread
def send_Lominous_data(): # function to receive & handle data from light sensor (photoresistor)
while True:
Lom_data = inLumPin() # reads from the inLumPin Pin
if Lom_data < 100: # changes output depending on the signal
print("Very dark =", Lom_data)
pybytes.send_signal(3, Lom_data) # send data to PyBytes
post_var_lum("pycom", Lom_data) # for sending data to Ubidots
elif Lom_data < 1000:
print("Dark = ", Lom_data)
pybytes.send_signal(3, Lom_data)
post_var_lum("pycom", Lom_data)
elif Lom_data < 3000:
print("Light = ", Lom_data)
pybytes.send_signal(3, Lom_data)
post_var_lum("pycom", Lom_data)
elif Lom_data < 4095:
print("Bright = ", Lom_data)
pybytes.send_signal(3, Lom_data)
post_var_lum("pycom", Lom_data)
else:
print("Direct sunlight = ", Lom_data)
pybytes.send_signal(3, Lom_data)
post_var_lum("pycom", Lom_data)
time.sleep(30) # repeat every 30 seconds
_thread.start_new_thread(send_Lominous_data, ()) # keeps send_Lominous_data on a seperate thread
def send_TH_data(): # function to receive & handle temperature/humidity data
while True:
result = th.read() # reads assigned pin
while not result.is_valid(): # tries after sleep to read data again
time.sleep(.5)
result = th.read()
print('Temperature:', result.temperature)
print('Relative Humidity:', result.humidity)
pybytes.send_signal(1,result.temperature) # sends data to pycom
pybytes.send_signal(2,result.humidity) # sends data to pycom
post_var_temp("pycom", result.temperature) # for sending data to Ubidots
post_var_humidity("pycom", result.humidity) # for sending data to Ubidots
time.sleep(30)
_thread.start_new_thread(send_TH_data, ()) # keeps send_TH_data on a seperate thread
# Builds the json to send the request
def build_json(variable1, value1):
try:
data = {variable1: {"value": value1}}
return data
except:
return None
# Sends the request. Please reference the REST API reference https://ubidots.com/docs/api/
def post_var_temp(device, value1): #sends temperature value to Ubidots
try:
url = "https://industrial.api.ubidots.com/"
url = url + "api/v1.6/devices/" + device
headers = {"X-Auth-Token": TOKEN, "Content-Type": "application/json"}
data = build_json("temp", value1)
if data is not None:
print(data)
req = requests.post(url=url, headers=headers, json=data)
return req.json()
else:
pass
except:
pass
def post_var_humidity(device,value2): #sends humidity value to Ubidots
try:
url = "https://industrial.api.ubidots.com/"
url = url + "api/v1.6/devices/" + device
headers = {"X-Auth-Token": TOKEN, "Content-Type": "application/json"}
data = build_json("humidity", value2)
if data is not None:
print(data)
req = requests.post(url=url, headers=headers, json=data)
return req.json()
else:
pass
except:
pass
def post_var_lum(device,value3): #sends luminous value to Ubidots
try:
url = "https://industrial.api.ubidots.com/"
url = url + "api/v1.6/devices/" + device
headers = {"X-Auth-Token": TOKEN, "Content-Type": "application/json"}
data = build_json("Luminous", value3)
if data is not None:
print(data)
req = requests.post(url=url, headers=headers, json=data)
return req.json()
else:
pass
except:
pass
def post_var_soil(device,value4): #sends soil moisture value to Ubidots
try:
url = "https://industrial.api.ubidots.com/"
url = url + "api/v1.6/devices/" + device
headers = {"X-Auth-Token": TOKEN, "Content-Type": "application/json"}
data = build_json("Soil_moist", value4)
if data is not None:
print(data)
req = requests.post(url=url, headers=headers, json=data)
return req.json()
else:
pass
except:
pass
```
## Transmitting the data
Sadly i couldn't get any connection to any LoRa gateway close to me, so I instead chose to go with Wifi sense it's easily accessible, good range, fast and plenty of bandwidth so we don't have to worry about the size of the packages we are sending,
The first thing you wanna do is head over to [Ubidots](https://ubidots.com/) sign up for free, then press "Device" in the top middle of the page. Select add new device and now you should be able to see your dashboard.
### Ubidots setup
To be able to send data to Ubidots you will need to packed you data, into a so called Jason-Object, and give it the proper address. In our case the address is called a "Token" and can be found on the left side of the screen. Ubidots uses a webhook protocol for sending and receiving data.

Copy that and then add it into line 10 of the main code
```python=10
TOKEN = "BBFF-CXTgCfGpjwSMB4cT53xC0ohZUh14mo" #Put here your TOKEN
```
Now the data will find it's way to your dashboard! For actually packaging there are 2 functions in the code that handles this.
```python=360
# Builds the json to send the request
def build_json(variable1, value1):
try:
data = {variable1: {"value": value1}}
return data
except:
return None
# Sends the request. Please reference the REST API reference https://ubidots.com/docs/api/
def post_var_temp(device, value1): #sends temperature value to Ubidots
try:
url = "https://industrial.api.ubidots.com/"
url = url + "api/v1.6/devices/" + device
headers = {"X-Auth-Token": TOKEN, "Content-Type": "application/json"}
data = build_json("temp", value1)
if data is not None:
print(data)
req = requests.post(url=url, headers=headers, json=data)
return req.json()
else:
pass
except:
pass
```
If you want to add or remove data you want to display on your dashboard all you have to do is remove of add one of the last functions (from row 369 to 382). To give label the data, so you know what you are reading in the dashboard later, simply change the "temp" on row 374 to whatever you feel appropriate. Right now every sensor send it's data to Ubidots every 30 seconds, which is a bit to often, but i kept it like this so you don't have to wait very long to see if the data is being sent property, and this can easily be modified by changing the *time.sleep()* at the end of every sensors function.
## Presenting the data
The data is saved at Ubidots every time you send something
Now all the hard work should be done and all that is left is to look at your data rolling in. Here you can see your dashboard and you can press every box/data type to get a closer look at how the data have changed over some time
### Dashboards

A dry plan before and after getting water.

Change in humidity

## Finalizing the design
As you can see from the pictures below the project still needs some beauty work before being completely done. But it is fully functional, so if you want to build a nice box or something for it go ahead! We didn't have access to a 3D printer. Otherwise we would have made something to keep everything together nicely, to keep all the sensors safe and so it would look better.


This has been a really fun project to work on and we have many ideas on how to evolve and improve on this design. Maybe if there is more time we will do that and update this guide with the improvements!