B4R Question How to convert this code to create a web server

barx

Well-Known Member
Licensed User
Hi,

As an example of how to use the ESP826 (Wemos), how would we go about creating thi sample project?

Thanks

B4X:
#include <ESP8266WiFi.h>

//////////////////////
// WiFi Definitions //
//////////////////////
const char WiFiAPPSK[] = "sparkfun";

/////////////////////
// Pin Definitions //
/////////////////////
const int LED_PIN = 5; // Thing's onboard, green LED
const int ANALOG_PIN = A0; // The only analog pin on the Thing
const int DIGITAL_PIN = 12; // Digital pin to be read

WiFiServer server(80);

void setup()
{
  initHardware();
  setupWiFi();
  server.begin();
}

void loop()
{
  // Check if a client has connected
  WiFiClient client = server.available();
  if (!client) {
    return;
  }

  // Read the first line of the request
  String req = client.readStringUntil('\r');
  Serial.println(req);
  client.flush();

  // Match the request
  int val = -1; // We'll use 'val' to keep track of both the
                // request type (read/set) and value if set.
  if (req.indexOf("/led/0") != -1)
    val = 0; // Will write LED low
  else if (req.indexOf("/led/1") != -1)
    val = 1; // Will write LED high
  else if (req.indexOf("/read") != -1)
    val = -2; // Will print pin reads
  // Otherwise request will be invalid. We'll say as much in HTML

  // Set GPIO5 according to the request
  if (val >= 0)
    digitalWrite(LED_PIN, val);

  client.flush();

  // Prepare the response. Start with the common header:
  String s = "HTTP/1.1 200 OK\r\n";
  s += "Content-Type: text/html\r\n\r\n";
  s += "<!DOCTYPE HTML>\r\n<html>\r\n";
  // If we're setting the LED, print out a message saying we did
  if (val >= 0)
  {
    s += "LED is now ";
    s += (val)?"on":"off";
  }
  else if (val == -2)
  { // If we're reading pins, print out those values:
    s += "Analog Pin = ";
    s += String(analogRead(ANALOG_PIN));
    s += "<br>"; // Go to the next line.
    s += "Digital Pin 12 = ";
    s += String(digitalRead(DIGITAL_PIN));
  }
  else
  {
    s += "Invalid Request.<br> Try /led/1, /led/0, or /read.";
  }
  s += "</html>\n";

  // Send the response to the client
  client.print(s);
  delay(1);
  Serial.println("Client disonnected");

  // The client will actually be disconnected
  // when the function returns and 'client' object is detroyed
}

void setupWiFi()
{
  WiFi.mode(WIFI_AP);

  // Do a little work to get a unique-ish name. Append the
  // last two bytes of the MAC (HEX'd) to "Thing-":
  uint8_t mac[WL_MAC_ADDR_LENGTH];
  WiFi.softAPmacAddress(mac);
  String macID = String(mac[WL_MAC_ADDR_LENGTH - 2], HEX) +
                 String(mac[WL_MAC_ADDR_LENGTH - 1], HEX);
  macID.toUpperCase();
  String AP_NameString = "ESP8266 Thing " + macID;

  char AP_NameChar[AP_NameString.length() + 1];
  memset(AP_NameChar, 0, AP_NameString.length() + 1);

  for (int i=0; i<AP_NameString.length(); i++)
    AP_NameChar[i] = AP_NameString.charAt(i);

  WiFi.softAP(AP_NameChar, WiFiAPPSK);
}

void initHardware()
{
  Serial.begin(115200);
  pinMode(DIGITAL_PIN, INPUT_PULLUP);
  pinMode(LED_PIN, OUTPUT);
  digitalWrite(LED_PIN, LOW);
  // Don't need to set ANALOG_PIN as input,
  // that's all it can be.
}
 

Erel

Administrator
Staff member
Licensed User
You can create a sockets server, it is not a http server.

It is very similar to the Ethernet example: https://www.b4x.com/android/forum/threads/ethernet-network-tutorial.65664/#content

B4R code:
B4X:
Sub Process_Globals
  Public Serial1 As Serial
  Private wifi As ESP8266WiFi
  Private server As WiFiServerSocket
  Private const serverPort As UInt = 51042
  Private astream As AsyncStreams
  Private bc As ByteConverter
  Private timer1 As Timer
End Sub

Private Sub AppStart
  Serial1.Initialize(115200)
  Log("AppStart")
  If wifi.Connect("dlink") = False Then
  Log("Error connecting to network.")
  Return
  Else
  Log("Connected to network. My ip address: ", wifi.LocalIp)
  End If
  server.Initialize(serverPort, "server_NewConnection")
  server.Listen
  timer1.Initialize("timer1_Tick", 1000)
End Sub

Sub Timer1_Tick
   astream.Write("Time here: ").Write(NumberFormat(Millis, 0, 0))
End Sub

Sub server_NewConnection (NewSocket As WiFiSocket)
  Log("New connection")
  astream.Initialize(NewSocket.Stream, "astream_NewData", "astream_Error")   
  timer1.Enabled = True
End Sub

Sub astream_NewData (Buffer() As Byte)
  Log("Received: ", bc.HexFromBytes(Buffer))
End Sub

Sub astream_Error
  Log("Error")
  server.Listen
  timer1.Enabled = False
End Sub

B4J code (non-ui):

B4X:
Sub Process_Globals
  Private sock As Socket
  Private astream As AsyncStreams
End Sub

Sub AppStart (Args() As String)
  sock.Initialize("sock")
  sock.Connect("192.168.0.10", 51042, 10000) 'change to Arduino ip
  StartMessageLoop
End Sub

Sub sock_Connected (Successful As Boolean)
  If Successful Then
  astream.Initialize(sock.InputStream, sock.OutputStream, "astream")
  astream.Write(Array As Byte(1, 2, 3, 4))
  End If
End Sub

Sub astream_NewData (Buffer() As Byte)
  Log(BytesToString(Buffer, 0, Buffer.Length, "utf8"))
End Sub
 
Upvote 0

barx

Well-Known Member
Licensed User
Which isn't a http server?
The above example is probably not the best, I am wanting to create a sketch that activates the WiFi AP. the user then uses a browser to login to AP and view web page. The web page will then list the local Wifi netwoks and you pick on and enter the credentials for it. then the unit will connect to that wifi network. I'm sure you are aware, this is how a lot of IoT market products work.

Let me post my test arduino sketch that create a page. I created this just to test the wemos board and then to expand on for the project:

B4X:
#include <ESP8266WiFi.h>

const char WiFiAPSSID[] = "Wemos";
const char WiFiAPPSK[] = "password";

WiFiServer server(80);

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);
  setupWifi();
  server.begin();
}

void loop() {
  // put your main code here, to run repeatedly:
  WiFiClient client = server.available();
  if (!client) {
    return;
  }

  //Read the first line of the request
  String req = client.readStringUntil('\r');
  Serial.println(req);
  client.flush();

  //HTTP Header
  String s = "HTTP/1.1 200 OK\r\n";
  s += "Content-Type: text/html\r\n\r\n";
  s += "<!DOCTYPE HTML>\r\n<html>\r\n<body>\r\n";

  //routes
  if (req.indexOf("/login") != -1) {
    s += "Login";
  }
  else if (req.indexOf("/account") != -1) {
    s += "account";
  }
  else {
    //home
    Serial.println("home");
    s += "Some Welcome message";
  }

  //HTML closure
  s += "</body>\r\n</html>\n";

  client.print(s);
  delay(1);
  Serial.println("Client disconnected");

}

void setupWifi()
{
  WiFi.mode(WIFI_AP);
  WiFi.softAP(WiFiAPSSID, WiFiAPPSK);
}

As you can see,I also have simplle routing.

So with the above example, do I create a web socket with port 80 and then is aSync used to build / output the HTML?

I'm not the brightest spark, so if I'm being n00b, just bear with me, I'll get it in the end ;)

Thanks
 
Upvote 0

barx

Well-Known Member
Licensed User
I already had (have) the access point setup but I cannot see how the request / response is done. I'll jusst carry on the project in arduino ide, cheers.
 
Upvote 0

barx

Well-Known Member
Licensed User
I have made an effort but not got far

B4X:
#Region Project Attributes
    #AutoFlushLogs: True
    #CheckArrayBounds: True
    #StackBufferSize: 300
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'Public variables can be accessed from all modules.
    Public Serial1 As Serial
    Private WiFi As ESP8266WiFi
    Private server As WiFiServerSocket
    Private aSync As AsyncStreams
    Private bc As ByteConverter
    'Private timer1 As Timer
   
End Sub

Private Sub AppStart
    Serial1.Initialize(115200)
    Log("AppStart")
    If WiFi.StartAccessPoint2("ProPV", "password") Then
        Log("Access point Available")
        Log("IP = ", WiFi.AccessPointIp)
       
        server.Initialize(80, "server_NewConnection")
        server.Listen
       
    End If
End Sub

Sub server_NewConnection (NewSocket As WiFiSocket)
    Log("connected")
    aSync.Initialize(NewSocket.Stream, "aSync_NewData", "aSync_Error")
End Sub

Sub aSync_NewData (Buffer() As Byte)
    'Log("Received: ", Buffer)
    'aSync.WaitForMoreDataDelay(bc.IntsToBytes(Array As Int(500)))
    Dim s As String
    'routing
    If (bc.IndexOf(Buffer, bc.StringToBytes("GET /login")) <> -1) Then
        'return login
        Log("login - ", Buffer.Length)
        Log("writing")
        aSync.Write(bc.StringToBytes("HTTP/1.1 200 OK\r\n"))
          aSync.Write(bc.StringToBytes("Content-Type: text/html\r\n\r\n"))
          aSync.Write(bc.StringToBytes("<!DOCTYPE HTML>\r\n<html>\r\n<body>\r\n"))
        aSync.Write(bc.StringToBytes("Welcome to ProPV<br>\r\n"))
        aSync.Write(bc.StringToBytes("login"))
        aSync.write(bc.StringToBytes("</body></html"))
        Return
    Else If (bc.IndexOf(Buffer, bc.StringToBytes("GET /account")) <> -1) Then
         'return account
        Log("account")
        Return
    Else
        'return home
        Log("home")
        Return
    End If
   
End Sub

Sub aSync_Error
    Log("Error")
    server.Listen
End Sub

I can connect to the AP and kind of get to the right route.

if I visit wemos_ip/login i get the following in the log

B4X:
connected
login - 100
writing
home
home
home
home

nothing gets returned to the browser and for some reason it sees new data multiple times. I tried the 'WaitforMoreDataDelay' to wait for the buffer to fill before i read it but I don't seem to get that function to work. I'm probably miles away but this is the only way I could get close.
 
Upvote 0

Erel

Administrator
Staff member
Licensed User
Start with this example:
B4X:
Sub Process_Globals
  'These global variables will be declared once when the application starts.
  'Public variables can be accessed from all modules.
  Public Serial1 As Serial
  Private WiFi As ESP8266WiFi
  Private server As WiFiServerSocket
  Private aSync As AsyncStreams
  Private bc As ByteConverter
  'Private timer1 As Timer
   
End Sub

Private Sub AppStart
  Serial1.Initialize(115200)
  Log("AppStart")
  If WiFi.Connect("dlink") Then
  Log("Access point Available")
  Log("IP = ", WiFi.LocalIp)
   
  server.Initialize(80, "server_NewConnection")
  server.Listen
   
  End If
End Sub

Sub server_NewConnection (NewSocket As WiFiSocket)
  Log("connected")
  aSync.Initialize(NewSocket.Stream, "aSync_NewData", "aSync_Error")
End Sub

Sub aSync_NewData (Buffer() As Byte)
  Log(Buffer)
   If bc.IndexOf(Buffer, "GET") <> -1 Then
     If bc.IndexOf(Buffer, "GET /login") <> -1 Then
       aSync.Write("HTTP/1.1 200 OK").write(CRLF)
       aSync.Write("Content-Type: text/html").Write(CRLF).Write(CRLF)
       aSync.Write("<b>Hello World</b><br/><a href=""https://www.b4x.com"">B4X</a>")
    Log("login - ", Buffer.Length)
     Else
       aSync.Write("HTTP/1.1 404").Write(CRLF)
     End If
     CallSubPlus("CloseConnection", 100, 0)
   End If
End Sub

Sub CloseConnection(u As Byte)
   Log("writing")
   server.Socket.Close
End Sub

Sub aSync_Error
  Log("Error")
  server.Listen
End Sub

Note that if you want to create a real web server then you should use a Raspberry Pi with B4J. This will allow you to easily create very powerful web servers.
 
Upvote 0

barx

Well-Known Member
Licensed User
OK, I worked with that Erel and still didn't get an output. I then added

B4X:
server.Docket.Stream.Flush

Just before closing the socket and Voila I got the response in the browser.

As for a powerful web server. This project doesn't need to be powerful, it is very basic. The wemos will be used as a datalogger, reading data from an RS485 connected device and upload the data to an online API. The built in web server will just allow the user to connect the module to their wifi network. So they connect to AP, see a list of available networks, pick theirs and enter credentials, then the wemos connects to the wifi. Well that is the plan anyway. Thinking further into the future though I will probably need to add eeprom to store the chosen wifi details so that it can re-connect after a power cycle. Not sure if there is built in eeprom or not, will need to check...
 
Upvote 0
Top