# 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). | ![](https://i.imgur.com/YdckhPU.png =300x100) | | 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. | ![](https://i.imgur.com/H7bafM5.jpg =300x100) | | 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. | ![](https://i.imgur.com/U3VEAR4.png =300x100) | | 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. | ![](https://i.imgur.com/DjnxdUd.png) | | 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. | ![](https://i.imgur.com/1sgX262.jpg =300x100) | | 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. | ![](https://i.imgur.com/w8Vh8HN.jpg =300x100) | | 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. | ![](https://i.imgur.com/OTJSxCN.png) | | 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 | ![](https://i.imgur.com/5kMe2ee.png) | ## 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. ![](https://i.imgur.com/MrfY22q.png =400x200) There you search for pymakr and install the plugin. ![](https://i.imgur.com/Fe9DWcK.png =800x200) 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 ![](https://i.imgur.com/O8wOo23.png) 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. ![](https://i.imgur.com/j7WeEba.png =300x300) 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 ![](https://i.imgur.com/6Gesxfb.png) A dry plan before and after getting water. ![](https://i.imgur.com/LUTzSKx.png) Change in humidity ![](https://i.imgur.com/IV7vVDO.png) ## 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. ![](https://i.imgur.com/FhTQVye.jpg) ![](https://i.imgur.com/2pfjCuh.jpg) 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!