Android Question ESP8266 - ESP32 OTA Update from B4A WebView [SOLVED]

max123

Well-Known Member
Licensed User
Longtime User
Hi all,

for my 3D Printing Host app I need to do ESP8266-ESP32 OTA updates directly inside the app instead of use external browser.

I never used WebView for my projects before now, so I have some problems to know how to do it.

With current code I wrote I'm able to see on the WebView the ESP OTA update page, but when I press the button to choose a binary file (.bin)
the WebView show a message that says there in no application to do it.

This is the code I wrote, next I will put my questions:
B4A Code:
WebView1.JavaScriptEnabled = True  ' Already enabled by default
'WebView1.ZoomEnabled = False
'WebView1.Zoom(True)
WebView1.Color = Colors.LightGray
WebView1.SendToBack
        
Dim wve As WebViewExtras
wve.addWebChromeClient(WebView1, "wve")
wve.addJavascriptInterface(WebView1, "b4a") ' Is this required ???

Dim WebViewSettings1 As WebViewSettings
Log("UserAgent before: " & WebViewSettings1.getUserAgentString(WebView1))

Dim UserAgent As String = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/20100101 Firefox/4.0"
WebViewSettings1.setUserAgentString (WebView1, UserAgent)
'WebViewSettings1.setDefaultZoom(WebView1, "FAR")
'WebViewSettings1.setUseWideViewPort(WebView1, True)
'WebViewSettings1.setLoadWithOverviewMode(WebView1, True)
    
Log("Save passwords: " & WebViewSettings1.getSavePassword(WebView1))
Log("UserAgent after: " & WebViewSettings1.getUserAgentString(WebView1))

..........
..........

Sub btnStart_On(Index As Int, Tag As String)
    btnStart.DisableNoMask
    Dim URL As String = txtHost.Text.Trim
    WebView1.LoadUrl(URL)
    Log("Loading URL: " &  WebView1.Url)
    ToastMessageShow("Loading URL: " & WebView1.Url, False)
    
    WriteSlotVal(79, URL)
End Sub

Sub btnStop_On(Index As Int, Tag As String)
    btnStart.Enabled = True
    WebView1.StopLoading
    Sleep(400)
    btnStart.Value = 0
    btnStop.Value = 0
End Sub

Sub WebView1_UserAndPasswordRequired (Host As String, Realm As String) As String()
    ToastMessageShow(Host & " require Username and Password to access: " & Realm, True)
    Log(Host & " require Username and Password to access: " & Realm)
    Return Array As String("admin", "password") ' OTA update page, we set on ESP to require a login
End Sub

Sub WebView1_OverrideUrl (Url As String) 'As Boolean
    ToastMessageShow("Override URL: " &  Url, False)
    Log("Override URL: " &  Url)
End Sub

Sub WebView1_PageFinished (Url As String)
    ToastMessageShow("PageFinished: " &  Url, False)
    Log("PageFinished: " & Url)
End Sub

My questions are:

1) I've used UserAndPasswordRequired sub to pass the username and password, after I added this It successfull login and I see the OTA update page, but want I want is to show the original login (as showed on browser) so the user insert username and password itself directly on the login view, is that possible?

2) When I load a page, pressing on button to choose a binary file, a message show that there are no applications to do it, I do not khow how I can handle it... Maybe need an Intent or I'm wrong ?

Attached some images of attemp on the app and the original login from Android FireFox browser.

Many thanks for any clarification.
 

Attachments

  • Page_loaded.jpg
    99.6 KB · Views: 343
  • No_app_found.jpg
    69.3 KB · Views: 337
  • Firefox_login.jpg
    97.4 KB · Views: 365

max123

Well-Known Member
Licensed User
Longtime User
Hi Ivica,

Your FIX in the last ShowFileChooser SOLVED completely the problem, it works well now, like Erel's code but because no limitations you says on ContentChooser I think better, It even show the right file name in the web page.

After this worked I've tried to adapt to my app, the OTA process works perfectly, I reflashed ESP32 at least 20 times without problems.
When the OTA update finish the ESP send a page with Success! Rebooting..... then ESP reboots, then start executing the new firmware.

The only big problem I found is that after OTA process completes, after a couple of seconds the app crash unaxpectately with this error relative on page overloaded, but seem not to be in mine code but inside B4X WebView Wrapper.

I've tried to comment the WebView1_OverrideUrl but nothing changed, the app crash after OTA succesful update.
Tried in Debug Mode, but the error is always the same, a wrong cast from Boolean and String and the log do not show the B4A line where error occour.

Here a relevant part of the log, do you have suggestions to fix it ?

Many thanks
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
There no other code from OverrideUrl, it is Async operation, after OTA Update B4A do nothing.... maybe in background.
In the OverrideUrl function I just put
B4X:
 Log ("OverrideUrl: " & Url)
If I comment the function nothing change.
 
Last edited:
Upvote 0

Ivica Golubovic

Active Member
Licensed User
There no other code from OverrideUrl, it is Async operation, after Ota Update B4A do norhing.... maybe in background. In the OverrideUrl I just put Log ("OverrideUrl: " & Url). If I comment the function nothing change
You must return Boolean value at the end of event.
Example::
Private Sub WebView1_OverrideUrl (Url As String) As Boolean
    'Your code here
    Return False 'Must have
End Sub
Have you implemented the WebViewExtra library?
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Here the offending line in the B4X WebView Wrapper:
https://github.com/AnywhereSoftware...wheresoftware/b4a/objects/WebViewWrapper.java
Line 65

Many thanks I will try it... but because if I comment at all the function it always return the error ?
It is mandatory to have the function in the code ?

Are these 2 permissons in the manifest required for my use case ?
B4X:
'Important
SetApplicationAttribute(android:usesCleartextTraffic,"true")
AddPermission(android.permission.DOWNLOAD_WITHOUT_NOTIFICATION)
Have you implemented the WebViewExtra library?
No, just the inline code you posted. No other libraries.

Many thanks
 

Attachments

  • Screen Shot 01-31-22 at 05.52 PM.PNG
    75.5 KB · Views: 218
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Returnig False in the OverrideUrl function seem to fixed the problem, for now I do not have crashes, just it respond with a page Success! Rebooting, ESP then send other messages probably and the PAGE NOT FOUND appear in the webview, but this not a really problem, maybe I will load next a blank HTML page.

Now I need to implement a small custom screen login so the user can change password and login, so no way to show some default login screen?
I need to create it custom and so it appear in the WebView1_UserAndPasswordRequired function ?

For the progress I need to see, the ArduinoOTA class show in the serial log one point (.) every some bytes (this is an OTA library I wrote) need to know how to send it to Android, I've already a TCP connection in my app, and already ESP send the Print (percent) progress and app show it as numeric and in a progressbar, maybe I can do it the same way but not sure. If you think there is a better way please let me know.

After this the OTA part is completed, many thanks to you too...

Many Thanks
 
Last edited:
Upvote 0

Ivica Golubovic

Active Member
Licensed User
Now I need to implement a small custom screen login so the user can change password and login, so no way to show some default login screen? I need to create it custom and so it appear in the WebView1_UserAndPasswordRequired function ?
Yes, you need to implement a custom LoginDialog because there is no LoginPage in your WebSource (Source Uses Classic HTTP Authentication).

I think this is the only way you can apply if WebSource does not have a progress bar implemented by default.
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
I think this is the only way you can apply if WebSource does not have a progress bar implemented by default.
Eventually I can change it, I have included the HTTP OTA class inside OTA library I've developed for ESP.

This library require just 4 lines of code on every sketch to mantain exposed OTA update service in background while the CPU execute other tasks,
it contain the inline HTML I can edit.

Here the simple class, for ESP32 is similar....
but I have no much experience on HTML and this require automatic refresh of a page...

C++:
#include <Arduino.h>
#include <WiFiClient.h>
#include <WiFiServer.h>
#include <ESP8266WebServer.h>
#include <WiFiUdp.h>
#include <flash_hal.h>
#include <FS.h>
#include "StreamString.h"
#include "ESP8266HTTPUpdateServer.h"

namespace esp8266httpupdateserver {
using namespace esp8266webserver;

static const char serverIndex[] PROGMEM =
  R"(<!DOCTYPE html>
     <html lang='en'>
     <head>
         <meta charset='utf-8'>
         <meta name='viewport' content='width=device-width,initial-scale=1'/>
     </head>
     <center>
          <body style="background-color:powderblue;">
          <h2>Update Service</h2>
          <form method='POST' action='' enctype='multipart/form-data'>
                <strong>Firmware</strong><br>
                <input type='file' accept='.bin,.bin.gz' name='firmware'><br><br>
                <input type='submit' value='Update Firmware'><br>
          </form>
          <br><br>
          <form method='POST' action='' enctype='multipart/form-data'>
                <strong>FileSystem</strong><br>
                <input type='file' accept='.bin,.bin.gz' name='filesystem'><br><br>
                <input type='submit' value='Update FileSystem'><br>
          </form>
          </body>
     </center>
     </html>)";
static const char successResponse[] PROGMEM =
  "<META http-equiv=\"refresh\" content=\"15;URL=/\">Update Success! Rebooting...";

template <typename ServerType>
ESP8266HTTPUpdateServerTemplate<ServerType>::ESP8266HTTPUpdateServerTemplate(bool serial_debug)
{
  _serial_output = serial_debug;
  _server = NULL;
  _username = emptyString;
  _password = emptyString;
  _authenticated = false;
}

template <typename ServerType>
void ESP8266HTTPUpdateServerTemplate<ServerType>::setup(ESP8266WebServerTemplate<ServerType> *server, const String& path, const String& username, const String& password) {
    _server = server;
    _username = username;
    _password = password;

    uint8_t cnt = 0;

    // handler for the /update form page
    _server->on(path.c_str(), HTTP_GET, [&](){
      if(_username != emptyString && _password != emptyString && !_server->authenticate(_username.c_str(), _password.c_str()))
        return _server->requestAuthentication();
      _server->send_P(200, PSTR("text/html"), serverIndex);
    });

    // handler for the /update form POST (once file upload finishes)
    _server->on(path.c_str(), HTTP_POST, [&](){
      if(!_authenticated)
        return _server->requestAuthentication();
      if (Update.hasError()) {
        _server->send(200, F("text/html"), String(F("Update error: ")) + _updaterError);
      } else {
        _server->client().setNoDelay(true);
        _server->send_P(200, PSTR("text/html"), successResponse);
        delay(100);
        _server->client().stop();
        ESP.restart();
      }
    },[&](){
      // handler for the file upload, gets the sketch bytes, and writes them through the Update object
      HTTPUpload& upload = _server->upload();

      if(upload.status == UPLOAD_FILE_START){
        _updaterError.clear();
        if (_serial_output) Serial.setDebugOutput(true);

        _authenticated = (_username == emptyString || _password == emptyString || _server->authenticate(_username.c_str(), _password.c_str()));
        if(!_authenticated){
          if (_serial_output) Serial.printf("Unauthenticated Update\n");
          return;
        }

        WiFiUDP::stopAll();
        if (_serial_output)
          Serial.printf("\nUpdate: %s\n\n", upload.filename.c_str());
        if (upload.name == "filesystem") {
          size_t fsSize = ((size_t) &_FS_end - (size_t) &_FS_start);
          close_all_fs();
          if (!Update.begin(fsSize, U_FS)){//start with max available size
            if (_serial_output) Update.printError(Serial);
          }
        } else {
          uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
          if (!Update.begin(maxSketchSpace, U_FLASH)){//start with max available size
            _setUpdaterError();
          }
        }
      } else if(_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()){
        if (_serial_output) {
             Serial.print(".");
             if(cnt++ > 100) {
                 Serial.print("\n");
                 cnt = 0;
             }
          }
        if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
          _setUpdaterError();
        }
      } else if(_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()){
        if(Update.end(true)){ //true to set the size to the current progress
          if (_serial_output) Serial.printf("\n\nUpdate Success: %u\nRebooting now ...\n", upload.totalSize);
        } else {
          _setUpdaterError();
        }
        if (_serial_output) Serial.setDebugOutput(false);
      } else if(_authenticated && upload.status == UPLOAD_FILE_ABORTED){
        Update.end();
        if (_serial_output) Serial.println("\nUpdate was aborted");
      }
      delay(0);
    });
}

template <typename ServerType>
void ESP8266HTTPUpdateServerTemplate<ServerType>::_setUpdaterError()
{
  if (_serial_output) Update.printError(Serial);
  StreamString str;
  Update.printError(str);
  _updaterError = str.c_str();
}

};
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Ivica, we can write a Book on B4X WebView and sell it..... or a Bible...... we have 50 posts now on this thread with only two partecipants, You and Me ???
 
Upvote 0

Ivica Golubovic

Active Member
Licensed User
You can do it with javascriptinterface for webview. You need to add one line to HTML file to CallSub, for example:
Example::
} else if(_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()){
        if (_serial_output) {
             Serial.print(".");
             B4A.CallSub("UploadProgress", cnt.toString()) 'This line will CallSub in B4X code!!'
             if(cnt++ > 100) {
                 Serial.print("\n");
                 cnt = 0;
             }
          }
        if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
          _setUpdaterError();
        }
      } else if(_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()){
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Many thanks... I will try it... I've no much experience with javascript and HTML, I just used simple things

Taking on your code seem simple, but I suppose I've to create the JavaScript interface on B4A side, I currently do not use any library, no your UltimateWebView, no WebViewExtras, just the inline code you posted.
So I suppose I need to use JavaObject, but is not simple things for me.

What I do not know at all is: but if I put this line in the Arduino side, it do not compile and securely return an error:
C++:
B4A.CallSub("UploadProgress", cnt.toString()) 'This line will CallSub in B4X code!!'
what I miss?

Do it shoud be inside the <script> tag inside the HTML file, where JavaScript is?

If I find a way to directly use UltimateWebView library and work on the Intent so it do not return null, this semplifies my code in my app, even I can create the JavaScript interface, even your library will be used in a 3D printing host app that control machinaries over USB, over local wifi, over Internet and even without wifi at all, tested in mobility using a data line (SIM) but maybe do not works with all operators.

Many thanks Ivica for your great help I appreciated a lot.

Sorry if I cannot donate you some $ because here I've no work, but if you need something I'm here, no problem if I can help.
I'm pretty expert on ESP8266 and ESP32 and something with Raspberry that I've used python and B4J to program and wrote
some libraries for these, some others are in develop, other libraries for B4J and others for B4A...
Libraries that I have to release when I've some time....
 
Last edited:
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
From your last ShowFileChooser function this fixed the Intent:
B4X:
    'This part of code will repair Intent
    Dim IntentJO As JavaObject = FileChooserParamsIntent
    Dim IntentType As String = IntentJO.RunMethod("getType",Null)
    If IntentType.Contains(".bin") Then
        FileChooserParamsIntent.SetType("application/octet-stream/*")
    End If
    '-----------------------------
Now I want remove a long inline code from the project and use UltimateWebView to do all the work with a few of lines as originally expected when I downloaded it.

The problem is that in the B4AExample (your library example) there is no ShowFileChooser sub, it just use this that is when the file chooser already initialized inside the library when the Intent already return null pointer:
B4X:
Private Sub UltimateWebView1_FileChooserInitialized (FilePathCallback As Object, FileChooserParams1 As FileChooserParams) 'Works from API level 21 and above. WebChromeClient required.
    UltimateWebView1.FileChooserStart(FilePathCallback,FileChooserParams1,False)
End Sub
How fix the intent using directly the UltimateWebView ?
This is something you need to do on the library ?

Many thanks
 
Last edited:
Upvote 0

Ivica Golubovic

Active Member
Licensed User
I have to put that in the library because the library is configured to be as easy to use as possible. You can't fix it yourself. And so I'll put that in the next library update. I will send you a beta version of the corrected library today or tomorrow.
 
Upvote 0

Ivica Golubovic

Active Member
Licensed User
As I promised, here is a beta version of the library with corrected intent. I also shared a FileUpload Example project with you.
 

Attachments

  • FileUpload.zip
    11.4 KB · Views: 194
  • UltimateWebView_2.12_Beta_Lib.zip
    104.1 KB · Views: 214
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Many many many thanks Ivica for your great library, support and help.

I will try it and post feedback.
How I can Fix it ?

I've tried to reconstruct the Intent in the FileChooserInitialized Event before a call of UltimateWebView1.FileChooserStart function, but without success, here my code:
B4X:
Private Sub UltimateWebView1_FileChooserInitialized (FilePathCallback As Object, FileChooserParams1 As FileChooserParams) 'Works from API level 21 and above. WebChromeClient required.
'    UltimateWebView1.FileChooserStart(FilePathCallback,FileChooserParams1,False) ' Original
    
'    ' As global variable
'    Private UploadContent As Object
    
    Dim FileChooserParams As Reflector
    FileChooserParams.Target = FileChooserParams1
    Dim Mode As Int = FileChooserParams.RunMethod("getMode")
    Dim FileChooserParamsIntent As Intent = FileChooserParams.RunMethod("createIntent")
'    UploadContent = FilePathCallback
  
    Log("FileChooserParamsIntent: " & FileChooserParamsIntent)
    Log("FilePathCallback: " & FilePathCallback)
    Log("Mode: " & Mode)
    
    'This part of code will repair Intent ///////////// INTENT FIX //////////////
    Dim IntentJO As JavaObject = FileChooserParamsIntent
    Dim IntentType As String = IntentJO.RunMethod("getType",Null)
    If IntentType.Contains(".bin") Then
        FileChooserParamsIntent.SetType("application/octet-stream/*")
    End If
    '-----------------------------
    
    Dim jfcp As Object
    Dim fcp As FileChooserParams
    fcp.Initialize(
    fcp.
    Log("AcceptTypes: " & fcp.AcceptTypes)
'    If Mode = 1 Then
'        FileChooserParamsIntent = ActivityClass.RunMethod("alowMultipleFiles", Array(FileChooserParamsIntent))
'    End If
  
    If FileChooserParamsIntent <> Null Then
        Log("Initial intent exist, call StartActivityForResult with the Intent")
'        StartActivityForResult(FileChooserParamsIntent)
        ' >>>>>>>> HERE FileChooserParams need to be passed as FileChooserParams, nor Reflection object
        UltimateWebView1.FileChooserStart(FilePathCallback, FileChooserParams, False)
    Else
        Log("Initial intent does not exist")
    End If
End Sub

I want to implement a progressbar, I've read all (very big and cool) UltimateWebView documentation searching to know how to do it, but....

You can do it with javascriptinterface for webview. You need to add one line to HTML file to CallSub, for example:
C++:
      } else if(_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()){
if (_serial_output) {
Serial.print(".");
B4A.CallSub("UploadProgress", cnt.toString()) 'This line will CallSub in B4X code!!'
if(cnt++ > 100) {
                 Serial.print("\n");
cnt = 0;
             }
          }
if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
          _setUpdaterError();
        }
} else if(_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()){

What I do not know at all is: but if I put this line in the Arduino side, as your suggestion, it cannot compile and securely return an error:
B4X:
B4A.CallSub("UploadProgress", cnt.toString()) 'This line will CallSub in B4X code!!'
what I miss?

Do it shoud be inside the <script> tag inside the HTML file, where JavaScript is ? And if yes, how to call from here from my code?

I know that on B4A side I call
B4X:
UltimateWebView1.AddJavascriptInterface (JavascriptInterface As Object, Name As String) As String
'Injects the supplied JavascriptInterface As Object into this UltimateWebView.
'The object is injected into all frames of the web page, including all the iframes, using the supplied name. This allows the Java object's methods to be accessed from JavaScript.
'Added in API level 1.
but what I don't know is how to really implement it.

Many thanks
 
Upvote 0

Ivica Golubovic

Active Member
Licensed User
How I can Fix it ?
You dont need to fix it by yourself, Intent will be automatically fixed by library.

It will not throw an error, read this post for tutorial:
Post in thread 'UltimateWebView Custom View' https://www.b4x.com/android/forum/threads/ultimatewebview-custom-view.135666/post-869686
 
Upvote 0

max123

Well-Known Member
Licensed User
Longtime User
Because you helped me, now if possible I want to help you to do some debugs......

The lntent now is FIXED. Great !!!

I tried the 2.12_Beta with the original B4AExample and now it worked like a charm with just a few of lines. Great !!!

I've tried it with UploadFile you posted and I do not know because my link works well and your link always open the page, I select a small file, but when I press the Upload button it always return an unaccessible page.

The library returns some strange warnings on the log related to Reflection, this happen before all, before to load a page, I always seen this same error, even in 2.11 library version. I suppose It happen internally in library initialization, but this is your library, so I think you only can know it.

Maybe it is related on some permissons, I've all activated in the manifest and the PERMISSONS table show a lots of permissons you sblocked. Great !!!
Now I will try to comment some to know if this is the problem.

To be complete I attached the Filtered and Unfiltered logs of one Update OTA Session, maybe can help you to know where is the problem.

Filtered:
Unfiltered:
Note even the:
Accepted types: [Ljava.lang.String;@ae28a02
And note that B4AExample rerurned me an error, I see you changed a way.... I changed these 2 lines in the CreateChildWindow Sub:
B4X:
    NewUltimateView.SetWebViewClient(True) ' Added boolean argument
    NewUltimateView.SetWebChromeClient(True) ' Added boolean argument

I have a question, I do not found a way to return the name of selected file in the FileChooser, it is possible extract it ?
I tried some code to extract infos from a Content, but it is a bit long code and require to import SQL (need Cursor) and ContentResolver libraries.

For JavaScript interface I will try to study it and inform you if I've success.

Many thanks.
 
Last edited:
Upvote 0

Ivica Golubovic

Active Member
Licensed User
The problem is in old version of B4A and old SDK package. This library is related on CustomView class. This Logs show me that Custom View properties from designer could not be loaded. So, the main problem in your case is very old IDE and very old SDK package. When you decide to go to latest, all logs will dissapear.
 
Upvote 0

Ivica Golubovic

Active Member
Licensed User
No, it is not possible on easy way.
 
Upvote 0
Cookies are required to use this site. You must accept them to continue using the site. Learn more…