# Automatic IoT garden irrigation system using the LoPy4 and Sigfox
---
Author
Mikael Sjöberg, mw225wh
---
## Introduction
This tutorial aims to show how you can utilize the LoPy4 to create an automated irrigation system for your garden, with IoT! The data acquired is sent via Sigfox to be displayed on Ubidots.
This project will take approximately 10-20 hours to complete. It can take more time depending if you 3D-print the parts or you have to troubleshoot the connection for Sigfox.
### Objective
The objective of this project was to create an automated irrigation system for my small kitchen garden (I forget to water, a lot). I also need to see how much direct sunlight some of my plants get as they thrive in the shade, requiring me to cover them. The problem with store bought solutions for irrigation is that they require a connection to a wall outlet for power, water from a pressurized watering system and a wired connection or Wi-Fi for those with IoT solutions.
My kitchen garden has neither of those, requiring an off-grid solution. This project can therefore be utilized by anyone. I hope that this inspires more people that you can save money and learn a new hobby at the same time!
### Material
Down below are the list of materials. All parts were bought at Swedish stores but are available at both UK and US amazon store. The filament came with my Flashforge Finder 3D-printer, but any PLA-filament will do.
Many parts are 3D-printed, though equivalent parts can be made from plastic tupperware containers and plastic bottles. Also, the material marked optional is more suited for a non 3D-printed solution.
| Component | Link | Price
| -------- | -------- | -------- |
| LoPy4 | [Link](https://pycom.io/product/lopy4/) | 366 SEK |
| Expansion Board 3.0 | [Link](https://pycom.io/product/expansion-board-3-0/) | 167 SEK |
| Capacitive moisture sensor | [Link](https://www.electrokit.com/produkt/jordfuktighetssensor-kapacitiv-analog/) | 89 SEK |
| Photoresistor | [Link](https://www.electrokit.com/produkt/fotomotstand-cds-2-5-kohm/) | 8 SEK |
| HS-755HB Servo | [Link](https://www.electrokit.com/produkt/servo-hs-755hb/) | 289 SEK |
| Breadboard | [Link](https://www.electrokit.com/produkt/kopplingsdack-170-anslutningar/) | 33 SEK |
| 4 x 10K Ohm resistors| [Link](https://www.electrokit.com/produkt/motstandssats-10k-82k-x25-300-st/) | 49 SEK |
| DELTACO Powerbank 15600 mAh | [Link](https://www.inet.se/produkt/8501478/deltaco-powerbank-15600-mah-svart) | 299 SEK |
| 1/2" Ball valve| [Link](https://www.bauhaus.se/kulventil-1-2-25bar) | 80 SEK |
| 2 x Micro-USB| [Link](https://www.teknikdelar.se/mobiltillbehor/htc/one-m8/kablar-laddare/deltaco-micro-usb-kabel-1-m-svart/) | 2 x 29 SEK |
| Water barrel| [Link](https://www.jula.se/catalog/bygg-och-farg/forvaring/forvaringshyllor-lador/forvaringslador-korgar/forvaringstunna-662069/?fbclid=IwAR0TW0jpSI1dt5qiWz2nILS20qWA1F62jDoQcvPGCMl7bISpocFCM4au28Y#tab04) | 169 SEK |
| 1/2" Soaker hose| [Link](https://www.jula.se/catalog/tradgard/bevattning-och-dranering/bevattning/tradgardsslangar/fuktslang-007690/?fbclid=IwAR2JNFJD5rMgoBeHezxqf27mwz7b6C8E4Ib8A44aQ12Oj5dRWLRh77ixTC4#tab04) | 149 SEK |
|
| | **Total** | **1756 SEK** |
The powerbank is the one I used, but any should do. The same goes for the servo, ball valve, water barrel and soaker hose, any with equivelent characteristics should make due.
The photoresistor and moisture sensor are the main sensors in the build, which lets us get the relative value of light and soil moisture.
The servo will actuate the ball valve. Though the servo and ball valve can be replaced by a [magnetic valve](https://www.electrokit.com/produkt/magnetventil-12v-1-2/) and a [step-up converter](https://www.electrokit.com/produkt/dc-dc-omvandlare-step-up-1-25-35v-3a/) (which were out of stock when doing this project).
### Computer setup
Word of warning, the USB connector on the expansion board is easily broken and can be hard to resolder. I recommend that you disconnect the USB cable from the computer side and not the expansion board when it is needed.
First of all you have to update the firmware of your expansion board. The tutorial for that can be found [here](https://docs.pycom.io/pytrackpysense/installation/firmware/). I did this part on an Ubuntu machine as it is easier than on Windows, but it should work fine with any Windows OS above 7. If you are using Windows 7 you will have to [manually install drivers](https://docs.pycom.io/gettingstarted/installation/drivers/).
When the expansion board firmware is updated, disconnect the expansion board form your computer and insert the LoPy4 into its slots. Also, connect the antenna in the slot intended for Sigfox communication.


You risk damaging the device if you try to send data via Sigfox without the antenna attached.
Now we want to update the firmware of the LoPy 4. This is done with the Pycom firmware update tool found [here](https://docs.pycom.io/gettingstarted/installation/firmwaretool/). After installation is completed, connect the USB-cable to your computer again. Start the updater and press continue until you reach the same point as in the figure below.

We will leave "Force update Pybytes registration" unchecked for now, we will return to this later. Press continue.
At this step we can choose which version of the firmware we want, the latest stable version will be pre-selected. We will check "Erase during update". Erase here is the only way to erase files on the device, when you upload your data on this device it cannot be deleted any other way. If you choose to modify the code below you will probably get acquainted with this tool. Press continue

When it is finished your LoPy 4 is up to date with the latest firmware. Save the data displayed on the last window of the firmware updater, this will be used later on in the tutorial. The data you are saving are unique to your device and should not be shared. These will remain the same every time you update the firmware on your device.

I am using the Atom IDE for coding in MicroPython (based on Python 3). To use Atom with your Pycom board you need to first download the [program](https://atom.io/) and install it. Once installed, you will need to install the pymakr package to be able to interact and upload code to your device.
Open atom, navigate to Settings, Install and search for “pymakr”, press install.

When it is installed you should get a command window at the bottom of the program. If not, try to restart Atom.
Press the reset button on your LoPy 4, it’s the button beside the antenna you plugged in before. If you are unsure, you can see it on the LoPy 4’s [pinout diagram](https://docs.pycom.io/gitbook/assets/lopy4-pinout.pdf.).
Now you should see the slide turn green in the command window. It should look something like this.

Try writing 1+1, if it returns 2 your device have successfully run your first command! If the slider has not turned green, try pressing it.
If Atom still cannot connect to the device, you need to install JavaScript on your computer. This is usually the case for Windows computers.
Close Atom, download and install [Node.js](https://nodejs.org/en/). After installation, try running Atom once again. if you still cannot run the command window, try updating the firmware on the LoPy 4 again.
**Note**: you cannot update the firmware at the same time as you are connected to the device via atom, you need to shut of the connection by pressing the slider so that it is red before you can update.
### Putting everything together

The ADC input pins can by default only read input signals between 0-1.1V. With attenuation in the code we can increase this to 0-3.3V. When testing the device I found that the device maxed out at 3110 mV. Therefore we need to have an input signal < 3110mV. The sensors input voltage is 5V and their output is routed through two separete voltage dividers of four 10k Ohm resistors, effectively reducing the maximum input voltage to 2.5V.
If you dont have these specific resistors you can use this [website](http://www.ohmslawcalculator.com/voltage-divider-calculator) to calculate which resistors you need.
**Note**: The voltage divider should consist of at least 10K Ohm resistors. This is because a low Ohm voltage divider has a high impedance output, this in turn activates a protective resistance built in the LoPy 4. This will bring the input voltage to an unusable level. More on this can be read [here](https://electronics.stackexchange.com/questions/74299/voltage-divider-not-functioning-as-expected).
The servo is supplied directly from the powerbank. Powering it directly from the microcontroller can damage it. It can also lead to unrealiable actuation.
To supply directly, cut the micro-USB head from one of your cables and strip of a small part of the red (+) and black (-) wire. The other two wires are for data, which can be ignored.

Solder or use electrical tape to attach these to pin connectors so that you can attach these to the breadboard.
The sensors are supplied 5V directly from the LoPy 4. When checking the output directly from the microcontroller and from the powerbank I found that the former produced less noise, resulting in more stable sensor output values.
The electrical wiring is good for development purposes. If one were to make this a product I would highly suggest making a custom PCB and soldering everything together.
### Platform
When looking at which platform I should use I broke down my requirements as such:
* Nice design for presenting the data.
* Free/ low cost.
* Sigfox compatible.
* Can retrieve at least 140 messages per day (message limit for Sigfox, will be explained later).
I looked at Adafruit IO as it seems like one of the more "open" platforms with less restrictions. But I did not like the presentation of the data as it felt to business-like.
The other platform was Ubidots, it has a great design, is free and had tutorials on how to connect Sigfox to it.
A restriction with Ubidots is that you are limited to sending 4000 "dots" per day. A dot is a point of data. In this tutorial we are sending four different points of data with every message(sent every 15 minutes). With 96 messages per day, the total number of dots per day is 384, well within the limit. An added bonus is that Ubidots is a cloud based service, so accessing your data "on the move" is easy.
The tutorials on how to connect Sigfox and Ubidots are not applicable to the LoPy 4 as of writing this (8/7-2020). So I will explain here.
Go to the Ubidots [website](https://industrial.ubidots.com/accounts/signup_industrial/) and sign up for "Educational or Personal Use". Once signed up, log in to the website.
Navigate to your user profile in the top right hand corner and press “My Profile”, then go to “API Credentials”. Press the plus sign to add a new token, then copy that token by pressing the copy button beside it.


This will be used so that Sigfox knows where to send the data. Save the token in a text document as we will be using this later on.
### The code
**Note**: The following code will NOT work until you have finished the rest of the tutorial. Return to this part when you have finished the electrical wiring and Sigfox/Ubidots setup.
Before running the main code you need to do some calibration. First go to the [Pycom documentation](https://docs.pycom.io/firmwareapi/pycom/machine/adc/) and calibrate the reference voltage. This can only be done if you have a multimeter. You can skip this part, but it can result in sensor data which is slightly off from reality.
Run the following code **once** to calibrate your sensors.
```python=
# Required libraries
from machine import ADC
import machine
import time
# Pin setup
adc = ADC(0)
adc_c = adc.channel(pin='P17', attn = machine.ADC.ATTN_11DB)
adc.vref(1100) #Remember to check reference voltage as per documentation "Pycom ADC",
#in this case it was the same as the basic value adc_c()
adc_d = adc.channel(pin='P13', attn = machine.ADC.ATTN_11DB)
input('Moisture sensor calibration \n---------------------------')
input('Hold sensor in air, dry off if wet\nPress enter when ready: ')
sensorValSum_moist = 0
for i in range(0, 100):
sensorValSum_moist += adc_d.value()
sensorValMedian_moist = sensorValSum_moist/100
input('Hold sensor in water\nPress enter when ready: ')
sensorValSumWater = 0
for i in range(0, 100):
sensorValSumWater += adc_d.value()
sensorValWater = int(sensorValSumWater/100)
print('\nPhotoresistor calibration')
input('Put finger over photoresistor\nPress enter when ready: ')
photoResSumDark = 0
for i in range(0, 100):
photoResSumDark += adc_c.value()
photoResValDark = int(photoResSumDark/100)
input('Hold lightsource against photoresistor\nPress enter when ready: ')
photoResSumLight = 0
for i in range(0, 100):
photoResSumLight += adc_c.value()
photoResValLight = int(photoResSumDark/100)
print('Calibration finished\nSensor values\n-------------')
print(('sensorValMoisture_Air: ' + str(sensorValAir)) + '\nsensorValMoisture_Water: ' + str(sensorValWater))
print(('sensorValPhotoRes_Dark: ' + str(photoResValDark)) + '\nsensorValPhotoRes_Light: ' + str(photoResValLight))
print('Insert these values in "main.py"')
```
Save the code below as "boot.py" and "main.py" respectively. These will be run continuously.
```python=
# Required libraries
from machine import UART
import machine
import os
# Setup for communication (Sigfox)
uart = UART(0, baudrate=115200)
os.dupterm(uart)
machine.main('main.py')
```
```python=
#Required libraries
from machine import ADC
import machine
import time
from machine import Pin
from network import Sigfox
import socket
import struct
from machine import PWM
from time import sleep
# Sigfox setup
sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=Sigfox.RCZ1)
s = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW)
s.setblocking(True)
s.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False)
# GPIO pin setup
# Use GPIO pins WITH pullup/pulldown internal resistance, check pinout in documentation
# Following pins are pull down enabled, will register 0/1 (LOW/HIGH)
pinWaterTank_10 = Pin('P10', mode=Pin.IN, pull=Pin.PULL_DOWN)
pinWaterTank_30 = Pin('P9', mode=Pin.IN, pull=Pin.PULL_DOWN)
pinWaterTank_50 = Pin('P8', mode=Pin.IN, pull=Pin.PULL_DOWN)
pinWaterTank_70 = Pin('P7', mode=Pin.IN, pull=Pin.PULL_DOWN)
# Output pin supplying the watertank with current ONLY when checking water level
pinWaterTank_OUTPUT = Pin('P6', mode=Pin.OUT)
# PWM setup, for more about which frequency you should use look at PWM documentation
pwm = PWM(0,frequency=50)
# ADC input setup for the analog sensors, attenuated to read 0-3.3V (11DB)
adc = ADC(0)
adc_moisture = adc.channel(pin='P13', attn = machine.ADC.ATTN_11DB)
adc_photoRes = adc.channel(pin='P17', attn = machine.ADC.ATTN_11DB)
adc.vref(1100) #Remember to check reference voltage as per ADC documentation,
#in this case it was the same as the factory set value.
#Values retrieved from sensor calibration
sensorValMoisture_Air = 2080
sensorValMoisture_Water = 1500
sensorValPhotoRes_Dark = 2220
sensorValPhotoRes_Light = 2800
sensorSpanPhotoRes = sensorValPhotoRes_Light - sensorValPhotoRes_Dark
sensorSpanMoisture = sensorValMoisture_Air - sensorValMoisture_Water
#Global values which are saved outside specifik functions
waterTankLevel = 0
consumedWater = 0
#############################
#IN: String (sensor type)
#OUT: Mapped sensor value (0-100)
#DO: Calls upon medianSensVal() to get the median, raw value from the called sensor sensorType.
# Then it sends that data and corresponding sensor type to mapping().
#############################
def sensorReading(sensorType):
sensorVal = medianSensVal(sensorType)
if str(sensorType) == 'photoRes':
sensor = 'photoRes'
else:
sensor = 'moisture'
mappedSensorVal = mapping(sensorVal, sensor)
return mappedSensorVal
#############################
#IN: Median value of sensor data and which type of sensor it is
#OUT: Mapped sensor value (0-100)
#DO: Checks if the sensor data is within pre-set boundaries established in "sensorCalibration.py"
# If below, returns 0. If above, returns 100. If it is in between it calculates the percentage value.
#############################
def mapping(inputSensVal, sensorType):
if str(sensorType) == 'photoRes':
sensorVal_High = sensorValPhotoRes_Light
sensorVal_Low = sensorValPhotoRes_Dark
sensorSpan = sensorSpanPhotoRes
else:
sensorVal_High = sensorValMoisture_Air
sensorVal_Low = sensorValMoisture_Water
sensorSpan = sensorSpanMoisture
if(inputSensVal > sensorVal_High):
return 0
elif(inputSensVal < sensorVal_Low):
return 100
else:
subMappingSensorVal = inputSensVal - sensorVal_High
mappingSensorVal = (subMappingSensorVal/sensorSpan)*100
return abs(int(mappingSensorVal))
#############################
#IN: String (sensor type)
#OUT: Median value of 100 sensor readings
#DO: Reads the analog input 100 times of the called upon sensor type.
# It Then calculates the median value.
#############################
def medianSensVal(sensorType):
sensorValSum = 0
if str(sensorType) == 'photoRes':
adc_sensor = adc_photoRes
else:
adc_sensor = adc_moisture
for i in range(0, 100):
sensorValSum += adc_sensor.value()
sensorValMedian = sensorValSum/100
return sensorValMedian
#############################
#IN: -
#OUT: Water tank level in Liters
#DO: Checks which GPIO pins connected to the water tank that registers 1.
# If one or pins registers 0 it checks at which level those pins are.
#############################
def checkWaterLevel():
pinWaterTank_OUTPUT.value(1)
waterLvlList = [pinWaterTank_70(), pinWaterTank_50(), pinWaterTank_30(), pinWaterTank_10()]
pinWaterTank_OUTPUT.value(0)
tankVolume = 70 #Watertank volume in Liters
possibleLvlList = [0.7*tankVolume, 0.5*tankVolume, 0.3*tankVolume, 0.1*tankVolume] #Levels in water tank, [%] of total tank volume [L]
for x in range(len(waterLvlList) + 1): #Number of pins + 1
if(waterLvlList[x] == 0): #Executes when pin is set to LOW, i.e no water present
return possibleLvlList[x]
else:
return 70 #All pins are set to HIGH, water level is at least 70 liters
#############################
#IN: String which tells desired action (open/close)
#OUT: -
#DO: Open or closes the valve by turning the servo 90 degrees clockwise.
# It begins with "turning on" the servo by setting its duty cycle to 1.
# When closing it also sets the duty cycle to zero, this is so that the
# servo does not try to hold that position, effectively drawing no current.
#############################
def servoValve(openOrClose):
pwm_c = pwm.channel(0,pin='P11', duty_cycle = 1)
if openOrClose == 'close':
pwm_c.duty_cycle(0.075) # 0 degrees
sleep(1)
else:
pwm_c.duty_cycle(0.117) # 90 degrees
sleep(1) # Give the servo time to travel, otherwise the mechanical
# actuation will fall behind the following line of code.
pwm_c = pwm.channel(0,pin='P11', duty_cycle = 0)
#############################
#IN: String which tells desired action (open/close)
#OUT: Current water tank level and total consumed water
#DO: Open or closes the valve by turning the servo 90 degrees clockwise.
# It begins with "turning on" the servo by setting its duty cycle to 1.
# When closing it also sets the duty cycle to zero, this is so that the
# servo does not try to hold that position, effectively drawing no current.
#############################
def waterDischarge():
servoValve('open')
time.sleep(60)
servoValve('close')
time.sleep(60) #Let water settle in soil
sensorMoistVal = checkMoisture()
if sensorMoistVal < 70:
waterDischarge()
else:
newWaterTankLevel = checkWaterLevel()
if waterTankLevel < newWaterTankLevel: #If the water container has been refilled
waterTankLevel = newWaterTankLevel #Assumed that refilling does not occur during discharge
elif waterTankLevel > newWaterTankLevel:
consumedWater += (waterTankLevel-newWaterTankLevel)
waterTankLevel = newWaterTankLevel
servoValve('close') #Makes sure that valve is closed at startup
while True:
sensorMoistVal = sensorReading('moisture')
if sensorMoistVal < 30:
waterDischarge()
else:
photoResVal = sensorReading('photoRes')
# Send data via Sigfox
s.send(struct.pack("<B", sensorMoistVal)+struct.pack("<B", waterTankLevel)+struct.pack("<B", photoResVal)+struct.pack("<L", consumedWater))time.sleep(10)
time.sleep(900) #Sleep for 15 minutes (in seconds)
```
If you want to modify the data that is being sent, you need to change the custom callback function to match the data you are sending. In practice you need to choose carefully which kind of "Format character" you choose as I have done on line 192 in "main.py". See the [struct library](https://docs.python.org/3/library/struct.html) for more information.
### Transmitting the data / connectivity
We will be using using Sigfox to send our data. Sigfox is a radio-based network that has local antennas in a majority of countries around the world. See this [website](
) if you have coverage in your area. Data is sent on a public frequency, which is different depending on which country you are sending from. Using the Sigfox protocol stack, the LoPy 4 can transmit the messages to Sigfox's global network. These messages are uploaded to the cloud and then routed via "callback" to Ubidots.

With your LoPy 4 you have a pre-paid, 1 year subscription to the Sigfox service. Thereafter you need to pay for one of their subscription types.
There are two main limitations to this service.
* Small message size, maximum is 12 bytes.
* Limited to 140 messages per day.
We will send messages of 7 bytes every 15 minutes, which is a total of 96 messages per day.
Another limitation specific to the LoPy 4 is that we cant utilize downlink communication. This means that we cant send instructions to the microcontroller from the Ubidots dashboard via Sigfox.
To connect to Sigfox you first need to register the LoPy 4 at this [website](https://buy.sigfox.com/activate). At “DevKit” registration, enter your “Sigfox ID” and “Sigfox PAC” that you saved when using the Pycom firmware tool.
You will be asked to name your company, this can be any name you want.
When the device is registered, log in with your credentials on [Sigfox backend](https://backend.sigfox.com/auth/login).
Under “Device” you should see your device as in the picture below.

We want to confirm that connection to Sigfox via your device is established.
Start with registering an account on Pybytes [website](http://pybytes.pycom.io/).
When logged in, navigate to “Settings”, “SIGFOX API”. Register with your Sigfox credentials you got from the Pycom firmware tool. When done, it should look like the picture below.

Now, navigate “Devices” and press “Add via USB”. Select your device, in my case it was the LoPy 4.
In "Enable networks", choose Sigfox, and press next. Before saving, you can name your device (or you will get a randomly generated one).
Now, copy the activation token.

Start the Pycom firmware update tool and choose “Force update Pybytes registration”. Press continue, and enter your copied activation token and continue updating the firmware.
On the Pybytes website, under the device “Dashboard” we should see that a signal was received. Now we can confirm that the device and Pybytes can communicate.
Go back to “Provisioning” and press “CHECK SIGFOX STATUS”. If they gray dots turn green we can confirm that the Sigfox activation was successful.

Return to Sigfox backend. Go to device settings by pressing the name under “Device type”.

Navigate to "Callbacks" and press "New", choose "Custom callback"
Enter the following page as below
**Type**: DATA, UPLINK
**Channel**: URL
**Custom payload config**:
`soilMoisture::uint:8 waterTankLevel::uint:8 sunExposure::uint:8 totConsumedWater::uint:32:little-endian`
**Url pattern**: `http://industrial.api.ubidots.com/api/v1.6/devices/{device}`
**Use HTTP Method**: POST
**Send SNI**: Uncheck
**Header**: x-auth-token
**Value**: YOUR UBIDOTS TOKEN
**Content type**: application/json
**Body**:
```
{
"Soil moisture":{"value":"{customData#soilMoisture}"},
"Watertank level" : {"value":"{customData#waterTankLevel}"},
"Sun exposure" : {"value":"{customData#sunExposure}"},
"Total consumed water" : {"value":"{customData#totConsumedWater}"}
}
```
In the "custom payload config" we are sending three values that are 8 bit, therefore, these values can only be values between 0-255. The last value is 32 bit and will be sufficient for practically any value, although it takes more data. To send the last value, you need to add “little-endian”, you can read more on this on [Wikipedia](https://en.wikipedia.org/wiki/Endianness). Remember that each packet sent can’t be more than 12 bytes, here I am sending 7 bytes.
Now, press “Ok” to finish the Sigfox setup.
To test that the data is being sent correctly, run the following code.
```python=
# Required libraries
from network import Sigfox
import socket
import struct
data1 = 42 #0-255
data2 = 82 #0-255
data3 = 70 #0-255
data4 = 310 #0-(2^32 - 1)
sigfox = Sigfox(mode=Sigfox.SIGFOX, rcz=Sigfox.RCZ1) #Change rcz-zone depending on your area.
# create a Sigfox socket
s = socket.socket(socket.AF_SIGFOX, socket.SOCK_RAW)
# make the socket blocking
s.setblocking(True)
# configure it as uplink only
s.setsockopt(socket.SOL_SIGFOX, socket.SO_RX, False)
s.send(struct.pack("<B", data1)+struct.pack("<B", data2)+struct.pack("<B", data3)+struct.pack("<L", data4))
```
Go to Sigfox backend, navigate to "Device" > Messages
You can click the green callback symbol and see that the correct values was sent.

**Note**: At startup, your device may send a startup communication message to Sigfox. Your custom payload will interpret these as random values. You will have to remove these manually in Ubidots, which we will cover in "Presenting the data".
The data we are sending is the optimal size we can get. When comparing with LoRa and Wi-Fi, the Sigfox is the most "lean" in terms of data and energy consumption. An improvement in the project would be to put the device in "deep sleep" when not transmitting. Although you would need to save variables such as watertank-level on the onboard memory to do that. The range of Sigfox is good as it works on low frequency bandwidth, although some areas have no Sigfox capability at all which could be seen in the coverage map mentioned earlier.
### Presenting the data
Now we will visualize the data in Ubidots.
Go to Ubidots website. Under "Devices" a new device should be seen. Clicking on it should return a page much like this.

By pressing a variable you can see when and what have been sent, here you can limit which values are acceptable. To mitigate the problem of Sigfox communication message discussed earlier, I am limiting "soil-moisture", "sun-exposure", "waterTank-level" to be within 0-100.

To create a custom dashboard to visualize this data, go to "Data" > "Dashboards".
Press the symbol as in the figure below, then select the plus sign to create a new dashboard.

Pressing "Add new widget" you can choose how you want to present your data, in this example we are using two "Line chart", one "Tank", one "Metric" and a "Map" to show our location.
When adding a widget, it will as for the variable. This can be found in the device we saw earlier.

The final design for my dashboard is

As explained in the communication chapter, we are sending the data every 15 minutes. An improvement upon this would be to exlude sending values that have not changed since the last message. Your data will be stored at Ubidots for 24 months. If you plan to store data for longer I would suggest to either store it locally on an SD-card or send it to a local server via callback. Also, note that some of the data wont update until the moisture sensor registers under 30%, triggering water dispensation and subsequent water level check.
### Finalizing the design
As of writing I am 3D-printing the final components, which I have uploaded on my [Github](https://github.com/miksjobe/applied_IoT).
For future work I would suggest on making the LoPy 4 go into deep sleep when not transmitting data. Right now it will draw unnecessary battery power as it is just "paused" and not in low power mode. To do this you will need to store the variable for total consumed water locally.
Also, we are sending just 7 of the 12 bytes available. This makes data such as air humidity an interesting point of data regarding the garden environment.
In terms of hardware, I would suggest adding a [flowsensor](https://https://www.electrokit.com/produkt/flodesmatare-1-30l-m-1-2/) that utilizes the hall effect to approximate how much water is being used. It wont be as robust than the one used in this project, but it is fun to learn about pulses and callbacks. I bought one, but mine was defective.
In the pictures below you can see the testbench setup with an attached video explaining how the system works.


https://streamable.com/5xknl