SOLVED Receiving API calls on a ESP-01 (API reversed)

I'm building an individually addressable led lamp, as the controller I'm using an ESP-01 (ESP8266). As the primary "controlling environment", I want to use a mobile app (or website), and secondary an IR controller. With IR it's no problem, but the mobile app...

I've got it to receive commands from MIT AppInventor2 by connecting a WiFi element to ESP's local address with a command behind a slash (and optionally a value) http://x.x.x.x/command/value/ and read it using String request = client.readStringUntil('\n');. But I find the MIT AI2 very limiting. I've found FlutterFlow, which would be way better, but when I try to make an API call there, it returns an error.
Any suggestions on what might be wrong? Does it need to be https for FlutterFlow? Is it a problem with it being only local?

Here is the code (I wrote it into a custom library, which might have been a waste of time...):

//Include libraries
#include <Arduino.h>

#include <myWifi.h>

//Pin Config
#define PIN_STATUS_LED 2  // Builtin led used to display status


//CONFIG
#define BAUDRATE      74880  // Baudrate for Hardware Serial
#define LOOP_TIME       100  // Run loop() every [x] ms
#define BEHIND_LOOP_WARN 40  // If target loop time is [x] ms behind millis(), print warning to Serial

#define MY_SSID  "O2-Internet-826" // SSID of the wifi network
#define MY_PASS  "bA6RfeFT"        // Password of the wifi network

//Define variables, etc

std::map<String, std::function<void(int)>> callbacks = {
    {"/def/", [](int value){ Serial.println(F("Connected to def")); }},
};

myWiFi wifi(MY_SSID, MY_PASS, IPAddress(10,0,0,58), callbacks);

void setup()
{
    Serial.begin(BAUDRATE);
    delay(100);
    Serial.println('\n');

    wifi.connect();
}

void loop(){
    wifi.update();
}

myWiFi.h:

#ifndef WIFI_H
#define WIFI_H

#include "Arduino.h"
#include "map"
#include "ESP8266WiFi/src/ESP8266WiFi.h"

/*Usage:
    myWiFi wifi(SSID, PASS, IPAddress, ipCallbacks, connectionUpdateCallback);
    wifi.connect(); once in setup
    wifi.update(); often in loop*/
class myWiFi {
    public:
        myWiFi(String SSID, String PASS, IPAddress IP,
                std::map<String, std::function<void(int)>> ipCallbacks);
        ~myWiFi();
        void connect();
        void update();
    protected:
        String SSID;
        String PASS;
        IPAddress IP;
        IPAddress GATEWAY;
        IPAddress SUBNET;
        std::map<String, std::function<void(int)>> ipCallbacks;
};

#endif //WIFI_H

myWiFi.cpp:

#include "myWifi.h"

WiFiServer ESPserver(80);        // Service Port


myWiFi::myWiFi(String SSID, String PASS, IPAddress IP,
                std::map<String, std::function<void(int)>> ipCallbacks) {
    this->SSID = SSID;
    this->PASS = PASS;
    this->IP = IP;
    GATEWAY = IPAddress(192,168,1,1);
    SUBNET = IPAddress(255,255,255,0);
    this->ipCallbacks = ipCallbacks;
}

myWiFi::~myWiFi() {
    
}

void myWiFi::connect() {
    Serial.printf_P((const char *)F("Connecting to: %s with a static IP: %s\n"), SSID.c_str(), IP.toString());

    WiFi.config(IP, GATEWAY, SUBNET);

    WiFi.begin(SSID, PASS);

    while (WiFi.status() != WL_CONNECTED) {
        delay(150);
        Serial.print(F("*"));
    }

    ESPserver.begin();
    Serial.printf_P((const char *)F("\nWifi connected, server started at http://%s/\n\n"), WiFi.localIP().toString());
}

void myWiFi::update() {
    if (WiFi.status() != WL_CONNECTED) {
        return;
    }

    // Check if a client has connected
    WiFiClient client = ESPserver.accept();
    if (!client) {
        return;
    }

    // Wait until the client sends some data
    int i = 0;
    while(!client.available()) {
        i++;
        delay(50);
        if(i > 10) {
            break;
        }
    }
    if (!client.available()) {
        return;
    };

    // Read the first line of the request
    String request = client.readStringUntil('\n');
    client.flush();

    Serial.println(request);

    // Match the request
    request.remove(0,4);
    request.remove(request.length()-10,10);

    int value = 0;
    int index = request.lastIndexOf("/")+1;

    try {
        value = request.substring(index).toInt();
    } catch (const std::exception& e) { value = 0; }

    request.remove(index, request.length()-index);

    if (this->ipCallbacks.find(request) != this->ipCallbacks.end()) {
        this->ipCallbacks[request](value);
    }

    // Return the response
    client.println(F("HTTP/1.1 200 OK"));
    client.println(F("Content-Type: text/html"));
    client.println(""); // IMPORTANT
    client.print(F("Request recieved!"));
    delay(1);
}

Thanks a lot for your help, Viktor

Just found out, that you can't download the app from FlutterFlow without paying. Any suggestions for other easy mobile app IDEs or tuts on how to make a website work?

I tend to use Android Studio - more powerful and flexible than MIT AppInventor but it is far more difficult to use
do you have write your own smartphone app?
you could run a webserver on the ESP-01 and connect to it using a standard web browser
e.g. se ESP0101S-RELAY-MODULE-TUTORIAL switch a relay on/off using a web browser

Using the server; would it mean, that it could be used from any device on the local network? That would be better, the only problem is that I have absolutely no idea how to do it... I'll take a look at the tutorial you provided (thanks!) and see if it's too much for me to handle or not...

Ok, I've seen this tutorial, and I don't find it that helpful in terms of how to run the web server... I'm already using the same way to detect the client on the "IP address tree", but the problem is displaying anything else than a simple line of text (like "Request received!" or something) on the web page...

Edit: I used this tut: Control a Relay using ESP8266 and Android MIT App Inventor to do the thing.

you will have to implement a web web page to meet your requirements - do you have a specification
the ESP-01S has 1M of flash so you should have plenty of space

I can understand that, but I have absolutely no knowledge on web pages... If I understand it right, I have to just return an "HTML script" (no idea what it's called) instead of e.g. "Request received!". Right?

I use a web interface for my esp code. It is more versatile because it can be used by any device that supports a web browser. I also set up a mDNS server so the esp device can be called by name. This does require learning a minimal amount of HTML. Here is a simple example for a millis() driven state machine blinking a LED that I did for another thread. Ignore checkPattern() and everything after the first two lines of loop() for a start.

/* ***********************************************************************************************
 * 
 * oldcurmudgeon November 6 2021
 * 
 * This sketch is for an ESP8266 to provide a web interface for blinking an LED
 *
 * The onboard LED is used so no additional hardware is needed
 *
 * It is implemented as 2 state machines. The first has the state defined by the pattern
 * being run. The state is defined by currentPattern. The state transition is done via 
 * a browser request changing newState to define the next state.
 *
 * The second state machine defines the step in the pattern of blinks. The state transition
 * occurs when the current time for the LED state expires. When the time is up the LED toggles
 * to the other state and the time for that state starts. At the end of the pattern it is
 * started over from the beginning. 
 * 
 *************************************************************************************************/ 


#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

const char* ssid = "your-ssid";
const char* password = "your-pw";

ESP8266WebServer server(80);

#include <string.h>
#include <Streaming.h>   //  from "Streaming by Mikal Hart" in library manager

// Pattern selection state machine variables
volatile uint16_t newPattern = 0;  //default to first pattern volatile because it is set asynchronously
uint16_t currentPattern = 0; // default first pattern

// Pattern step state machine variables
byte step = 0;                // current step in pattern default to start
unsigned long currentTime;   // current time for test
unsigned long startTime;     // start time of this step

// led on state - the onboard led is active low (when the pin is low the led
// is on). If you are using some other pin to drive the led so that the led is on when
// the pin is high this should be changed to one.
const byte LED_ON = 0; 
byte ledState = LED_ON;     // current led state default on

// led patterns - this is a series of numbers defining the led on and off times
// This example is 4 patterns of 6 changes. A zero before the end of the pattern
// indicates a short pattern that restarts when it detects a zero.
const byte NUM_PATTERNS = 4;    // number of patterns 
const byte PATTERN_LEN = 6;    // pattern length
const unsigned long pattern[ NUM_PATTERNS][PATTERN_LEN] = 
        {{200,200,200,200,200,200},
        {1500,1500,1500,1500,1500,01500},
        {1500,1500,200,200,0,0},           // short pattern example 
        {1500,1500,1500,1500,200,200}};
/* ***************************************************************************
*
* Handle root web page
*
******************************************************************************/
void handleRoot() {
  if (server.hasArg("pattern")){ // the browser has sent a response
    // Save new value from web page browser response  
    newPattern = server.arg("pattern").toInt();
  }
   /* Build html page
    * 
    * The new lines (\n) are not required by the browser - they are formatting for a human 
    * looking at the page source on the browser
    * 
    * The "String((newPattern == x) ? " checked" : " ")"  construct is used to set the
    * pattern selected as checked when the web page is refreshed. If newPage is equal to
    * the value "checked" is added to the attributes, otherwise a blank is added. You can
    * see the result on the web page by right-click then select "View Page Source".
    * This will show the actual HTML sent to the browser.
   */ 
  String htmlPage =
    String("<!DOCTYPE HTML>\n") 
    + "<html lang='en'>\n"
    + "<head>\n"
    + "<!-- Ignore request for nonexistent favicon -->\n"
    + "<link rel='icon' href='data:,'>\n"
    + " <meta name='viewport' content='width=device-width, initial-scale=1.0'>"
    +"</head>\n"
    + "<body>\n\n"
    + "<h1 style='text-align: center;'>LED Pattern Blinker</h1>\n"
    + "<div>\n"
    + "<form style='font-size: 20px'>\n"
    +   " <input type='radio' name='pattern' value='0' required"
    +  String((newPattern == 0) ? " checked" : " ")        
    +   " > Short blink<br>\n"
    +   " <input type='radio' name='pattern' value='1' required"
    +  String((newPattern == 1) ? " checked" : " ") 
    +   "> Long blink<br>\n"
    +   " <input type='radio' name='pattern' value='2' required"
    +  String((newPattern == 2) ? " checked" : " ") 
    +   "> Long Short blink <br>\n"
    +   " <input type='radio' name='pattern' value='3' required"
    +  String((newPattern == 3) ? " checked" : " ") 
    +   "> Long Long Short blink<br>\n"
    +   "<input type='submit' value='Set Pattern'>\n"
    +   "</div>\n"
    +"</body>\n</html>\n";

  server.send(200, "text/html",htmlPage);
    
}

void handleNotFound() {
    // no clue what they sent
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";

    for (uint8_t i = 0; i < server.args(); i++) {
      message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }

    server.send(404, "text/plain", message);

}

void setup() {
  pinMode(LED_BUILTIN,OUTPUT);

  Serial.begin(19200);
   

  // Set up WiFi network
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  // Set up mDNS responder:
  // - first argument is the domain name, in this example
  //   the fully-qualified domain name is "esp8266.local"
  // - second argument is the IP address to advertise
  //   we send our IP address on the WiFi network
  if (!MDNS.begin("esp8266")) {
    Serial.println("Error setting up MDNS responder!");
    while (1) {
      delay(1000);  // hang forever
    }
  }
  Serial.println("mDNS responder started");

  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);
  
  // Start the server
  server.begin();
  Serial.println("TCP server started");

  // Add service to MDNS-SD
  MDNS.addService("http", "tcp", 80);
  
  startTime = millis();      // get current time for start of pattern
}

void checkPattern() {
    if (newPattern != currentPattern){     // browser has sent a new pattern
        // switch to new pattern
        ledState = LED_ON;  // start with led on
        digitalWrite(LED_BUILTIN,ledState);  // start with led on
        step = 0;   // start at beginning of pattern
        currentPattern = newPattern;  // switch to new pattern
        startTime = millis();      // get current time for start of pattern
    } 
};    

void loop() {
    
  MDNS.update();
     
  server.handleClient();      // check for connection request
  
  // Check to see if there is a new pattern request. If the check is done here
  // the new pattern starts immediately. If you want the new pattern to start 
  // after the current pattern has completed, comment out the line below and uncomment
  // the one in the end of pattern processing
  checkPattern();
  
  currentTime = millis();      // get current time for compare
  if (currentTime  - startTime > pattern[currentPattern][step]) {
     // done with this step 
     ledState ^= 1;  // toggle led state
     digitalWrite(LED_BUILTIN,ledState);  // change led state       
     step++;             // next step
     startTime = currentTime;  // start next led pattern step time
     if ((step >= PATTERN_LEN)                    // end of pattern
         || (pattern[currentPattern][step]==0)){  // or short pattern
        // end of pattern processing     
        step = 0;   //restart pattern
        
        // Check to see if there is a new pattern request. If the check 
        // is done here the new pattern starts after the current pattern
        // has completed. (Remember to comment out the call above.)
       // checkPattern();        
     }
  }     
}  

Thanks a lot, but it'd be a lot more useful if I could run the code and see what it does...
Do I need any extra hardware, or is it just the ESP-01? Do I need to set the mDNS (no idea what it actually is) elsewhere than in the code? The router maybe?
Edit: @oldcurmudgeon

It is self contained all you need is an ESP-01. The mDNS statements set up a name server on the esp8266 so that you can connect by name rather than IP address. The line

 if (!MDNS.begin("esp8266")) {

sets the name. You can replace the name in quotes with whatever makes sense to you. The actual web page is esp8266.local in this case. The line

server.handleClient();      // check for connection request

handles the server request from the web browser. If a request comes it determines what routine to call. The lines

  server.on("/", handleRoot);
  server.onNotFound(handleNotFound);

set up the routines to call. In this case we just have a root page, so everything else is treated as not found with an error message. The initial page looks like this

After selecting an option and clicking Set Pattern the pattern argument is set.

This is tested and processed by the lines

  if (server.hasArg("pattern")){ // the browser has sent a response
    // Save new value from web page browser response  
    newPattern = server.arg("pattern").toInt();
  }

in the handleRoot() routine.

Thanks a lot, I've tried to partly copy, partly write this into my script and was not expecting it to work. But it did! Even the mDNS (which did not work in your script for me...)!

Again, thanks a lot. It is still quite limiting, but much better and more versatile than an basic app. And I think it's just that I saw html for the first time... The biggest challenge was a colour picker, which is really simple in html.

There are online tutorials for HTML. I like this one.

I would suggest going in order for the first few topics to get the basics. After that you can skip around a bit for specific things.

Oh, thank you again so much :smiley:

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.