# Temperature and humidity monitoring with Lopy4

###### `Emmy Adolfsén - ea224nb`
This document is a walkthrough of a project with the aim to monitor temperature and humidity in an environment for fruitwine fermentation. A device for this will be built using Lopy4 with expansion board connected to a DHT11 temperature and humidity sensor. The device will be connected to a local wifi and send signals once every hour to a Ubidots application. This project takes around 4 hours to set up.
## Objective
A local startup is fermenting fruitwine and ciders and also storing the products in a basement. To make sure that the temperature and humidity stays consistent they want to monitor the environment. To do this they need to be able to read and collect results from the measurements on a regular timeline every day.
This project will help me understand how I can use sensors and connect them all the way to an app for the end users.
## Material
For this project I used Pycoms Lopy4 and expansion board together with a DHT11 sensor.

*Figure 1. Lopy4 on expansion board*

*Figure 2. DHT11 temperature and humidity sensor*
Material and hardware used in this project was bought from Electrokit:
- [x] [LoPy4 and sensors bundle](https://www.electrokit.com/produkt/lnu-1dt305-tillampad-iot-lopy4-and-sensors-bundle/) - 949 SEK
- [x] [DHT11](https://www.electrokit.com/produkt/digital-temperatur-och-fuktsensor-dht11/) - 49 SEK
| Material | Specification |
| ----------------- |:----------------------- |
| LoPy4 | Development board |
| Expansion board | Enabling programming and connecting to the development board|
| Breadboard | Enabling easier connection with jumper wires|
| Micro USB cable | Connection between expansion board and computer|
| Jumper wire | Connection between sensor and expansion board |
| DHT11 |Digital temperature and humidity sensor|
## Computer setup
First thing before connecting to a code editor you might have to update the firmware on your Pycom device. For Instructions on how to do this visit https://docs.pycom.io/updatefirmware/device/. Remember that if you work with MacOS you also might have to give the right permissions to open the firmware update application. More on how to do this here: https://support.apple.com/en-us/HT202491.
This project is built with Visual Studio Code(VSC) on MacOS. To make everything work the following installations have to be made:
- Visual Studio Code
- Node.js
- Pymakr VSCode Extension
I already had VSC and Node.js installed which meant I only had to install the Pymakr extension in VSC. Instructions how to install all these and to connect your pycom device to the computer can be found at: https://docs.pycom.io/gettingstarted/software/vscode/.
After these steps you should have a connection established between your pycom device and VSC via the USB port. It should look similar to this:

*Figure 3. Connected to device on serial port.*
When uploading code to your device you should simply use the "upload" button in VSC. I had a problem with this and got an error message every time I tried. I solved it by pushing the reset button on the LoPy4 and then hitting ctrl+C multiple times until the three arrows(>>>) showed up in the terminal and I was, from here, able to do the upload.
## Putting everything together
To connect the DHT11 sensor to the expansion board I used a breadboard and three different colored wires(figure 4). I also tried to connect the sensor without breadboard and wires(figure 5). That worked fine because the three pins on the sensor fit straight into the expansion board where the GND(ground), 3V3(3.3 Volts) and P23 is located. That is also why I chose P23(you can choose a different one depending on your needs).
However I found that the breadboard was good to have because i could use it to wedge it in place outside, above my window, when I took it out for a testdrive. :grin:
Here is how I connected the wires:
| Wire | Connection |
| --------------|:----------------------- |
| Blue | GND to GND |
| Red | Vin to 3V3 |
| Green | S to P23 |

*Figure 4. Connection using a breadboard*

*Figure 5. Connection without breadboard*
## Platform
Ubidots is a cloud based platform used for sending data from a device to the cloud, this platform can also take care of things like notifications on certain events. Ubidots is also a good option if you want to scale your project.
I tried two platforms, Pybytes and Ubidots, and ended up using Ubidots because the user interface is a lot nicer and easy to use with widgets that fit my intentions. Compared to Pybytes, Ubidots is easier to style and also easy to use for things like notifications on certain events.
To use Ubidots in your project:
- Register on https://ubidots.com/
- Get your Token from API Credentials - default Token and save it for later.
## The code
First you need to set up the right directory(Figure 6). Create a folder and name it ```Ubidots```. Inside this folder create a folder called ```lib``` and three different python files named ```boot.py```, ```keys.py``` and ```main.py```.
Inside the lib folder create two python files named ```dht.py``` and ```urequests.py```.

*Figure 6. Project Directory*
To be able to connect the device to wifi you need to get the wifi name and the wifi password. For security and to make life a bit easier I created a specific file to store this information and named it ```keys.py```.
Paste this code into ```keys.py```:
```python=
WIFI ="Your-wifi-name-here"
PASS = "Your-wifi-password-here"
```
Replace ```Your-wifi-name-here``` and ```Your-wifi-password-here``` in the code block above with your preferable wifi credentials.
To enable communication with the serial port paste this code snippet into ```boot.py```:
```python=
from machine import UART
import machine
import os
uart = UART(0, baudrate=115200)
os.dupterm(uart)
machine.main('main.py')
```
Here is the code block that will take care of communication with Ubidots. Paste it into ```urequests.py```:
```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)
```
To use the **DHT11 sensor** add the following code into your ```dht.py``` file:
```python=
# https://github.com/JurassicPork/DHT_PyCom
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
```
Last code block to add is the one that reads the result from the sensor and sends it to Ubidots using a REST API.
Add this to ```main.py```:
```python=
from network import WLAN
import urequests as requests
import machine
import time¢¢
import keys
from machine import Pin
from lib.dht import DHT # https://github.com/JurassicPork/DHT_PyCom
TOKEN = "YOUR-UBIDOTS-DEFAULT-TOKEN" # TOKEN
DELAY = 3600 # Delay in seconds
wlan = WLAN(mode=WLAN.STA)
wlan.antenna(WLAN.INT_ANT)
# Wi-Fi credentials from keys
wlan.connect(keys.WIFI, auth=(WLAN.WPA2, keys.PASS), timeout=5000)
while not wlan.isconnected ():
machine.idle()
print("Connected to Wifi\n")
# Builds the json to send the request
def build_json(variable1, value1, variable2, value2):
try:
data = {variable1: {"value": value1}, # Temp
variable2: {"value": value2}} # Humidity
return data
except:
return None
# Sends the request. Please reference the REST API reference https://ubidots.com/docs/api/
def post_var(device, value1, value2):
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("temperature", value1, "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
# Read result from pin
th = DHT(Pin('P23', mode=Pin.OPEN_DRAIN), 0) # Pin used by sensor
time.sleep(2)
while True: # While loop if true
result = th.read() # Read result from pin
while not result.is_valid(): # While loop if not valid result
time.sleep(.5) # Sleep 0.5s
result = th.read() # Read result from pin
post_var("pycom", result.temperature, result.humidity) # value1 and value2
print("Temperature", result.temperature)
print("Humidity", result.humidity)
time.sleep(DELAY) # Sleep 1hour
```
Now the last thing to do is to take the Token you got from your Ubidots account and replace ```YOUR-UBIDOTS-DEFAULT-TOKEN``` on line 9 in the above code whith it.
After uploading everything above to your device, disconnect it from your computer and connect a power source, I used the same USB-cable for this. Your device should now send the results and upload them to your Ubidots application once every hour.
## Transmitting the data / connectivity
Data is sent from the device to Ubidots via wifi and through a HTTP-protocol. I chose these because the device will be situated inside a building with a stable wifi and will also be powered by a USB-cable which means I don't have to think about power consumption.
The format that is used to upload data in Ubidots REST API is json. The values sent is determined by line 57 in ```main.py```:
```python=57
post_var("pycom", result.temperature, result.humidity) # value1 and value2
```
This is where the function to send the request initiates.
The data is sent once every hour, which is determined by line 10 in ```main.py```:
```python=10
DELAY = 3600 # Delay in seconds
```
## Presenting the data
Presentation of the data is done in Ubidots application. Here you can go to ```Device``` - ```Pycom``` and there will be a display of your Pyom device showing temperature and humidity. The result is sent every hour to Ubidots and are saved in the database for 24 months. It is also possible do download Ubidots app for Android and Iphone. You can also easily download and save the data from Ubidots. For more information on how Ubidots presentation of data works go to: https://help.ubidots.com/en/articles/854333-ubidots-basics-devices-variables-dashboards-and-alerts.
To share and display the result I used the Dashboard where I created different widgets for temperature and humidity(Figure 7). From the Dashboard settings you can click the share link and you will get a url that will take you, or the people you share it with, to your created Dashboard.
Here is my project Frukteriet:
https://stem.ubidots.com/app/dashboards/public/dashboard/lGq6QHSQFYWnSXqcca-I3VmkuO2Fqq3XS0RHURutmO8?displayTitle=true

*Figure 7. Dashboard with widgets created for this project.*
One thing to mention is that when I was setting up the Dashboard and the widgets, I had to do different things in different browsers. For example, to change the name of the Dashboard. This didn't work in my default browser(Chrome) so I used Firefox instead. And also when I tried to delete one widget it was the other way around and didn't work in Firefox but I was able to do it in Chrome.
## Finalizing the design
The objective has been fulfilled. A device that measures temperature and humidity once an hour has been made. The device sends the data to a cloud which displays the data in an application. Figure 9 shows how the Ubidots android application displays the data.

*Data shown in Ubidots Android application*
As for now the device is sending data from my front porch(Figure 8). As soon as Frukteriet has everything they need for starting fermentation I will install the device in their basement. I will also have to construct some kind of box for the device to sit in.

*Figure 8. My project device*
The project went as planned. I didn't add notifications for when the temperature or humidity goes over or under certain numbers. This is something I can do after consulting Frukteriet on how they want it to be set up. There might also be other things they want to have measured. I believe I now know enough to be able to add sensors to the device and to build other similar devices.