Interface and Application Programming


Group assignment

Individual assignment


Learning outcomes:


Have you:


Software Used


Group Assignment

For the other group assignments go to the group assignment page.

This assignment was completely done by me as I am the only student at the Algarve FabFarm this year.

The app

MIT App Inventor

MIT App Inventor is a great, simple, free tool that lets you design phone apps. There are two flavors: one with an installation and the most recent one, which is totally online.

Files

Source Code

APK

Steps:

  1. To access the online version go to this link.
  2. Next, log in with your Google account.
  3. Click "Start new project".

    MIT App Inventor start new project

  4. Name the project. Mine is "groupproject".

    Naming the project

  5. #1 in the center is the area reserved for the design of the app; #2 this section is used in case you want to have more than one screen; #3 to the left, the palette contains all the objects, like buttons or a video player, used in the visual construction of the app; #4 each object can have its properties edited in this area to the right; #5 switches to the blocks area, where the logic of the app is created.

    App design interface

  6. In this quick example, I drag a button from the left side to the screen.

    Dragging a button

  7. Here I also show how to add text in the same fashion as the button, but I won't be using the text in my app.

    Adding text

  8. Using the Paint 3D application from Windows 10, I quickly drew a big red button.

    Drawing a red button

  9. Next, I upload the red button to MIT App Inventor. This procedure will be used every time a media element is used.

    Uploading the red button

  10. Then I edit its properties, like height, width and text. On #3 I define the image that will serve as the button, in my case the big red button.

    Editing button properties

  11. Then I drag a sound object to the screen area. The process to link this object to its media is the same as before. I will also repeat that for a video.

    Adding a sound object

  12. Next, I move to the blocks editor area in the upper right corner. Clicking on the button opens the possible blocks I can add to the app logic. I then add a button click block.

    Adding a button click block

  13. Clicking on the sound block and selecting "call sound1.play" from the options allows me to add a command to the button, to be executed when it's clicked.

    Adding sound play command

  14. I do the same as before to add a video and then to open it in full screen.

    Adding a video

  15. After the app is done, I generate an APK and save it to my phone's file system in order to install the program.

    Generating APK

    Saving APK

    Installing APK

  16. On the phone, I went to the location where I saved the APK and installed it. Play Protect will try to block the app, but that can be ignored.

    Installing on phone

    Play Protect warning

  17. This is the resulting app made in the group assignment.


Files

  1. Code

Introduction

This assignment is part of my final project development.

As my final project has been changed to an irrigation system, I started developing its web interface.

It's really complicated to separate the competences and keep here only the part of my project that has to do with the interface, so at some points I will have subjects that are related to other weeks.

Hero-Shot Hero-Shot

The design so far is the one above. I still need to modify it for the final project, as I do not want it merely activating/deactivating the pumps, but I also want scheduling of zones, among other features.

I will compile below the steps it took me to arrive at this point, which partly overlap with my Project Update 2.

As I am writing this, there are no current design files, there is a sketch of code and there is a prototype on a breadboard!

On the other hand, there are requirements for the hardware and for the software:

Software Requirements and stage

  1. Simplify the code creating more functions;
  2. Try to separate the HTML part for cleaner code;
  3. Improve the appearance/Interface of the code;
  4. Add readings to HTML;
  5. Add a log of occurrences like over current;
  6. Add more safety for the equipment;
  7. Add a phone interface (APP);
  8. Add a function to set the current time;
  9. Add a renaming function to each relay so one can relate the relay to the area of interest, or at least rename relays to actual areas of the farm.

Development

7th of June, 2020

As of today, 7th of June 2020, the development is like this.

Initial setup What I start with is a system that works on an Uno and is a torture on an ESP8266; besides, it is a shock hazard, and because of water damage I need to replace some relays.

The code running on this setup can be found on the Fabfarm GitHub: Autovalve.

Shakeel, one of our volunteers, managed to create a version that runs on an ESP8266, which can be found in the same repo here. Due to the pandemic situation he left us in a hurry and did not have time to test his code, so it worked but was not stable, due to the fact that he heroically attempted to share the analog pin with the WiFi, leading to WiFi drops that made using the system almost impossible. This is the code that I started with.


8th of June, 2020

After many hours trying to understand the code, I managed to port Shakeel's code to an ESP32 and also split the file, with each function in a separate file.

In this video I show the progress I have made so far in merging the codes and having it work on the ESP32.

Check the high-resolution version on YouTube

Porting the code to the ESP32 might be simple for a programmer, but it took me many hours.

Steps:

  1. Separated everything that was already a function into its own file to make it more readable;
  2. Then opened another session of the Arduino IDE and started copying every part of the code, line by line, to the new session; in this session I renamed the main file to "esp32irrigation";
  3. Remapped the pins to ESP32 pins;
  4. Ran Beautify on the HTML in Brackets;
  5. Organized the code so the pins are on top;
  6. Created variables for the RTC pins;
  7. Commented out parts of the code that print errors to the serial monitor.

The code is too big to display in the HTML, so I will leave a link to the state of the code as of this early morning: code here.

Here in this video you can see the testing setup of the current irrigation system of the farm.

Check the high-resolution version on YouTube


9th of June, 2020

Today, after testing the relays, I realized that some relays work and some don't. After testing directly on the lab power supply, with 5 volts applied to the signal pin, I believe that this is due to the low voltage being supplied by the ESP32, or to the signal voltage.

I then watched Great Scott's video on YouTube about relays and optocouplers.

I will make sure the relays have at least a 5 volt power supply, which is their required voltage according to the datasheet. The signal should be fine at 3.3 V. Will be tested in situ.

Relay setup

The other point is the current reading, which is completely wrong. In order to have the irrigation system working, I will have to run it without current sensing until it is debugged.


10th of June, 2020

After the tests, the conclusion about the voltage problem is that the ESP32 should not supply power to the relay; that should be supplied by an external power source, set at 5 volts. The signal can come from the ESP32 unchanged, but the ESP32 should share the same ground.

More changes in the code:

  1. Increased the interval at which the time is displayed from 3000 to 5000 ms (5 s) in the variable RTCtimeInterval;
  2. Increased the interval at which the setup is displayed from 10000 to 30000 ms (30 s) in the variable configTimeInterval;
  3. Set recognizable names in the relays' HTML interface;
  4. Cleaned up the serial monitor a bit more by removing seconds from the settings display.

I will put the setup back to work this morning, set times for each irrigation and field test the ESP32 irrigation module.

Strangely, the external RTC clock is not keeping accurate time. I will look into using the internal clock with an external battery in case of power failure.

According to this closed issue discussion in the ESP repo on GitHub and this ESP accuracy topic, the internal clock will drift: "Internal RTC clock frequency error is about 5%". This can be solved with an external 32.768 kHz crystal assembled according to the schematics below:

Another solution would be to have the ESP always connected to the Internet, grabbing time updates from the Internet to compensate for the inaccuracy.

RTC clock schematic

After-wakeup update:

Today I went on to install the temporary testing setup. I troubleshot a few problems.

  1. Setup was not working when disconnected from the computer. Solution: the power wiring was not correct; I ended up connecting 2 USB cables, one for the ESP32 and another for the relays, and they both share the same ground. I might have solved the RTC clock problem with the same approach as well.

This is how the prototype looks now:

Prototype setup Still need to wire the pump and the solenoid valves.

I hope the wasps don't mind the invasion I hope the wasps don't mind the invasion

Wiring the system Wiring the system

testing...testing...

After the tests I went back to the code for more customization.

I first watched a series of videos on YouTube about how to really create functions and then started creating even more functions.

Basically, I went looking into how to reduce code repetition and how to send a value to a function.

From the code available I created this function:

void turnOffRelay(int valveHere){
  // wait then turn valve relay OFF
  Serial.print("Waiting ");
  Serial.print(waitTimeValveOff / 1000);
  Serial.println("s before deactivating Valve Relay 3.");
  delay(waitTimeValveOff);
  digitalWrite(valveHere, LOW);
  valveHere = 0;
  Serial.print("*** Valve Relay ");
  Serial.print(valveHere);
  Serial.println("3 turned OFF ***");
}

Warning: valveHere = 0; runs before Serial.print(valveHere);, so the message always comes out as "Valve Relay 03 turned OFF", whatever the valve. The "3" is also hard-coded in both messages. Print valveHere before zeroing it (or drop that line) and take the fixed "3" out of the strings.

Every time I want to call this function, I use:

      {
        int valveHere = valveRelay3;
        turnOffRelay (valveHere);
      }

The reason I use the {} is that I had to declare the local variable valveHere; this way I could replace only that piece of code each time I called the function.

Technically, calling that function would be just having this code here turnOffRelay (valveHere);, but that would not work alone, as I am always replacing the valve relay pin with int valveHere = valveRelay3;

Warning: the {} and the local variable are not needed: the pin goes in as the argument, so turnOffRelay(valveRelay3); alone does the same thing.

I still haven't tested the behaviour but at least it compiles :-)


9th of June, 2020

I burned it on the ESP32 and the behavior is unexpected. The function keeps printing again and again. I believe I need to send back the state of one variable, and I did not do that. I will try using the code return variable; to do it and then test, but first I will watch the functions video again to better understand it.


13th of June, 2020

Argh... OK, long time no updates. Well, the last code uploaded works and I have some functions, but I'm not happy yet, as it's working on Arduino but not on PlatformIO. What I have learned so far is that the Arduino IDE does a lot "under the hood", and with PlatformIO you need to set it up in more detail.

Read the following:

I ended up being able to write a file to the SPIFFS using PlatformIO. I was already able to do it with Arduino, but without really understanding how. Now I think maybe I got it: to do that, I had to set up the file platformio.ini and the directory tree following a set of steps, so let's see:

  1. I created a new project file in the "Projects" tab area in PlatformIO;
  2. Then I created a data folder at the same level as the src folder. Another option is to include a different path for the data folder in the platformio.ini file;
  3. Then, with the board connected, I run pio run -t uploadfs;
  4. Had to remove the Time library from PlatformIO, as it was conflicting with ESPAsyncWebServer, and here is where I found the relevant information.

It worked.

With that, I started understanding how I did not know what I was doing. The code I started with had the HTML included in the Arduino sketch. That bothered me. Now, with this new understanding, I can keep it apart from the sketch file.

Then I found this tutorial: ESP32 Web Server using SPIFFS

And this, in case I want to use the Arduino IDE plugin for the upload process.

I really wish I had understood it better before! So many days I spent on it!

The C++ code so far.


            /****************************************************************************
            *                                  Aknowledments                           * 
            *                                  by LucioPGN                             * 
            ****************************************************************************/
           /*  Up to this date: 07th of June 2020 I don't consider myself a programer 
            *  so I need to stand on top of giants sholders for my programing projects:
            *  A Portion of this code was based on Rui Santos Code;
            *  A Portion of this code was based on losely based Shakeels code for ESP8266;
            *  A Portion of this code was based on several websites I lost track of....!
            *  My contributions: 
            *     -So far I made it work on platformio :), that took me quite a lot of time
            *     -That means:
            *        +created a new project;
            *        +created a folder named data under the main folder (fabfarm_irrigation)
            *        +linked the platformio.ini to the folder of the project + the data folder
            *        +linked the needed libraries to their github repo in the platformio.ini
            *        +found a conflict with time library and ESPAsyncWebserverLibrary can be solved by renaming time library or by removing it
            *        +Made it work with a separate HTML file under the data folder using SPIFFS
            *        +I can load the data to Spiffs inside platformio
            *  Things I still want to program for my final project:
            *    -so far I ported Shakeels code into ESP32;
            *    -simplify the code creating functions rather than repeating the code;
            *    -try to separate the HTML part for a cleaner code (this applies to maybe mix up the other previous code to this);
            *    -Improve the appearance/Interface of the code
            *    -Add readings to HTML
            *    -Add a log of occurrences like over current
            *    -Add more safety for the equipment
            *    -Add a phone interface (APP)
            *    -Add function to set current time
            *    -Add renaming function to each relay so one can relate the relay to the area of interest or at least rename relays to actual areas of the farm.
            * 
            ****************************************************************************/
            
           
           //Required Libraries
           #include "WiFi.h"
           #include "ESPAsyncWebServer.h"
           #include "SPIFFS.h"
           #include <AsyncTCP.h>
           
           // Network Credentials
           const char* ssid = "rato";
           const char* password = "imakestuff";
           
           //Start the Async Web Server listening on port 80
           AsyncWebServer server(80);
           
           // Set to true to define Relay as Normally Open (NO)
           #define RELAY_NO false
           
           // Set number of relays, will be used in the array
           #define NUM_RELAYS  4
           
           // Assign each GPIO to a relay
           int relayGPIOs[NUM_RELAYS] = {26, 25, 33, 32};
           
           //
           const char* PARAM_INPUT_1 = "relay";  
           const char* PARAM_INPUT_2 = "state";
           
           // Replaces placeholder with button section in your web page
           String processor(const String& var){
             //Serial.println(var);
             if(var == "BUTTONPLACEHOLDER"){
               String buttons ="";
               for(int i=1; i<=NUM_RELAYS; i++){
                 String relayStateValue = relayState(i);
                 //Here parts of the HTML will be parsed to index.html like Relay # followed by its value in variable for the GPIO numbers
                 buttons+= "<h4>Turn on water on " + String(i) + "</h4><h4>Valve (relay) #" + String(i) + " - GPIO " + relayGPIOs[i-1] + "</h4><label class=\"switch\"><input type=\"checkbox\" onchange=\"toggleCheckbox(this)\" id=\"" + String(i) + "\" "+ relayStateValue +"><span class=\"slider\"></span></label>";
               }
               return buttons;
             }
             return String();
           }
           
           String relayState(int valveRelayNum){
             if(RELAY_NO){
               if(digitalRead(relayGPIOs[valveRelayNum-1])){
                 return "";
               }
               else {
                 return "checked";
               }
             }
             else {
               if(digitalRead(relayGPIOs[valveRelayNum-1])){
                 return "checked";
               }
               else {
                 return "";
               }
             }
             return "";
           }
           
           void setup(){
             // Serial port for debugging purposes
             Serial.begin(9600);
             // Initialize SPIFFS
             if(!SPIFFS.begin(true)){
               Serial.println("An Error has occurred while mounting SPIFFS");
               return;
             }
             // Set all relays to off when the program starts - if set to Normally Open (NO), the relay is off when you set the relay to HIGH
             for(int i=1; i<=NUM_RELAYS; i++){
               pinMode(relayGPIOs[i-1], OUTPUT);
               if(RELAY_NO){
                 digitalWrite(relayGPIOs[i-1], HIGH);
               }
               else{
                 digitalWrite(relayGPIOs[i-1], LOW);
               }
             }
             
             // Connect the ESP to the Wi-Fi using the credentials entered before
             WiFi.begin(ssid, password);
             while (WiFi.status() != WL_CONNECTED) {
               delay(1000);
               Serial.println("Connecting to WiFi..");
             }
           
             // Print ESP32 Local IP Address
             Serial.println(WiFi.localIP());
           
           /*
           *Now we are going to configure the route where server will be listening for incoming HTTP requests 
           and a function that will be executed when a request is received on that route.
           We specify this by calling the "on" method on the server object. With server.on(){}; 
           As first input, this method receives a string with the path where it will be listening. 
           We are going to set it to listen for requests on the "/" route. This could be anything. 
           It is basically what you write after the ip adress when in the browser or an APP.
           This website has a great explanation of the ESP32 Arduino: Asynchronous HTTP web server
           https://techtutorialsx.com/2017/12/01/esp32-arduino-asynchronous-http-webserver/
           So... 
           - First parameter here is: "/" thats the root directory.
           - Second parameter is HTTP_GET thats an enum of type WebRequestMethod a method defined in the library here --> https://github.com/me-no-dev/ESPAsyncWebServer/blob/63b5303880023f17e1bca517ac593d8a33955e94/src/ESPAsyncWebServer.h
           - Third parameter is a the function AsyncWebServerRequest
           So there is this c++ lambda function used here. My litle understanding is that they are locally declared unamed function this means they dont have a name and are declared locally :-)
            I don't grasp the concept fully haha.
            the syntax is [captures](params){body} where in here [] is empity
           */
           
             // Route for root / web page
             server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
               request->send(SPIFFS, "/index.html", String(), false, processor);
             });
           
            // Send a GET request to <ESP_IP>/update?relay=<inputMessage>&state=<inputMessage2>
             server.on("/update", HTTP_GET, [] (AsyncWebServerRequest *request) {
               String inputMessage;
               String inputParam;
               String inputMessage2;
               String inputParam2;
               // GET input1 value on <ESP_IP>/update?relay=<inputMessage>
               if (request->hasParam(PARAM_INPUT_1) & request->hasParam(PARAM_INPUT_2)) {
                 inputMessage = request->getParam(PARAM_INPUT_1)->value();
                 inputParam = PARAM_INPUT_1;
                 inputMessage2 = request->getParam(PARAM_INPUT_2)->value();
                 inputParam2 = PARAM_INPUT_2;
                 if(RELAY_NO){
                   Serial.print("NO ");
                   digitalWrite(relayGPIOs[inputMessage.toInt()-1], !inputMessage2.toInt());
                 }
                 else{
                   Serial.print("NC ");
                   digitalWrite(relayGPIOs[inputMessage.toInt()-1], inputMessage2.toInt());
                 }
               }
               else {
                 inputMessage = "No message sent";
                 inputParam = "none";
               }
               Serial.println(inputMessage + inputMessage2);
           
               //This is the last part of the lambda function. 
               //This method receives as first input the HTTP response code, which will be 200 in our case.  This is the HTTP response code for "OK".
               request->send_P(200, "text/plain", "OK");
             });
           
             // Start server here
             server.begin();
           }
           
           void loop(){
           
           }
        

Warning: hasParam(PARAM_INPUT_1) & hasParam(PARAM_INPUT_2) uses the bitwise &; the logical AND is &&. With two bools it happens to give the same result, but it does not short-circuit, and && is the right operator.

Current version of this code (E-SPlash on GitHub)

The HTML code so far.


<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    h2 {font-size: 3.0rem;}
    p {font-size: 3.0rem;}
    body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
    .switch {position: relative; display: inline-block; width: 120px; height: 68px} 
    .switch input {display: none}
    .slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 34px}
    .slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 68px}
    input:checked+.slider {background-color: #2196F3}
    input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
  </style>
</head>
<body>
  <h2>Fab Farm Irrigation System</h2>
  %BUTTONPLACEHOLDER%
<script>function toggleCheckbox(element) {
  var xhr = new XMLHttpRequest();
  if(element.checked){ xhr.open("GET", "/update?relay="+element.id+"&state=1", true); }
  else { xhr.open("GET", "/update?relay="+element.id+"&state=0", true); }
  xhr.send();
}</script>
</body>
</html>
                
The HTML file needs to be placed in a folder named "data", or at another address if specified in platformio.ini.

Current version of this code (E-SPlash on GitHub)

It currently looks like this:

Current interface

The appearance and the code right now are pretty much similar to those on the Random Nerd Tutorials website for relays controlled with ESP32.

The main difference from the original code is that it runs on PlatformIO for VS Code and also uses a separate folder structure for the HTML file, which is written to the SPIFFS of the ESP.


14th of June, 2020

After all this personal breakthrough, I decided I need to start the code from scratch and use only the general algorithm of the current irrigation system installed on the farm. The reason is that the code is too messy and repetitive; the approaches found in yesterday's sources made me realize it's faster to add the features to the latest code one by one than to recode everything from the code currently running on the farm.

Today I will attempt to add updated information, like temperature and humidity. I am again basing it on the work by Rui Santos on this page about a weather station. With this approach I believe I will also be able to include the time of day using an RTC clock.

The schematics for connecting the DHT22 are from his website:

DHT22 wiring

It uses a 4.7k Ohm resistor as a pull-up.

After some work, this is how the page looks:

Updated interface

The reason the temperature is now negative, I believe, is that the temperature and humidity sensor is either faulty or my wiring is a bit loose. I believe that once I mill my own board and solder everything, instead of plugging it into a breadboard, this will be more reliable. Anyway, I am getting the values and I am able to show them on the web interface.

On the serial monitor I am currently able to display the Internet time as well as the network information; now I need to find a way to get the time value and display it in the web interface. Currently I am sending the word TESTE through the code, and that is being displayed instead of the time.

Serial monitor output


15th of June, 2020

Today I will focus on getting the time displayed in the web interface and perhaps create a function to turn on the relay at a specific set time.

20:44 Update:

- After the whole day trying, I was able to display the machine time and the irrigation system (ESP32) time.

Now the interface looks like this:

Interface with time

When I grow up I want to be a programmer :-) and not spend an entire day on something someone puts together in a few lines!

My struggles were stopped by a saint with this answer on Arduino Stack Exchange.

Here follows the original function posted there:


void printLocalTime()
{
  time_t rawtime;
  struct tm timeinfo;
  if(!getLocalTime(&timeinfo))
  {
    Serial.println("Failed to obtain time");
   return;
  }
  char timeStringBuff[50]; //50 chars should be enough
  strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
  //print like "const char*"
  Serial.println(timeStringBuff);

  //Optional: Construct String object 
  String asString(timeStringBuff);
}

With minor modifications, changing it from a void function into a String function and adding a return, which I named "timeAsAString":


//this function was found here https://arduino.stackexchange.com/questions/52676/how-do-you-convert-a-formatted-print-statement-into-a-string-variable
//I did a minor change so instead of a void function it now returns a string to be used to show time in the webinterface
String printFarmTime()
{
  time_t rawtime;
  struct tm timeinfo;
  getLocalTime(&timeinfo);
  char timeStringBuff[50]; //50 chars should be enough
  strftime(timeStringBuff, sizeof(timeStringBuff), "%A, %B %d %Y %H:%M:%S", &timeinfo);
  //print like "const char*"
  Serial.println(timeStringBuff);

  //Construct to create the String object
  String timeAsAString(timeStringBuff);
  return timeAsAString;
}

Warning: this version drops the if(!getLocalTime(&timeinfo)) check, so if NTP has not answered yet it formats an empty (garbage) time. Keep the check and return something like "time not set" when it fails.

So, with that out of the way, I will try to create the functions to turn the relays on and off according to the scheduled time. Let's see if I can finish it today...


Conclusion

As for this assignment, I am happy with the point I am at now, but I will develop the web interface further for the final project. You can pick up from where this assignment stops and continue on 16th of June, 2020 in the Project development page.

I had a meeting with a friend of mine, Jeff Knight, who is a professional programmer, and he helped me draft a sketch of a plan for where I want to take my final project and where we are. It's below and is part of the README.md of the GitHub page I created for the software I am writing for my final project.

In the video below I show the functionality.

Check the high-resolution version on YouTube

Plan Sketch

HTML <--> JavaScript <--> C++

Web Interface Microprocessor (ESP32)
Set per zone: Receives:
[ ] start times [ ] start times
[ ] duration of cycle, max 1 hour [ ] Duration of cycle
[ ] Override button
Displays [ ] Stores info above in SPIFFS (csv?)
[x] time of day logic test:
[x] Humidity keep checking: ({
[x] Temperature - am I ('zone x') in an On window ('time + duration') ?
Yes: 'be sure' I'm on
No: be sure I'm off
[ ] current zone programs stored -wait for override button and break cycle if received
[ ] override button wait for override button and turn on/off anytime
[ ] current zone state (ON/OFF) Updates/sends current situation:
[x]Time of day;
[x]State of zone (ON/OFF)
[x]sensor information (humidity/temperature)

microprocessor

On Startup:

Problem:

csv file: zone1, 20200101 00:00:00.0000, 1234, 1, 12 zone2, 20200101 00:00:00.0000, 1234, 1, 16 zone3, 20200101 00:00:00.0000, 1234, 1, 18

When do I touch this?

read the csv file. for each row: split on ',' into array ....

We need to save state:

This was a first plan for the final project web interface, and by the time you are reading this it's already more developed.

← (May 06) Networking and Communications · all weeks · Mechanical Design, Machine Design (May 20) →