# Tutorial on how to build a temperature and humidity sensor
[ToC]
###### tags: `Tag(Temperature, Humidity, Lopy4, Pycom)`
> This is my tutorial on how to create a temperature and humidity censor using Pycom.
> My name is Jesper Olausson and my student credentials are: ==jo222yr==
- [ ] Title
- [ ] Name and Student credentials
## :memo: Background
I've been studying from home for about a year now and during this hot early summer I relaised that my office has gotten pretty unbearable to stay on for longer periods of time. Therefore I wanted to figure out why by measuring my humidity rates and temperature. If you also want to do this it shouldn't take longer than ==about 6 hours== as a newbie. ~~Atleast if you don't run in to any troubles like I did.~~ Considering the results of my measures I've realised that I during the summer should move my office-setup to another room when an extremly warm week is forcasted.
- [ ] Short project overview
- [ ] How much time it might take to do (approximation)
- [ ] Why I choose this project
- [ ] What purpose does it serve
- [ ] What insights you think it will give
---
### :wrench: Material
- [ ] List of material
- [ ] What the different things (sensors, wires, controllers) do - short specifications
- [ ] Where you bought them and how much they cost
| IoT equipment | Purpose | Where to buy |
| ----------------- |:----------------------- |:----------------------- |
| Pycom Extensionboard 3.1|Use to power up Lopy4, Connect to your computer and, if needed, connect to battery pack to make it run portably |https://www.m.nu/pycom/expansion-board-31 199 Kr
| Lopy4 - With Headers | Great IoT platform to connect via multiple communication chanels including: LoRa, Sigfox, WiFi and Bluetooth. | https://pycom.io/product/lopy4/ €34.95 |
| Cables |Used to connect Expansionboard to DHT11 Sensor | https://www.electrokit.com/produkt/labsladd-1-pin-hane-hona-150mm-10-pack/ 29 Kr|
| USB-Cable 2.0 A- 2.0 Micro B |Connects the Expansionboard and Lopy4 to the computer.| You get this anywhere of the internet for 39 Kr, or maybe you already have one at home.
| DHT11 Sensor |Measures humidity and degrees when connected to the expansionboard and with the right code| https://www.kjell.com/se/produkter/el-verktyg/arduino/tillbehor/temperatur-och-luftfuktighetssensor-for-arduino-p87086?gclid=CjwKCAjwxev3BRBBEiwAiB_PWPXrYrrq0zrbEfEK6RXjTnFlivTNSyVUhePE_krIjCHsmKkHE_3NmRoCe-MQAvD_BwE&gclsrc=aw.ds 99.90 Kr |
 *1. Pycom Expansion Board 3.1*
 *2. Lopy4 - With Headers*
 *3. Cables*
 *4. USB-Cable 2.0 A- 2.0 Micro B*
 *5. DHT11 Sensor*
### :desktop_computer: Computer setup
- [ ] Chosen IDE
- [ ] How the code is uploaded
#### IDE
For this project I've used Atom with the extensions Python and Pymakr. Atom is a great tool for beginners with a lot of extensions available to help make the programming experience more fun, and also more practial!
The code is uploaded via the Pymakr console to the Pycom device. The device then runs the code to do various things (for my example measure temperature and humidity).
## Steps to set up what you need to start coding
- [ ] Steps that you needed to do for your computer. Installation of Node.js, extra drivers, etc.
1. Download Atom
2. Download and install node.js
3. Download the REPL console Pymakr
4. Download and install Python to your computer
5. Set up an account at Pycoms website
6. Download Pycom Firmware updater
7. Connect Lopy4 to your expansionboard (make sure the led is at the same side as usb-port)
8. Connect USB-cable to the computer and expansionboard
9. Check devicemanager to make sure that your device is connected to your computer
11. Follow instructions to connect the device to Pybytes (available at pycoms website)
12. Make sure that your device is connected
13. Start Atom and connect the device there by checking the Pymakr Console
14. And you are done! You have succesfully connected the Expansionboard and Lopy4 to your computer.
---
### Connecting the DHT11 Sensor
With this rough sketch you can see how the wiring is connected with the blue side of the DHT11 sensor facing towards you. The orange cable is connected between GND and the third pin of the sensor. Ground is a reference point for all signals or a common path in an electrical circit where all the voltages can be measured from. The red cable is the voltage cable, connected from 3V3 and the center pin of the sensor, 3v3 is 3.3 Volts. The final brown cable is connected between P23 and the first pin of the sensor. This is used to communicate with the expansionboard, it could be any other P as well but this one is closest. Remember this pin for programming purposes later on.
- [ ] Circuit diagram (can be hand drawn)
 *Circuit diagram*
---
### The code
The following code is the main.py. Here we first import time, then the pincode from the machine and finally the DHT from another document stored in a lib folder which we'll get into soon. After this we create a variable with the P23 in it and set mode to open drain and chose which DHT device we have, 0 for DHT11 and 1 for DHT22 (why we can do this will be explained in the next code snippet). While True the result is read via the th.read method and collected in variable result. Then we print the Temp and RH along with the result of said measurements. Finally we send the signals to the Pybytes dashboard här kan skapa visuella diagram för datan som samlas in samt använda sig av exempelvis LoRa-nätverket iställlet för wifi om det skulle behövas. When all is done the function is set to sleep 900 seconds or 15 minutes since ==we don't need to collect data more than that and therefore save data.== For this project data isn't that important since we're using wifi but with order methods like LoRa every bit of data is important to minimalize. As a transport protocol I used MQTT.
- [ ] How often is the data sent?
- [ ] Which wireless protocols did you use (WiFi, LoRa, etc …)
- [ ] Which transport protocols were used (MQTT, webhook, etc …)
- [ ] Describe platform in terms of functionality
###### Data is sent to Pybytes. Needs to flashed with Pybyte firmware
```python=
import time
from machine import Pin
from dht import DHT
###### Type 0 = dht11
###### Type 1 = dht22
th = DHT(Pin('P23', mode=Pin.OPEN_DRAIN), 0)
time.sleep(2)
while True:
result = th.read()
while not result.is_valid():
time.sleep(5)
result = th.read()
print('Temp:', result.temperature)
print('RH:', result.humidity)
pybytes.send_signal(1,result.temperature)
pybytes.send_signal(2,result.humidity)
time.sleep(900)
```
---
This is the dht.py document placed in the lib folder. This file is basically to make the sensor available to use in the main.py file. It also makes it possible to use other DHT devices such as the DHT22.
```python=
import time
import pycom
from machine import enable_irq, disable_irq, Pin
class DTHResult:
'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 DTH:
'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 DTHResult(DTHResult.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 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 __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
```
---
Finally we have the dht.py file where the class DHT is created which you refer to in the main.py file. Here you basically read the data collected from the sensor and convert it into a readable result.
```python=
import time
from machine import enable_irq, disable_irq
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
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 DHTResult(DHTResult.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 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 __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 # noqa
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
```
## Presenting the data any my final design
- [ ] Provide visual examples on how the dashboard looks. Pictures needed.
- [ ] How often is data saved in the database.
- [ ] Show final results of the project
- [ ] Pictures
This is the result of my dashboard. It shows my temperature and humidity. you can also enter a more detailed view with the edit option on pybytes dashboards. And as explained earlier the data is saved every 15 minutes into the database. The reason I used Pybytes for this project is that it's a simple way to easily describe how to work with collecting data and presenting it.
 *Dashboard.*
As a final note I think this project is a really great way of learning how the process works with IoT. Now that I've learned this I feel ready to use other sensors, connect them and use more advanced dashboards to create a clear view of what I'm trying to measure.
---