# Indoor Weather Guard
**Peter Nordlander - pn222nh**

The goal of this project is to build a system for monitoring weather attributes in an indoor environment.
An IoT device will collect temperature, humidity and light intensity from attached sensors and transmit collected values to a cloud platform for storage, presentation.
*Estimated time to complete: ~8h*
## Objective
The project will aim to build a more generic concept for measuring weather attributes for any purpose at home (or other indoor/semi-indoor environment). Temperature, humidity and light intensity will be measured and sent to a cloud platform for preservation and presentation.
Being a house owner myself I know how I worry about humidity and temperatures all the time.
Putting this device in the house's basement may tell you if you need to dehumidify. Or why not in the suspended foundation or up on the attic?
The intensity of light may be used to see if you forgot to turn off the lights, or in the future pass on to another platform (as Google Home) for turning on the lights at sunset.
From a technical perspective insights will be given in how to build a complete IoT solution in a development setting.
Insights will also be given about the health/status of the indoor environment where the device is installed, does the garage manage to keep warm during winter, is it too humid during summer?
## Material
### All items used
| Image|Item | What is it for? |
|-|-------- | -------- |
||LoPy4 with headers | Micropython programmable microcontroller. Will be programmed to read from the sensors and transmit data over WIFI and HTTP to the cloud platform of choice. Supports LoRa, BLE IoT and Sigfox for long range communication but only WIFI will be used in this project. |
||Pycom Expansion board | Allows connecting sensors easier and provides a USB connection for uploading of code and communication with the device. Has a JST connector for connecting batteries and powering the device without the need of a USB cable attached |
||3xAAA Battery holder with JST connector and On/Off switch | Powering device without USB, we may not have a charger or wall socket near the placement of the device |
||Breadboard| For easier connection of our sensors and wiring without the need of soldering. |
|| 8 x Jumper Wires male/male| To connect from the breadboard to the pins on the expansion board |
||DHT11| Temperature and Humidity sensor. Reports current temperature and humidity |
||Photo resistor | Measure light intensity |
| |10kΩ resistor | We need to lower the voltage over the photo resistor |
||3 x AAA batteries | We need to fill the battery holder with batteries |
||Micro USB 2.0 Cable | Communicating with the device and uploading of code from our computer|
**Optional**, if your computer lacks USB-A ports
| Image|Item | What is it for? |
|--|--|--|
||**Optional** USB-A female to USB-C male | In case your computer only has USB-C ports |
### Where to buy them
Electrokit has a bundle that includes almost everything in our list except the **DHT11 sensor** and **battery holder with JST connection**. AAA batteries need to be purchased if you do not have any laying around the house.
|Part | Price | Link |
|---|----|---|
|LoPy4 and sensors bundle|€101.54| [Order at Electrokit](https://www.electrokit.com/produkt/lnu-1dt305-tillampad-iot-lopy4-and-sensors-bundle/) |
|DHT11 Temperature and Humidity sensor|€5.24| [Order at Electrokit](https://www.electrokit.com/produkt/digital-temperatur-och-fuktsensor-dht11/) |
|3xAAA Battery holder with JST connector|€3.10| [Order at Electrokit](https://www.electrokit.com/produkt/batterihallare-3xaaa-med-strombrytare-och-jst-kontakt/) |
|3xAAA Batteries |~€2| At your nearest store |
|USB-A to USB-C adapter |~€15| [Order at Kjell.com](https://www.kjell.com/se/produkter/dator/kablar-adaptrar/usb/usb-kablar/otg-adapter-usb-c-till-usb-02-m-p69161) |
|**Total**|**€111.88 (€126.88 with adapter)**|
## Computer Setup
### Install Visual Studio Code
I've chosen to use **Visual Studio Code** for this project. Visual Studio Code is an open source and free IDE that can be extended with support for many different programming languages.
The first step is to install the IDE itself. Go to [Visual Studio download page](https://code.visualstudio.com/) and download/install Visual Studio Code for your operating system.
### Install Node.js
PyMakr plugin depends on Node.js, so you will have to install that before installing PyMakr.
Go to [Node.js](https://nodejs.org/en/) and download/install Node.js for your platform.
### PyMakr plugin
For MicroPython support, we need to install PyMakr.

Open up Visual Studio Code and press the *Extension button* (Step 1. in image above). Type **Pymakr** in the search input field (Step 2. in the image above).
Pymakr should be found below the input field, press the blue install button to install the plugin (Step 3. in above image).
After installation is done, the install button should have been replaced with a *Reload* button. Press that to reload Visual Studio Code with the plugin you just installed.
The bottom of your Visual Studio Code window should now have a command bar like below:

The added tools and commands allows for controlling an attached Pycom device directly from Visual Studio.
Uploading and synchronizing code written in the editor to the microcontroller is performed by pressing the **Upload** button on the command bar.
### Unpacking the hardware
The expansion board and the LoPy4 comes in separates packages. Before attaching the LoPy4 controller to the expansion board the firmware on the expansion board may need to be updated.
### Flashing the firmware on the Expansion Board
Please follow the steps on [this](https://docs.pycom.io/updatefirmware/expansionboard/) guide carefully, and pay special attension to the version number on your expansion board.
### Flashing the firmware on the Microcontroller
When the firmware on the expansion board has been updated, the LoPy4 microcontroller can be attached to the expansion board. Look at the picture [here](https://docs.pycom.io/gettingstarted/) and esure that you mount the LoPy4 in the right direction.
Plug in the USB cable into the expansion board. Please note that the USB-port on the expansion board may be fragile, so its best to keep the cable attached during development and only unplug in from the computer when not in use (leave it attached to the expansion board).
The final step is to flash the firmware on the LoPy4 as well. Pycom has a tool for this available on all common platforms (OSX, Linux, Windows 10) which makes it alot easier than flashing the firmware on the expansion board.
Download the tool and follow the instruction from Pycoms excellent guide [here](https://docs.pycom.io/updatefirmware/device/)
When done, you are now ready to start developing!
## Putting everything together
Before we start, **make sure that no power source is attached to the expansion board, and unplug the USB-cable!**.
### The breadboard
As this is a development setting, we will mount the sensors and cables on the breadboard. No soldering.
The two bottom and top horizontal lines of connection points are the power rails.

As a difference to the middle contact points, the power rails are inter-connected horizontally. This means that anything connected to a power rail can be tapped from any other contact point on the same row.

The contacts points in the middle of the board are connected **vertically**. Anything connected to given column in the middle section of contact points will be directly in contact with the contact points on the same column.

It is **really important** to understand the connection layout of the breadboard as all our wiring depends on this schema.
### Complete circuit diagram

**Important** Please use different colors for sensor data, +3.3V and GND. You do not have to use the same colors as I've chosen above, the importance is to be concistent.
### Attaching the sensors
The first step is connect the **DHT11 sensor** which has three pins.

GND, VCC (Voltage at the Common Collector, '+') and DATA. You must **always** make sure that you connect to the correct pins (find data sheets from where you bought the sensor). If you have a sensor like mine, in above image the right pin is the GND (ground), the middle is the VCC pin (+) and left most pin is the DATA pin.
Connect a jumper wire from the contact point below the GND pin on the sensor to the **lower** of the two bottom power rails.
Do the same for the VCC pin to the **upper** of the two bottom power rails.
The data pin on the sensor needs to be connected to pin 23 on the Pycom device.
The **photoresistor** is a little bit more complicated as it needs an additional resistor in order for us to measure appropriate voltage levels.
Before mounting the resistor, please verify that the color coding on the resistor translates to 10kΩ (10000 Ohm), there are many calculators and translators on the internet that can help you with this, e.g [this](https://www.allaboutcircuits.com/tools/resistor-color-code-calculator/)
If you have access to a multimeter, measure the resistance over the resistor. This is both the best (in my opinion) and easiest way to measure the actual restistance.
As we want to measure the voltage over the resistor, connect the resistor below the left leg of the photoresistor (having the breadboard in front of you ) but leave a contact point in between. We will need this contact point to connect to **pin 16** on our Pycom device.

The next step is to connect a jumper wire from the contact point below the resistor to the **lower** of the two bottom power rais.
We are almost done, but two steps remain. Connect a jumper wire from the contact point below the right leg to the **upper** of the two bottom power rails and connect a wire from the contact point left above the resistor to **pin 16** on your Pycom device.
You should have end up with something identical to this:

### We need power!
Before doing anything more, please ensure that all the wiring you've done so far is identical to the complete circuit diagram.
Connect a jumper wire from the **3V3** pin on the Pycom device to the **upper** of the two bottom power rails and from the **GND** pin on the Pycom device to the **lower** of the two bottom power rails.
Congratulations, you are done with all the wiring!
## Platform
I've chosen to use [Ubidots](https://ubidots.com/) for this project.
Ubidots is a cloud based platform where you can send, store and present data from your sensors on dashboards, but not the least send notifications (such as e-mails and SMS) in case of occuring events. Ubidots allows for sending your sensor data using HTTP requests over WIFI and is very easy to get started with.
Ubidots has a STEM edition that allows you to use their service for free in case your project is for educational use.
The STEM edition comes with limitations of 30 days storage, 3 free devices and up to 10 variables per device. A full comparison between the two editions can be found [here](https://help.ubidots.com/en/articles/639806-what-is-the-difference-between-ubidots-and-ubidots-stem). But in all, the STEM edition is more than enough for this project in a development setting.
Going forward, an update to a paid account would allow for e.g 24 months of persistent storage, less limitation on data processing, wider range of notification methods and ability to create larger (with more widgets) dashboards. This is one of the reasons behind the choice of platform, we want to keep the code base as intact as possible and just upgrade to another license on the cloud platform in case we scale our project.
In order to follow along this tutorial, please go to Ubidots and sign up for a STEM account [here](https://industrial.ubidots.com/accounts/signup_industrial/).
When we later configure the code base, you will also need your **access token** in order to identify your account when your device is sending data to the platform. Follow the guide [here](https://help.ubidots.com/en/articles/590078-find-your-token-from-your-ubidots-account
) to obtain your token. Write it down, you will need it later.
## The code
### Senap
During the learning process I decided to build a tiny framework that could be used for many IoT-applications (before I decided on this current project, which sensors I would use, choice of platforms etc)
This very small framework got its working name **Senap** (**SEN**sors**A**nd**P**latforms, but also the swedish translation of Mustard. Wasn't at all intentional, I promise...).
In general, the framwork is built upon two concepts. *Sensor*, base class that provides a generic interface and basic functionality to a sensor attached to a given pin on the Pycom device. And *Platform*, base class with a generic interface that allows for setup and transmission of a dictionary of variables to a given platform.
Sensors have variables that they write to a dictionary upon request. Platforms receives a dictionary of variables and its sole responsibility is to transmit those to a destination (e.g a cloud platform or a local server with a TIG-stack).
Each sensor must be given a unique Id. And the framework will automatically mangle/append the sensor's variable names with its unique Id.
This allows for two sensor instances (with id 1 and 2) sharing the variable name "Temperature" to be distinguished as their reported variables will be "Temperature_S1" and "Temperature_S2".
The sensor implementation simply registerers its variable names and receives the mangled variable name back, which it later use to populate the global dictionary of variables.
Below is the DHT sensor registering its variables in the constructor.
```python=
DHT_TEMPERATURE_VAR_NAME = 'Temperature'
DHT_HUMIDITY_VAR_NAME = 'Humidity'
# LDR Sensor class
class SensorDHT(Sensor):
from dth import DTH # https://github.com/JurassicPork/DHT_PyCom/tree/pulses_get
def __init__(self, pin, sensor_id):
Sensor.__init__(self, pin, sensor_id)
self.th = DTH(pin, TH_SENSOR_DHT111)
self.humidity_var = self._register_var(DHT_HUMIDITY_VAR_NAME)
self.temperature_var = self._register_var(DHT_TEMPERATURE_VAR_NAME)
```
Registration is handled by the Sensor base class:
```python=
# Register variable, automatically append sensor-id and return variable index
def _register_var(self, name):
# Mangle name, append and return
varname = name + SENSOR_INDEX_PREFIX + str(self.sensor_id)
self.vars.append(varname)
return varname
````
All code is available at [here](https://github.com/xorself/iot-senap/). Feel free to use however you like!
## Putting all the code together
If you're not famililar with Visual Studio Code, please go through this introduction [here](https://code.visualstudio.com/docs/introvideos/basics) as I will assume you know how to create an empty project, new files and directories.
### Create new project and lib folder
The first step is to create a new project in Visual Studio Code. You can name the project anything you like but I will use the name "Hygrometer" as an example in this tutorial.
We also need to create a **lib** folder in the root of our project. This is where all external code (such as Senap and other libraries ) must be put.

### Copy third party dependencies into lib
The DHT11 Sensor and Ubidots platform implementation in Senap require two external dependencies to be downloaded and copied into your project.
The Ubidots platform implementation in Senap requires `urequests.py`.
Download or clone `urequests.py` from Jose Reyes Garcia Delgado's Github [here](https://github.com/jotathebest/micropython-lib/blob/master/urequests/urequests.py) and put in in your project's **lib** folder.
Github user JurassicPork provides an MIT licensed library for reading from a DHT11/DHT22 sensor. Download `dht.py` from [here](https://github.com/JurassicPork/DHT_PyCom/tree/pulses_get) and put in in the **lib** folder of your project.
For both these, make sure to **carefully** read the latest license terms and act accordingly (e.g if you must redistribute the license terms).

### Copy and configure Senap into lib
Download/Clone Senap from [here](https://github.com/xorself/iot-senap/) and copy all python source files to the **lib** folder.

As we will use Senap for both setting up our WIFI connection and connecting to Ubidots. You will need to modify `senap_auth.py` with your credentials.
```python=
AUTH_WIFI_SSID = '<YOUR_WIFI_SSID>'
AUTH_WIFI_PWD = '<YOUR_WIFI_PASSWORD>'
AUTH_UBIDOTS_ACCESS_TOKEN = '<YOUR_UBIDOTS_ACCESS_TOKEN>'
AUTH_DEVICE_NAME = "<YOUR_DEVICE_NAME>"
```
Enter the SSID and password of your WIFI network. AUTH_UBIDOTS_ACCESS_TOKEN must be assigned the *access token* you earlier obtained during the Ubidots account sign up process.
You can name your device anyting, but I recommend to give it an explanatory name so you can easily identify it in the Ubidots platform portal.
### The application itself
Finally, we will develop the application itself. The application will concist of three files.
* `main.py`
* `boot.py`
* `config.py`
Create these three files in the root folder of your project (one level above the *lib* folder).

#### `boot.py`
`boot.py` is the first file executed when your program is run on the device.
```python=
from lib.senap_wifi import wifi_enable
import pycom
import config
pycom.heartbeat(False)
pycom.rgbled(config.CFG_LED_BOOT)
wifi_enable(config.CFG_WIFI_CONNECT_TIMEOUT)
```
Copy everyting above into your empty `boot.py`file.
Besides turning the device's heart beat off, we enable WIFI using the WIFI module from Senap.
#### `Main.py`
`main.py` Contains all core application logic. Instantiates SensorLDR, SensorDHT and PlatformUbidots. The main loop of the application does not need any future modifications if we decide to add new sensors or platforms. Adding a new sensor is simply a one line operation, same yields for another platform.
```python=
from lib.senap_platforms import PlatformUbidots
from lib.senap_sensors import SensorDHT
from lib.senap_sensors import SensorLDR
from lib.senap_wifi import wifi_is_connected
import pycom
import machine
import time
import config
# Dictionary of sensor data
sensor_data = {}
all_sensors = []
all_platforms = []
# List of all sensor's variables
sensor_var_list = []
# Append LDR and DHT sensor
all_sensors.append(SensorDHT('P23', 1))
all_sensors.append(SensorLDR('P16', 2))
# Add ubidots platform
all_platforms.append(PlatformUbidots())
# Fetch all sensor's variables. They are given as context to the platforms
for sensor in all_sensors:
sensor_var_list = sensor_var_list + sensor.get_vars()
# Allow all platforms to prepare, pass the variable list in case any platform needs it
for platform in all_platforms:
platform.init_with_context(sensor_var_list)
pycom.rgbled(config.CFG_LED_OK)
# Main loop
while True:
# Reset device in case we lost WIFI
if not wifi_is_connected():
machine.reset()
# New dictionary each cycle, populated by sensors
sensor_data = {}
read_result = True
# Iterate all sensors and read their current values
for sensor in all_sensors:
read_result = read_result and sensor.read(config.CFG_SENSOR_MAX_ATTEMPTS,
config.CFG_SENSOR_READ_RETRY_INTERVAL, sensor_data)
# Iterate all registered platforms and transmit data from sensors
for platform in all_platforms:
platform.transmit(sensor_data)
# Update status led on device
if read_result:
pycom.rgbled(config.CFG_LED_OK)
else:
pycom.rgbled(config.CFG_LED_ALARM)
# Sleep until next cycle
time.sleep(config.CFG_SENSOR_TRANSMISSION_INTERVAL)
```
Copy above into your empty `main.py`file. I've added comments in order to describe in detail what each part does. The part that collect all sensor's variables and pass it to the platform (line 26-32) is a generic approach and is actually not needed for Ubidots. I've added that as a mandatory step in order to support other platforms that may need to be aware of the context at the initialization step. A Pybytes implementation may e.g use this step to bind variable names to channels.
But in general, the application populates a list of sensors and a list of platforms. For each cycle, the sensors are read and passed to all platforms.
If wifi is lost, the device is reset in order to invoke the connection process again (in `boot.py`)
The results are logically ANDed for each sensor (line 50). This way we know when to trigger an alarm as the result will be **False** if *any* sensor reports a negative result.
#### `config.py`
Copy all below content to your empty `config.py` file. I've chosen to make the application throughout configurable. All constants in `config.py` may be modified to your liking (Comments above each constant should be self explanatory).
```python=
# LED light on Lopy on alarm, such as a failing sensor
CFG_LED_ALARM = 0xff0000
# LED light on Lopy during proper function
CFG_LED_OK = 0x000000
# LED light on Lopy during during boot
CFG_LED_BOOT = 0x007f00
# Number of errornous readings in a row to trigger a warning
CFG_SENSOR_MAX_ATTEMPTS = 5
# Interval, in seconds, to fetch sensor data and upload to platforms
CFG_SENSOR_TRANSMISSION_INTERVAL = 1800.0
# Interval, in seconds, to attempt to retry fetch sensor data in case failure
CFG_SENSOR_READ_RETRY_INTERVAL = 0.5
# Connect timeout for WIFI connection attempts
CFG_WIFI_CONNECT_TIMEOUT = 5000
```
### Upload your project
Connect the device to your computer, wait until its successfully connected and then press the **Upload** button in Visual Studio Code to upload the project to your device.

If everything is working correctly, you should see somthing similar to this in your terminal (with your SSID).

Note that all sensors report their Status variable during the first cycle but not in succeeding cycles. They will only be reported when they change (failed to OK or vice versa).

## Transmitting the data / connectivity
Sensor data is transmitted every 30 minute (1800 seconds inbetween each cycle) over WIFI and HTTP (Webhooks) to the Ubidots platform.
PlatformUbidots class from Senap converts the passed dictionary of sensor variables to JSON format before sending the POST request to Ubidots.
```python=
# Sends the request. Please reference the REST API reference https://ubidots.com/docs/api/
def transmit(self, values):
try:
url = UBIDOTS_API_URL + senap_auth.AUTH_DEVICE_NAME
print("Post to " + url)
headers = {"X-Auth-Token": senap_auth.AUTH_UBIDOTS_ACCESS_TOKEN, "Content-Type": "application/json"}
# Build JSON
data = self.build_json_from_dict(values)
# No need to send in case no variables exist...
if data is not None:
print(data)
req = requests.post(url=url, headers=headers, json=data)
return req.json()
else:
pass
except:
pass
```
The URL is appended with the device name (AUTH_DEVICE_NAME in `senap_auth.py`) and the "X-Auth-Token" is set in the headers of the request to identify your account at Ubidots.
## Presenting the data
When your device has started to transmit sensor data to Ubidots the data is preserved in the Ubidots database for 30 days (STEM edition).
All variables sent may be presented in a dashboard of widgets in your Ubidots account portal.
Building a dashboard in Ubidots is simple, and you can have up to 3 dashboards with 10 widgets in each (STEM edition).
When logged in to your account, you select Data and Dashboard.

In general, you add widgets to your dashboard and bind them to the variables that you have sent to the platform.
Below, I've chosen to present humidity and light intentity as line charts (As I'm using 12 bit for converting signals from the LDR sensor I will be getting values from 0 to 4096).
Temperature is presented as a *Thermometer widget* and a *Gauge widget* presents the last read humidity (ranging 0-100).

In order to present the sensor status, I've chosen to add two *Indicator widgets* that present the last known status of each sensor (variables status_s1 and status_s2 automatically sent by Senaps core Sensor class).
The widgets are set to green when the status is OK (reported as 1) and red when reported as failed (reported as 0). This is done by adding a so called *Color Logic* to the widget where values of the variable can be bound to certain colors or color ranges.

## Finalizing the design

As a final step, put batteries in your battery holder, unplug the usb cable and connect the JST connection to the expansion board.
The project went very well and I must say that I've had a lot of fun on the way! My original plan was to take the project to a level beyond development (including designing and building a case), so this is a natural step going forward.
As I settled on a smaller project than I originally planned but still kept most of the framework I built early the code may seem a bit over engineered for this small project. But on the positive side, this allows me to extend the project in future - and quickly prototype new applications and devices.