# Measuring temperature and light levels in windows for optimal plant placement
Student name: Edwin Holst
Student username: eh224sx
August 2022
This tutorial presents how to gather sensor data about temperature and light levels, store the data on a cloud, and finally visualize said data for analyzing.
With a basic understanding of microcontrollers and python programming this project should be completable in 2-4 hours, following this tutorial.
## Objective
Recently I’ve started to have more and more plants in my apartment. However, the perfectionist in me gets a little bit uncomfortable when reading up on my different plants and discovering that different plants thrive in different conditions, such as the amount of sunlight. It can be fairly easy, such as some plants thrive in the morning sun and some in the evening sun, however, due to surrounding buildings and trees, it can be hard to judge what windows have more sunlight hours than others.
The objective of this project is to create a sunlight and temperature logging device using a ESP32 microcontroller and storing that data in a cloud, in order to visualize and analyze different locations for placement of plants.
I hope for this project to give me an insight in not only the living conditions of my plants, but also to yield a better insight in IoT and more specifically the ESP32 microcontrollers.
## Material
The materials used in this project are all contained in the Freenove Ultimate Starter Kit for ESP32-WROVER. However, this is a kit with a large amount of components, most not used in this project. Below, you will find both the kit and all the specific components used from the kit listed. Note that some components in the kit are poorly labeled, such as the thermistor and the photoresistor, so I am not sure about the exact type of these components included in the kit.
### The Starter Kit
Name: Freenove Ultimate Starter Kit for ESP32-WROVER
Price: 499.00 SEK or $53.95
Link: [amazon.se](https://www.amazon.se/Freenove-Ultimate-ESP32-WROVER-Included-Compatible/dp/B08FM2NCST/) or [amazon.com](https://www.amazon.com/Freenove-Ultimate-ESP32-WROVER-Included-Compatible/dp/B08FM2NCST/)
| Parts used from the kit | Amount |
| -------- | ---- |
| Freenove ESP32-WROVER-DEV | 1 |
| 10kΩ Resistor | 2 |
| Breadboard | 1 |
| Thermistor | 1 |
| Photoresistor | 1 |
| Jumper wire (male to male) | 2 |
| Jumper wire (male to female) | 5 |
| Micro USB cable | 1 |
#### Specification of the components
A thermistor is a resistance that varies greatly in resistance depending on its temperature.
A photoresistor is much like the thermistor, except it instead varies resistance based on the receiving luminosity or light.
Freenove ESP32-WROVER-DEV is a microcontroller used to gather and send the data from the thermistor and the photoresistor. It is programmable using C/C++ or Micropython, and has connection possibilities with bluetooth and Wi-Fi.
The breadboard and the jumper wires are simply used for a convenient way to connect the different components together, without soldering.
## Computer setup
As mentioned previously, the ESP32 can be programed using either C/C++ or Micropython. Since this project use Micropython on windows, this section only covers Micropython on Windows. However, the Freenove Ultimate Starter Kit for ESP32-WROVER includes a guide usable for other platforms and C/C++ aswell, which can be found [here](https://github.com/Freenove/Freenove_Ultimate_Starter_Kit_for_ESP32).
### IDE
For this project Thonny was used as an IDE. Thonny is a free, open-source software platform with a simple interface, and is recommended by the previously mentioned guide. The main drawback I found using Thonny is the lack of help with coding, however, this doesn't matter too much since the code required for this project is rather simple. For installation, go to (https://thonny.org/) and follow their instructions.
### Drivers
The ESP32 uses CH340 to download codes, so before uploading any codes, we first make sure that we have this driver installed on our computer. Start by connecting the ESP32 to your computer with the micro USB cable. Then navigate to your device manager and expand the Ports tab. If you have a CH340 driver installed on your computer, you should now see a "USB-SERIAL CH340" connected to some COM port. If you have several COM devices and are unsure, you can unplug and replug the device to see which one is the ESP32. In the image below, the ESP32 device is highlighted with yellow on my system.

If the device doesn't appear, try installing the CH340 driver from this [website](http://www.wch-ic.com/search?q=CH340&t=downloads), and retry the previous step.
### Firmware
Inorder to run Python programs on the ESP32, we need to burn the Micropython Firmware to the device. This can be done from the official Micropython [website](https://micropython.org/download/esp32spiram/). Select the latest firmware release and download the .bin file.
To install the firmware on your device you can use Thonny.
Click "Run" and "Select interpreter". A new window will popup. In the new window under "Which interpreter...", select "MicroPython (ESP32)", and under "Port..." select your ESP32 device (the same COM port as found in the Drivers section). Then click on "Install or update firmware". A "ESP32 firmware installer"-window now opens. Select the correct port and at firmware, locate the .bin file you just downloaded. Make sure to check the "Erase flash"-box before clicking install, and your device should be ready for uploading and running code.

### Uploading and Running Code
Using Thonny you can either run scripts directly from the computer or upload files to the ESP32. On startup, the ESP32 will first look for a boot.py file and run its content and then a main.py file. After creating scripts and uploading them to the ESP32, a reboot signal can be sent through Ctrl+D, or F5 to run the current script.
## Putting everything together
The useful information here is the resistance of the photoresistor and the thermistor. Inorder to measure these resistances for each component, we connect them in series with a resistance. If we put a voltage over the resistor and one of the thermistor/photoresistor, then measure the voltage in between, we figure out the resistance by knowing that it is proportional to the resistance of the components compared to the resistor.
We use two pins for reading the voltages, Pin 32 and 33, as these work for the ADC mode. We also connect pin 25 and 26 to provide the voltage. The easiest way to connect this would be using the extension board that comes with the package. However, mine was broken on arrival, so this tutorial will not use the extension board. In the image below, we can see how this setup can be realized on the breadboard.

## Platform
In this project, Ubidots was used as a platform. Ubidots is a low code IoT solution, where you push your data through a rest API. I choose Ubidots because it is free and very easy to set up. It has data collection through a rest API, data post processing through their synthetic variables, and visualization with an easy to setup dashboard.
The free version however comes with a few limitations, such as the number of devices, and the amount of data/day you can store to the cloud. However, I found that these limitations of the free version do not have a great impact on small projects with a single device like this one, and it would also allow for some scaling to a couple of more devices. One thing to be aware of however is that you can only push data every few minutes in order to stay below the data/day limit.
## The code
The code used in this project was created by me with one exception. The first is a module "urequests.py" that was copied from a micropython library on [github.com](https://github.com/micropython/micropython-lib/tree/master/python-ecosys/urequests) under the MIT license. This module was used to handle sockets and requests.
Below you can see the logic of the main loop of the program. Here, we simply gather data at fixed intervals and send it to ubidots rest api. Since there is a limited amount of data that you can send, to get smoother values, I sampled from the sensors every minute and sent the average roughly every tenth minute. Reading the sensor values and sending the data has been moved to their own modules.
```python=
from Ubidots import sendData
from Sensors import readTemp, readPhotoRes
import time
DELAY = 600 # Delay in seconds
N_SAMPLES = 10
while True:
temp_sum = 0
photo_res_sum = 0
for i in range(N_SAMPLES):
temp_sample = readTemp()
while temp_sample is None:
temp_sample = readTemp()
photo_res_sample = readPhotoRes()
while photo_res_sample is None:
photo_res_sample = readPhotoRes()
temp_sum += temp_sample
photo_res_sum += photo_res_sample
time.sleep(DELAY/N_SAMPLES)
returnValue = sendData(temp_sum/N_SAMPLES, photo_res_sum/N_SAMPLES)
print(returnValue)
```
Below you see the code for gathering the data through the functions `readTemp` and `readPhotoRes`.
```python=
from machine import Pin,ADC
import time
import math
def readTemp():
SAMPLES = 10
pin_out = Pin(25, Pin.OUT)
pin_out.on()
adc=ADC(Pin(32))
adc.atten(ADC.ATTN_11DB)
adc.width(ADC.WIDTH_12BIT)
v_sample_sum = 0
for _ in range(SAMPLES):
v_sample_sum += adc.read_uv()
v = v_sample_sum/1000000/SAMPLES
if v < 3.3:
Rt=10*v/(3.3-v)
tempK=(1/(1/(273.15+25)+(math.log(Rt/10))/3950))
tempC=tempK-273.15
return tempC
return None
def readPhotoRes():
SAMPLES = 10
pin_out = Pin(26, Pin.OUT)
pin_out.on()
adc=ADC(Pin(33))
adc.atten(ADC.ATTN_11DB)
adc.width(ADC.WIDTH_12BIT)
v_sample_sum = 0
for _ in range(SAMPLES):
v_sample_sum += adc.read_uv()
v = v_sample_sum/1000000/SAMPLES
if v < 3.3:
r = 10*v/(3.3-v)
return r
return None
```
Both these functions get a voltage reading, and then translates it to the resistance of the respective components. However, when experimenting, the voltage seems to fluctuate quite a lot. Normally this can be fixed by increasing the reading time of the ADC pins, however, this is not available for this microcontroller. Instead, we can just take an average of several readings. This voltage is then translated to resistance by the formula: $$R = 10*V/(3.3-V)$$ Here $10$ is the resistance in series and $3.3$ is the total voltage. For the temperature, we also convert the Resistance to a temperature. This is done on line 21 and 22.
The posting of data to ubidots rest API is done through the sendData function in the Ubidots module.
```python=
import urequests as requests
TOKEN = "BBFF-not_my_actuall_private_tooken"
DEVICE_LABEL = "freenove-esp32-wrover-dev"
TEMP_LABEL = "temp" # Assign the variable label desire to be send
PHOTO_LABEL = "pres"
# Sending data to Ubidots Restful Webserice
def sendData(temp, photo_res):
data = {TEMP_LABEL: {"value": temp}, PHOTO_LABEL: {"value": photo_res}}
try:
url = "https://industrial.api.ubidots.com/"
url = url + "api/v1.6/devices/" + DEVICE_LABEL
headers = {"X-Auth-Token": TOKEN, "Content-Type": "application/json"}
if data is not None:
req = requests.post(url=url, headers=headers, json=data)
return req.json()
else:
pass
except:
pass
```
This function is partially taken from the hackmd tutorial from this course. Here, we construct a request with different labels and a token, as defined by Ubidots API. Notably, the labels are all decided by me when creating ubidots devices on their website. Another neit thing is that python dicts are formatted similarly to JSON objects, so we can use a dictionary as the JSON object without having to convert it. We then send the request using the previously mentioned urequest module.
The complete code can be found on my [github](https://github.com/EdwinHolst/Applied-IoT-LNU).
## Transmitting the data / connectivity
As seen in the code section, data is sent about every 10 minutes. This is done through WiFi. WiFi seemed like a good option, since the device is meant to be used in the home. When it comes to transport protocols, we upload the data using a http post request to ubidots rest API.
## Presenting the data
The dashboard consists of 4 different elements. Two line graphs depicting the raw values sent from the ESP32, that is, the temperature and the photoresistance. From the documentation of the photo resistor, it is unclear how to convert the resistance to lumen. But it can be quite hard to interpret the resistance as a general perception of light level. Therefore we also display a line graph of a synthetic variable, as a function of the photoresistance called "light". This function ranges from 0 to 1 and is inversely proportional to the photo resistance, ending up gaining a higher value from a higher level of light. Lastly there is a thermometer element, simply displaying the current temperature. A snapshot of this dashboard is found below.

Note that I recently made some changes so this data is just from the last few hours.
Since this project uses Ubidots, the data is simply saved every time I send new data, which is about once every ten minutes.
## Finalizing the design
I think the project was a successful one. Some learning has occured, and I succeeded in my goal of creating a device that can gathering data about light and temperature. I have yet to collect data and compare different windows, but this should be easy now with this IoT solution.
There are some future improvements I would like to make. There seems to be some issues with the accuracies of the voltages, or the components. I seem to read temperatures a couple of degrees higher than in reality, so some calibrations using an external thermometer could probably be useful. I would also like a clearer visualization and reference of the light levels. Maybe by making thresholds for direct sunlight, shadow and night could be an option. However to do this, I would need some more testing and data, but it should be easy enough to implement after a full day or two of data collection.
Here are some pictures of the final product sitting in my window gathering data, as an end to this tutorial.

