Getting Started with the Arduino IoT Cloud
- Emmanuel Odunlade
- https://twitter.com/emmaodunlade
- emmaodunlade@gmail.com
- 8.655 Views
- medium
- Tested
IoT is now mainstream. It has gone beyond the buzzword it used to be and several tools are being made available to makers to facilitate the development of solutions based on it. One of the newest IoT platforms is the Arduino IoT Cloud developed by our good friends at Arduino.cc. Today we will examine how you can build projects that interact with the Arduino IoT Cloud, such as sending and receiving data.
The Arduino IoT Cloud is Arduino’s way of democratizing IoT development, making it easy for everyone to build internet of things applications. It provides the ability for IoT based devices to exchange data between each other and the cloud where further processing can be done and use the data to solve specific problems. The platform allows communication via a host of protocols including; HTTP REST API, MQTT, Command-Line Tools, Javascript, and Websockets. It features the typical “easy to use” nature of the Arduino and also features tools that make it capable of automatically generating the sketch/code for your device, which helps reduce development time from hours to minutes. In its typical open source nature and support for other clones, the Arduino IoT Cloud allows the connection of other Linux-based devices and boards to the platform.
At the center of today’s project is the Arduino MKR WiFi 1010. It is a significantly improved version of the MKR 1000 WiFi. It’s equipped with an ESP32 module made by U-BLOX. The board aims to speed up and simplify the prototyping of WiFi-based IoT applications thanks to the flexibility of the ESP32 module and its low power consumption.
The board is composed of three main blocks:
- SAMD21 Cortex-M0+ 32bit Low Power ARM MCU
- U-BLOX NINA-W10 Series Low Power 2.4GHz IEEE® 802.11 b/g/n Wi-Fi
- ECC508 Crypto Authentication
The MKR WIFI 1010 includes a 32-bit computational power, the usual rich set of I/O interfaces associated with Arduino boards, and a low power Wi-Fi with a Cryptochip for secure communication using SHA-256 encryption. All these features can easily be programmed using Arduino Software (IDE) for code development and programming.
As a demo to show how the Arduino IoT cloud works, we will build a simple weather monitor. The device will obtain temperature, humidity, atmospheric pressure, and light intensity data from the environment using the Arduino MKR ENV shield and upload the data to the Arduino IoT Cloud every 2s. To provide an offline view of the data, we will use a 1.44 Adafruit TFT LCD display. You can check some of our past tutorials using the 1.44″ TFT display to better understands how it works.
The Arduino MKR ENV shield allows the MKR Arduino board series to acquire environmental data via a plethora of sensors built into the board. The inbuilt sensors include latest generation sensors for measuring;
- Atmospheric pressure
- Temperature and humidity
- Ultraviolet UVA intensity Ultraviolet UVB intensity,
- UV Index (calculated)
- Light intensity (in LUX)
To give you the ability to store data from the sensors, the ENV shield also has a slot for a microSD card onboard. We will use 4 out of the 5 sensors onboard the ENV Shield for today’s tutorial.
To demonstrate how to process/send data from the IoT cloud to devices, we will use the Arduino MKR RGB Shield to display the text “ON” or “OFF”, depending on the state of a button on the IoT Cloud.
The MKR RGB shield is the last “new” element for today’s project. The shield comprises an array of super bright LEDs which, with the use of a superb library, allows the display of static and scrolling text in minutes.
At the end of today’s tutorial, you would know how to work with the Arduino Create platform, build IoT devices based on the Arduino IoT Cloud, and work with the MKR WiFi 1010 Arduino board along with its accessories.
Required Components
The following components are required to replicate this project;
- Arduino MKR WiFi 1010
- Arduino MKR ENV Shield
- Arduino MKR RGB Shield
- Adafruit 1.44TFT Display
The components can all be bought from the Arduino store or from sellers on electronics components sales platforms like banggood, and Aliexpress.
Schematics
To make the project easier to follow and understand, we will break it down into two parts. The first part will entail how to send data to the Arduino IoT cloud while the second part will cover how to receive data from it.
Part 1 Schematics
For the first part (sending data to the cloud), we will use the Arduino MKR WiFi 1010 board, the MKR ENV Shield, and the 1.44″ TFT Display. The MKR ENV Shield, like the name implies, comes as a shield which is going to be plugged on top the MKR WiFi 1010, leaving us with just the connections between the MKR WiFi 1010 and the 1.44″ TFT display. Connect them as shown in the fritzing schematics below.
To make connections easier to follow, the pin to pin map of the connection between the MKR WiFi 1010 and the 1.44″ TFT is shown below.
1.44″ TFT – Arduino MKR WiFi
Vin - VCC GND - GND SCk - D9(SCK) SI - D8(MOSI) TCS - D3 RST - D2 D/C - D1
After connecting the components, your setup should look like the one in the image below.
Part 2 Schematics
For the second half of the project (Receiving Data from the cloud), we will use Arduino MKR WiFi 1010 and the Arduino MKR RGB Shield. While adjustments could be made to accommodate the sensors on the ENV shield, we will do it this way to keep things short and simple.
Just like the MKR ENV Shield, the RGB Shield, as the name implies also comes as a shield and should be plugged on the MKR WiFi 1010 as shown in the image below.
Since you will most likely use the same MKR WiFi 1010 for both parts, you can complete the code upload of one part before connecting the second part.
Programming
In contrast to what we used to, for today’s tutorial, we will not use the Arduino (offline) IDE software to write the code for our project. Instead, we will use the Arduino Create platform with the code for the project automatically generated by the Arduino IoT cloud.
Just like we did under the schematics, we will also split this into two parts. The first part will be for programming the board to send the data as we highlighted initially while the second part will be to receive data from the IoT Cloud and perform actions with it, which in this case is used to display ON/OFF on the RGB shield.
Programming Part 1
For the first part, We start by going to the Arduino IoT Cloud platform and selecting the -> Arduino IoT Cloud.
On the succeeding page, we select the “new thing” button which allows us to set up a new IoT device on the Cloud Platform.
On the page that opens, select the board on which the “thing” is based, Which in our case is the Arduino MKR WiFi 1010.
This launches the MKR 1010 configuration window. You will need to download and install the Arduino Create Plugin at this point if you don’t have it installed already. Click on the start button.
The system will instruct you to connect your board to your PC. As soon as this is done, if your installation of the Arduino Create plugin was successful, the board will appear under the board list like in the Image below. Rename the board to any name you like or just leave the default (not advisable for security reasons).
With that done, the next step is to configure the crypto chip on the board. This provides some sort of encrypted key specific to the board which allows the board to interact securely with the cloud. The setup will not be successful if you fail to go through this step.
With that done, the board should now be visible in your Arduino Cloud device manager interface and we can now proceed to create/associate a thing with it.
Click on the board and create a new thing based on it.
For each “thing” created on the Arduino IoT Cloud, you can define a couple of properties for the thing by clicking on the “+” sign in the things edit view. Properties represent specific data associated with your IoT Object and are defined with certain features including Data type, Update frequency and permissions which could be either be read-only or read&write. Read-only permissions means the values/state/content of that particular property cannot be edited from the cloud server and is thus ideal for holding sensor data, while the Read&Write on the other hand, is used when you want to be able to edit the state/value/content of a property from the IoT Cloud. Properties which involve sending data/commands from the cloud to the device are best defined using this permission.
For the first part of the project, we will attach 4 properties including; temperature, humidity, atmospheric pressure, and light intensity, to our “thing”. Each of these properties will hold the corresponding values from the sensors on the ENV Shield and will have read-only permission. Create the properties as shown in the example for the temperature property below.
Do the above for the remaining properties such that your “things property window”, looks like the image below.
With this done, the four properties should now be reflected on your widget page but with no values, since we are yet to program the board.
Another good thing about the Arduino IoT cloud is its ability to autogenerate the code for your project based on your board type and the properties you selected for the “thing”. To autogenerate the sketch files, click on the “edit code” button. You should now be able to see the sketch and the other files that have been generated for the project. All we now need to do is to add the code for the TFT to the sketch and the credentials of our WiFi (SSID and Password) to the Arduino_secrets.h file.
While the code is autogenerated, to keep the tradition, I will try to explain it to make it easier to work with going forward.
The sketch starts with the inclusion of the libraries and header files that will be used. In addition to the libraries and header files in the autogenerated code, we will use the Adafruit GFX Library (Core graphics display library from Adafruit) and the Adafruit ST7735 Library (Hardware Specific library for ST7735 TFT Display).
#include "thingProperties.h" #include <avr/dtostrf.h> #include <SPI.h> //////////////////////////////// #include <Arduino_MKRENV.h> //////////////////////////////// #include <Adafruit_GFX.h> // Core graphics library #include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
After the above, we declare the pins of the Arduino to which the Chip Select(CS), Reset(RST) and DC pins of the TFT LCD are connected (as described under the schematics section).
#define TFT_CS 3 #define TFT_RST 2 #define TFT_DC 1
Next, we create an object of the ST7735 library which will be used to address the LCD all through the sketch.
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
Fllowed by the declaration of variables to hold the values from the sensors on the ENV shield, alongside a variable to hold the last time the device connected to the cloud, and another to hold the interval at which data will be posted.
/////////////////////////// float _temperature = 0; float _humidity = 0; float _pressure = 0; float _lux = 0; unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds const unsigned long postingInterval = 2000; // delay between updates, in milliseconds
With this done, we move to the void setup() function. We start by initializing serial communication, setting the baud rate to 9600 and then initializing the parameters defined in the thingproperties.h header file with the initProperties() function.
void setup() { // Initialize serial and wait for port to open: Serial.begin(9600); // This delay gives the chance to wait for a Serial Monitor without blocking if none is found delay(1500); // Defined in thingProperties.h initProperties();
Next, we connect to the Arduino IoT Cloud using the ArduinoCloud.begin() function with “ArduinoIoTPreferredConnection” variable as an argument. This variable has been already set to WiFi in the “thingproperties” header files.
// Defined in thingProperties.h initProperties(); // Connect to Arduino IoT Cloud ArduinoCloud.begin(ArduinoIoTPreferredConnection);
Next, we initialize the display and set the screen back color to Black.
// Init ST7735R chip, green tab tft.initR(INITR_144GREENTAB); tft.fillScreen(ST77XX_BLACK);
We follow that with the initialization of the ENV shield and instruct the device to stay put if the initialization of the ENV shield fails using the while(1) statement.
if (!ENV.begin()) { Serial.println("Failed to initialize MKR ENV shield!"); while (1); }
The setup() function is wrapped up by setting it to show debug information. The ArduinoCloud.printDebugInfo() function allows you obtain more information related to the state of the network and IoT Cloud Connection and errors. The higher the number set for the debug message level the more information is displayed. The maximum is however 4 and the minimum is zero. For today, we will set it to 2.
setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); }
Up next is the void loop() function.
The logic for the void loop() function is quite simple. We start by checking if the 2 seconds have elapsed between the time the last connection was made by checking if the value obtained by subtracting millis from the last connection time is greater than the posting interval (2 seconds).
void loop() { // if 2 seconds have passed since your last connection, // then connect again and send data: if (millis() - lastConnectionTime > postingInterval) { readSensors(); TEMPERATURE = _temperature; HUMIDITY = _humidity; PRESSURE = _pressure; LIGHT_INTENSITY = _lux;
If the above is true, then we obtain new values from the ENV shield using the readSensors() function and then attach the values of each element to the corresponding variable.
readSensors(); TEMPERATURE = _temperature; HUMIDITY = _humidity; PRESSURE = _pressure; LIGHT_INTENSITY = _lux;
The values are displayed on the TFT display using the displayValuesOnTFT() function and the Last connection time is set to the current value of millis().
displayValuesOnTFT(); lastConnectionTime = millis();
Finally, the ArduinoCloud.update() function is called to update the values on the cloud and the status of the update is displayed on the screen.
ArduinoCloud.update(); testdrawtext(5,120,"data sent to Cloud!", ST77XX_WHITE,1); } }
The remaining part of the code is the three functions used which includes; the readSensor() function which is used to obtain data from the ENV shield, the displayValuesonTFT() function used as the name implies and the testdrawtext() function used to display the data upload state. We have quite a number of tutorials written on the 1.44″ TFT LCD display which you can check out to learn more about it.
//Read sensors value: Temperature, Humidity, Pressure, Lux void readSensors() { _temperature = ENV.readTemperature(); _humidity = ENV.readHumidity(); _pressure = ENV.readPressure(); _lux = ENV.readLux(); } // Display values on TFT void displayValuesOnTFT() { // float to char conversion char temperature[6]; char humidity[6]; char pressure[7]; char lux[8]; dtostrf(_temperature, 5, 2, temperature); dtostrf(_humidity, 5, 2, humidity); dtostrf(_pressure, 5, 2, pressure); dtostrf(_lux, 6, 2, lux); // Display on TFT tft.fillScreen(ST77XX_BLACK); testdrawtext(0,5,"T:", ST77XX_RED,2); testdrawtext(25,5,temperature, ST77XX_RED,2); testdrawtext(110,10,"C", ST77XX_RED,1); testdrawtext(0,35,"H:", ST77XX_GREEN,2); testdrawtext(25,35,humidity, ST77XX_GREEN,2); testdrawtext(110,40,"%", ST77XX_GREEN,1); testdrawtext(0,65,"P:", ST77XX_YELLOW,2); testdrawtext(25,65,pressure, ST77XX_YELLOW,2); testdrawtext(110,70,"hPa", ST77XX_YELLOW,1); testdrawtext(0,95,"L:", ST77XX_WHITE,2); testdrawtext(25,95,lux, ST77XX_WHITE,2); testdrawtext(110,100,"Lux", ST77XX_WHITE,1); } void testdrawtext(int x, int y, char *text, uint16_t color, int size) { tft.setCursor(x, y); tft.setTextColor(color); tft.setTextWrap(true); tft.setTextSize(size); tft.print(text); }
The complete code is available below and also attached under the download section.
#include "arduino_secrets.h" #include "thingProperties.h" #include <avr/dtostrf.h> #include <SPI.h> //////////////////////////////// #include <Arduino_MKRENV.h> //////////////////////////////// #include <Adafruit_GFX.h> // Core graphics library #include <Adafruit_ST7735.h> // Hardware-specific library for ST7735 #define TFT_CS 3 #define TFT_RST 2 #define TFT_DC 1 Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); /////////////////////////// float _temperature = 0; float _humidity = 0; float _pressure = 0; float _lux = 0; unsigned long lastConnectionTime = 0; // last time you connected to the server, in milliseconds const unsigned long postingInterval = 2000; // delay between updates, in milliseconds int debug = 0; void setup() { // Initialize serial and wait for port to open: Serial.begin(9600); // This delay gives the chance to wait for a Serial Monitor without blocking if none is found delay(1500); // Defined in thingProperties.h initProperties(); // Connect to Arduino IoT Cloud ArduinoCloud.begin(ArduinoIoTPreferredConnection); // Init ST7735R chip, green tab tft.initR(INITR_144GREENTAB); tft.fillScreen(ST77XX_BLACK); if (!ENV.begin()) { Serial.println("Failed to initialize MKR ENV shield!"); while (1); } /* The following function allows you to obtain more information related to the state of network and IoT Cloud connection and errors the higher number the more granular information you’ll get. The default is 0 (only errors). Maximum is 4 */ setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); } void loop() { // if 2 seconds have passed since your last connection, // then connect again and send data: if (millis() - lastConnectionTime > postingInterval) { readSensors(); TEMPERATURE = _temperature; HUMIDITY = _humidity; PRESSURE = _pressure; LIGHT_INTENSITY = _lux; displayValuesOnTFT(); lastConnectionTime = millis(); /* NOTE: We put "ArduinoCloud.update()" inside the "if condition" only to make sure that the values shown in the TFT are the same as those shown in the cloud in the same time interval. But this function by itself is responsible for sending every 2 seconds the value of the variables to the cloud. */ ArduinoCloud.update(); testdrawtext(5,120,"data sent to Cloud!", ST77XX_WHITE,1); } } //Read sensors value: Temperature, Humidity, Pressure, Lux void readSensors() { _temperature = ENV.readTemperature(); _humidity = ENV.readHumidity(); _pressure = ENV.readPressure(); _lux = ENV.readLux(); } // Display values on TFT void displayValuesOnTFT() { // float to char conversion char temperature[6]; char humidity[6]; char pressure[7]; char lux[8]; dtostrf(_temperature, 5, 2, temperature); dtostrf(_humidity, 5, 2, humidity); dtostrf(_pressure, 5, 2, pressure); dtostrf(_lux, 6, 2, lux); // Display on TFT tft.fillScreen(ST77XX_BLACK); testdrawtext(0,5,"T:", ST77XX_RED,2); testdrawtext(25,5,temperature, ST77XX_RED,2); testdrawtext(110,10,"C", ST77XX_RED,1); testdrawtext(0,35,"H:", ST77XX_GREEN,2); testdrawtext(25,35,humidity, ST77XX_GREEN,2); testdrawtext(110,40,"%", ST77XX_GREEN,1); testdrawtext(0,65,"P:", ST77XX_YELLOW,2); testdrawtext(25,65,pressure, ST77XX_YELLOW,2); testdrawtext(110,70,"hPa", ST77XX_YELLOW,1); testdrawtext(0,95,"L:", ST77XX_WHITE,2); testdrawtext(25,95,lux, ST77XX_WHITE,2); testdrawtext(110,100,"Lux", ST77XX_WHITE,1); } void testdrawtext(int x, int y, char *text, uint16_t color, int size) { tft.setCursor(x, y); tft.setTextColor(color); tft.setTextWrap(true); tft.setTextSize(size); tft.print(text); }
Part 2
As mentioned above, for the second part, we will sent data from the cloud to our device. A text, “ON” or “OFF” will be displayed on the MKR RGB Shield based on the status of a switch on the Cloud.
We start by going back to the property settings of our thing to add a new property, which we will call ON_OFF. It will be of type “ON / OFF (boolean)” and will have the read and write permission, with updates taking place only when there is a change in the value.
With this done, the property should now be listed on our widget along with the others as shown below.
Click on the “edit code” button, the code with the recent changes will be generated and just as we did with the last one, we will add our own bits to it, to display the text on the RGB Shield.
To make it easy to follow, we will remove the environment variables of Part 1 from the generated code leaving only the code required for this part.
As usual, the sketch starts with the inclusion of the required libraries and header files. In addition to the header files included by the IoT Cloud, we add the Arduino Graphics library and the Arduino MKR RGB library which are both required to simplify the code for the RGB shield.
#include "arduino_secrets.h" #include "thingProperties.h" #include <ArduinoGraphics.h> // Arduino_MKRRGB depends on ArduinoGraphics #include <Arduino_MKRRGB.h>
Next, we create the variables; state and debug.
int debug = 0; int state = 2;
With this done, we move to the void setup() function.
We start by initializing serial communication, setting the baud rate to 9600 and then initializing the parameters defined in the thingproperties.h header file with the initProperties() function.
void setup() { // Initialize serial and wait for port to open: Serial.begin(9600); // This delay gives the chance to wait for a Serial Monitor without blocking if none is found delay(1500); // Defined in thingProperties.h initProperties();
Next, we connect to the Arduino IoT Cloud using the ArduinoCloud.begin() function with “ArduinoIoTPreferredConnection” variable as an argument. This variable has been already set to a WiFi connection in the “thingproperties” header files.
// Connect to Arduino IoT Cloud ArduinoCloud.begin(ArduinoIoTPreferredConnection);
After this, we initialize the RGB LED Matrix using the MATRIX.begin() function after which we set the brightness, text scroll speed and clear it.
MATRIX.begin(); // set the brightness, supported values are 0 - 255 MATRIX.brightness(200); // configure the text scroll speed MATRIX.textScrollSpeed(125); MATRIX.clear(); MATRIX.endDraw();
with that done, just like with Part1, we end the code by setting the debug message level and printing debug info.
setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); }
With this done, we move to the void loop() function. We start by sending an update request to the cloud and checking if the value of the state variable has changed. If the value of the state variable has changed to 1, the RGB is cleared, cursor set, the “ON” text displayed using the Matrix.print() function and it is instructed to scroll.
void loop() { ArduinoCloud.update(); if (state == 1) { MATRIX.clear(); MATRIX.endDraw(); MATRIX.beginText(0, 0, 0, 127, 0); // X, Y, then R, G, B MATRIX.print(" ON"); MATRIX.endText(SCROLL_LEFT);
If the state is 0, the display is cleared and the “OFF” text is displayed using the same functions as above.
}else if (state == 0) { MATRIX.clear(); MATRIX.endDraw(); MATRIX.beginText(0, 0, 127, 0, 0); // X, Y, then R, G, B MATRIX.print(" OFF"); MATRIX.endText(SCROLL_LEFT); }
In case the system is unable to ascertain the value of the state variable for any reason, a blank screen is displayed by wiping the RGB LED Display.
else{ MATRIX.clear(); MATRIX.endDraw(); }
Lastly, for debug purposes, the state value is printed on the serial monitor.
Serial.println(state); }
After the void loop(), the function which handles the state value is written. As soon as an update is requested from the cloud, if there is a change in the value, the function updates the state variable with the change in value.
void onONOFFChange() { if (ON_OFF == true) state = 1; else state = 0; }
The complete code is available below and also attached under the download section of the tutorial.
#include "arduino_secrets.h" #include "thingProperties.h" //////////////////////////////// #include <ArduinoGraphics.h> // Arduino_MKRRGB depends on ArduinoGraphics #include <Arduino_MKRRGB.h> //////////////////////////////// int debug = 0; int state = 2; void setup() { // Initialize serial and wait for port to open: Serial.begin(9600); // This delay gives the chance to wait for a Serial Monitor without blocking if none is found delay(1500); // Defined in thingProperties.h initProperties(); // Connect to Arduino IoT Cloud ArduinoCloud.begin(ArduinoIoTPreferredConnection); // initialize the display MATRIX.begin(); // set the brightness, supported values are 0 - 255 MATRIX.brightness(200); // configure the text scroll speed MATRIX.textScrollSpeed(125); MATRIX.clear(); MATRIX.endDraw(); /* The following function allows you to obtain more information related to the state of network and IoT Cloud connection and errors the higher number the more granular information you’ll get. The default is 0 (only errors). Maximum is 4 */ setDebugMessageLevel(2); ArduinoCloud.printDebugInfo(); } void loop() { ArduinoCloud.update(); if (state == 1) { MATRIX.clear(); MATRIX.endDraw(); MATRIX.beginText(0, 0, 0, 127, 0); // X, Y, then R, G, B MATRIX.print(" ON"); MATRIX.endText(SCROLL_LEFT); }else if (state == 0) { MATRIX.clear(); MATRIX.endDraw(); MATRIX.beginText(0, 0, 127, 0, 0); // X, Y, then R, G, B MATRIX.print(" OFF"); MATRIX.endText(SCROLL_LEFT); }else{ MATRIX.clear(); MATRIX.endDraw(); } Serial.println(state); } void onONOFFChange() { if (ON_OFF == true) state = 1; else state = 0; }
Demo
Just like we did for both the schematics and programming sections, we will also separate the demo into two parts as I believe you will most likely use the same MKR WiFi 1010 board for both parts.
Part 1 Demo
At this stage, we are ready to upload the code to the Arduino MKR WiFi 1010 board. Double check the connections between the Arduino and TFT LCD, and ensure the ENV shield is well plugged in. Also, check to ensure you have filled the credentials for the WiFi access point in the Arduino_secrets.h file.
With the board plugged in as when you generated the code, upload the code to your board. You can launch the serial monitor to see how things are going. After a few seconds, you should see the MKR WiFi 101o connect to the Internet through the WiFi access point and you should see data being displayed on the widget page of the “thing” we created and also on the TFT as shown in the image below.
Part 2 Demo
For the Part 2, Plug in the MKR RGB Shield into the MKR WiFi 1010 board as described under the schematics section and double check to ensure you have filled the credentials for the WiFi access point in the Arduino_Secrets.h file. The Arduino_secrets.h file can be found in the same folder as the code generated from the IoT Cloud. With everything in place, upload the code to the board and toggle the switch on the Widget page of the Arduino IoT Cloud. You should see the text on being displayed on the RGB switch between “on” and “off’ depending on the state of the switch widget.
It is important to note that you can combine both sketches to build a system capable of two way communication with the IoT Cloud. We just separated things so as to be able to explain the steps in details. As a learning challenge, you can try combining the code for both parts or creating a similar project and testing it out. That’s it for today’s tutorial, feel free to reach out to me via the comment section with questions and any general comment about today’s project.
Pictures Credits: AvilMaru