Author Topic: Is it possible to update values on a Web Page without loading the entire page?  (Read 35695 times)

Offline BitCoder

  • Newbie
  • *
  • Posts: 7
    • View Profile
I am working on a device that measures forces and give the computed value back on a display. This value should be continuously updated for every one or two seconds on a Web Page without loading the entire page every time. How can I achieve such a behavior?

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3238
    • View Profile
    • uTasker
Hi BitCoder

I understand that this can possibly be achieved by using Ajax technology (this works together with Java Script). However I can't help any more that that at the moment because I have never used it.

Does anyone else have an axample?

Regards

Mark

Offline BitCoder

  • Newbie
  • *
  • Posts: 7
    • View Profile
Hi Mark
Your answer raises further questions.
1. Is it possible to update a Web Page automatically for every one or two seconds as long as the page is displayed on the browser, even if the entire page is updated?
2. How can such a scenario be implemented with http?
3. In case of using Java Script how would the Web Browser communicate with the uTasker http-Server? Is there a different protocol needed?

Regards
BitCoder

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3238
    • View Profile
    • uTasker
BitCoder

To refresh a page automically is simple by adding the following in the HTML meta tag - refresh - see John's example here:
http://www.utasker.com/forum/index.php?topic=98.0
The refresh time or the complete meta statement can also be generated using a £vXX tag. For example it is then possible to change the refresh time (a possible user configuration) or limit the refresh activity to a certain number of updates (the £vXX tag is added by webInterface.c and can send a blank string to stop further refreshing.

I can't fully answer the Javascript question at the moment but it works with standard HTTP. Either is makes connections (without updating the display) or it requires a persistent HTTP TCP connection - I don't know which, or whether both types are possible. The uTasker HTTP server is HTTP1.0, which does use persistent connections, however it is still possible to keep a connection open by using SUPPORT_DELAY_WEB_SERVING (search for this in webInterface.c for a small piece of demo code) - whether this is suitable is however another question.

Regards

Mark

Offline Thomas

  • Newbie
  • *
  • Posts: 17
    • View Profile
Read up on AJAX, start with Wikipedia for instance:
http://en.wikipedia.org/wiki/AJAX

Or, the best place to start might be (covers everything web-related, html, javascript, ajax and more. very nice page):
http://www.w3schools.com/

With AJAX you can basically request files to be loaded and handled by javascript running on your page. On the web-server it should be no different than requesting files the normal way. You can then for instance extract values and display them as you want. But if the page you want to refresh is very small and doesn't load lots of secondary files a simple page refresh might work for you.

If you want to reload a page it might be better to use a periodic timer using javascript, something like this to reload page every 2 seconds (2000ms)

Code: [Select]
<body onload="setInterval('document.location.reload()', 2000 )">

Meta refresh can cause problems
« Last Edit: January 28, 2008, 08:00:12 PM by Thomas »

Offline pht

  • Newbie
  • *
  • Posts: 7
    • View Profile
We use AJAX in our project to update real-time informations and it works like a charm.
One of the problems with page refresh is that you will see the page "flicker" because the whole document is reloaded.
If you refresh often then it will not look good. Also, you are forced to regenerate & resend the whole page HTML every time instead of just sending the values (possibly a few bytes) you want to update.

To use AJAX, you will need some basic knowledge of JavaScript, but that's all.

Here is a very simple example:

Suppose you have a value, let's call it Dummy, that you want to refresh on a page.

First you need to create dynamically a very simple XML document that will only contain the required value.
For example whenever you request 'http://utasker/XML?Dummy' from the uTasker HTTP server you will get the following response:
Code: [Select]
<Dummy>47</Dummy>Here, my dummy value is simply a counter that is incremented every request.

Next, you store a static web page that will look like the following (the simplest AJAX implementation I can think of), let's call it dummy.html:
Code: [Select]
<html>
    <head>
        <script language="JavaScript">
            var request;         
            function onLoad()
            {     
              sendRequest();
              setTimeout('onLoad();', 500);
            }
            function sendRequest()
            {       
              request = new XMLHttpRequest();
              request.onreadystatechange = onResponseReceived;
              request.open("GET", "XML?Dummy");    
              request.send(null);
            }
            function onResponseReceived()
            {           
            if (request.readyState == 4 && request.status == 200)
            {           
            document.getElementById("dummyval").innerHTML = request.responseXML.getElementsByTagName("Dummy")[0].firstChild.data;
            }
            }                    
        </script>
    </head>
    <body onload="onLoad();">
    Some stuff will not be updated.
    <br/>
        Some stuff will (dummy value = <span id="dummyval">.</span>).   
    </body>
</html>

From a user perspective, you only need to request 'http://utasker/dummy.html'. In this case, the JavaScript code will take care of requesting 'XML?Dummy' every 500 ms and then update the value of the 'dummyval' span element.

You will see something like this in your browser:

Code: [Select]
Some stuff will not be updated.
Some stuff will (dummy value = 2248).


Of course that's the theory. My AJAX implementation above is very simplistic (no error checking) and only works with Firefox. The hard part of using AJAX is to have a consistent cross-browser experience. Firefox and IE often doesn't use the same objects or methods, so you will have to rely on a few tricks and wrapper methods like the following:

Code: [Select]
function newXmlHttpRequest()
{
if (window.XMLHttpRequest)
{
// Mozilla
return new XMLHttpRequest();
}
else if (ActiveXObject)
{
// Internet Explorer
return new ActiveXObject("Microsoft.XMLHTTP");
}
}

Luckily, there is a lot of AJAX-related documentation and code snippets on the web.

-phil
« Last Edit: April 24, 2009, 11:16:05 PM by mark »

Offline jharvey

  • Newbie
  • *
  • Posts: 7
    • View Profile
Thanks for all of your help so far Mark.

I've been following the instructions in this thread along with the Coldfire AJAX demo and now have a webpage that can read a file and post the value needed.

However, I am unsure how to dynamically create the file when I get a character over the uart. 

I see that the code in application.c below gets called everytime a character is recieved over UART, would this be an acceptable place to make the call to write the file?
Code: [Select]
         -snip-
         if ((iAppState & (STATE_ACTIVE | STATE_DELAYING | STATE_ESCAPING | STATE_RESTARTING | STATE_VALIDATING)) && (Length = fnMsgs(SerialPortID))) {
            while (Length = fnRead( SerialPortID, ucInputMessage, MEDIUM_MESSAGE)) {
           
            // This gets printed any time a character is recieved.  Should be able to just tie in here.
            static const CHAR ucJ_TEST[] = "Testing to see where this gets done\r\n";
            fnWrite(SerialPortID, (unsigned char *)ucJ_TEST, sizeof(ucJ_TEST));
           
                fnEchoInput(ucInputMessage, Length);
               
                // Insert code to save character to file             
                -snip-

Also, when working with the coldfire, I was able to just create a static file and put in it "~14D".  This would cause it to return the value of html_vars[14] in decimal format.  Is there anything like that supported with uTasker?  I see the Stats page uses things such as "£vsf", is this doing the same thing?  How do I go about mapping a variable to a value?

Either of these solutions will work for me.

I'd appreciate any assistance you could offer.

Thanks,
-Jesse

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3238
    • View Profile
    • uTasker
Jesse

The UART driver has an input which collects received data (length RX_BUFFER_SIZE) - you could use this position to read the data and save it to local variables for use later when displaying, or else you could leave the reading from the UART to when it is actually called to be done via the web server interface (however in this case the data can only be read and displayed once - if no new data is ready it could display this fact instead). The best solution depends on your application [I know of some applications where the web server access causes a request of the data via UART transmission and then displays the received answer after a short delay - this may also be appropriate for you(?)].

The way to interact with the web interface is detailed in the following post. You will find that it is quite easy to display values, strings etc. (in decimal or hex) based on the references in the HTML (like £vsf as you have already identified):
http://www.utasker.com/forum/index.php?topic=94.0

It you look at the HTTP_DYNAMIC_CONTENT demo in the project you can see that it is also quite easy to generate dynamic HTML itself (eg. creating variable length tables, etc. and not just inserting values). You should also find that the uTasker serves the pages faster than the Coldfire AJAX demo since it is using a special high speed serving technique (ensure that you have SP6 for latest features in the demo).

Regards

Mark


Offline jharvey

  • Newbie
  • *
  • Posts: 7
    • View Profile
Thanks for the information Mark.

What I ended up doing is creating a file called XML as the user above suggested.  In it I placed:
Code: [Select]
<value>£vH0</value>
I then added a case for 'H' to the fnInsertString function in webInterface.c:
Code: [Select]
case 'e':
  #ifdef USE_SMTP
            cPtr = (unsigned char *)fnDebugDec(usSentEmails, 0, cValue);
            *usLengthToSend = ((CHAR *)cPtr - cValue);
  #else
            cValue[0] = '0';
            *usLengthToSend = 1;
  #endif
            break;
           
        case 'H':
            cPtr = (unsigned char *)fnDebugDec(ucLastHB, 0, cValue);
            *usLengthToSend = ((CHAR *)cPtr - cValue);
            break;

        default:
            *usLengthToSend = 0;
            return 0;
        }

I made ucLastHB a static unsigned char towards the top of config.h so that I could modify it in application.c and read it in webInterface.c:
Code: [Select]
static unsigned char ucLastHB;
I set this variable in application.c:
Code: [Select]
if ((iAppState & (STATE_ACTIVE | STATE_DELAYING | STATE_ESCAPING | STATE_RESTARTING | STATE_VALIDATING)) && (Length = fnMsgs(SerialPortID))) {
            while (Length = fnRead( SerialPortID, ucInputMessage, MEDIUM_MESSAGE)) {
           
            // This gets printed any time a character is recieved.  Should be able to just tie in here.
            ucLastHB = ucInputMessage[0];

            fnEchoInput(ucInputMessage, Length);         

If I replace the 'ucLastHB' in the 'H' case above with a hard coded value (127), that value will be returned to me just fine by the XML file (The response is <value>127</value>).  However, if I leave it as ucLastHB, all I get is 0 (<value>0</value>).

Does anyone see something I could be doing wrong that would cause this behavior?

Thank you,
-Jesse
« Last Edit: April 21, 2008, 02:18:29 AM by jharvey »

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3238
    • View Profile
    • uTasker
Hi Jesse

I think the problem is with the variable ucLastHB due to the fact that you are declaring it in config.h.
This means that each file (application.c where the serial input is being read and webInterface.c where it is being displayed) have
static unsigned char ucLastHB;

This means that each file has its "OWN" ucLastHB. The one being set to the serial value in application.c is a "different" variable to the one being displayed by webInterface.c. The displayed value will thus always remain at 0.

I would do the following:

In application.c declare the variable ucLastHB as a global variable:

extern unsigned char ucLastHB;

In application.c initialise this variable
extern unsigned char ucLastHB = 0;

Then webInterface. c (which imports the global via the header application.c) will be able to display the value, which is modified by application.c when it receives serial data.

Regards

Mark

Offline pht

  • Newbie
  • *
  • Posts: 7
    • View Profile
A few people asked me how to generate dynamically XML files, so here it goes.

DISCLAIMER: Generally you should only make changes to the uTasker codebase when necessary. For simple cases you should rely on the "official" method for generating dynamic documents which is inserting values in static HTML pages (through fnInsertValue). The method I describe below works well but has drawbacks (such as documents being limited to a single packet - 1400 bytes).

The general idea is to look for a specific string at the beginning of the GET URL. For example, is someone requests '/1.htm' then the HTTP server will behave normally. However, if someone requests '/XML?GetDummyValue' then some code will detect that the query string begins with XML? and pass on 'GetDummyValue' to a special function. From this function I just write directly to the HTTP buffer.

So, a (very) simplified version of the code will look like this:

A) This is inserted in fnDoWebPage. Can't tell you exactly where since we've made extensive changes to the original code, but it shouldn't be too hard to figure out.

Code: [Select]
if (!uMemcmp(data, "XML?", 4))
{
    int len = MakeXmlDocument(tcpBuffer, data + 4);
    session->ucState = HTTP_STATE_ACTIVE;
    session->usUnacked = len;
    return fnSendTCP(session->OwnerTCPSocket, (uchar*)&tcpBuffer->tTCP_Header, len, 0) > 0;
}

B) The MakeXmlDocument writes a XML document to tcpBuffer depending on the query and returns the number of bytes written.

Code: [Select]
int MakeXmlDocument(char* tcpBuffer, char* query)
{
     if (!uStrcmp(query, "GetDummyValue"))
     {
          // Write to tcpBuffer here, return the number of bytes written.
     }
}

There you go. This post wasn't meant to be a cut-and-paste working example, but I hope you get the idea. Please tell me if you want more implementation details.


Offline jharvey

  • Newbie
  • *
  • Posts: 7
    • View Profile
I appreciate the sample code BitCoder!  I just wanted to follow up and list exactly how I used your information with the M52235evb project.  Look for the two sets of // Begin New Code and // End New Code comments.\

I basically have an array of characters stored.  The array was created with a size of 500 and all elements were set to 0 to start:
Code: [Select]
extern char ucLastHB[500] = {0};

The following basically inserts all elements from index 0 to the first 0 valued element.

1) In http.c:
Code: [Select]
// Begin New Code
// Create an XML document
int MakeXmlDocument(char* query)
{
unsigned int len;

fnDebugMsg("In MakeXmlDocument, Sending Data\r\n");

// Write to tcpBuffer here, return the number of bytes written.
len = uStrlen("<value>");
len += uStrlen(ucLastHB0);
len += uStrlen("</value>");

uMemcpy(&(HTTP_Tx->ucTCP_Message[0]), "<value>", uStrlen("<value>"));
uMemcpy(&(HTTP_Tx->ucTCP_Message[uStrlen("<value>")]), ucLastHB0, uStrlen(ucLastHB0));
uMemcpy(&(HTTP_Tx->ucTCP_Message[uStrlen("<value>") + uStrlen(ucLastHB0)]), "</value>", uStrlen("</value>"));

return len;
}
// End New Code

// Display a specific web page
//
static int fnDoWebPage(CHAR *cFileName, HTTP *HTTP_session)
{
    unsigned short usLen = HTTP_BUFFER_LENGTH;
    unsigned char ucPush = TCP_FLAG_PUSH;
    unsigned char *ucFile;
#ifdef WEB_WINDOWING_BUFFERS
    int iRtn = 0;
    unsigned short usNext = 0;
#endif

#ifdef SUPPORT_INTERNAL_HTML_FILES
   
// Begin New Code
if ( (cFileName != 0) && (!uMemcmp(cFileName, "XML?", 4)))
{
    int len = MakeXmlDocument(cFileName + 4);
    HTTP_session->ucState = HTTP_STATE_ACTIVE;
    HTTP_session->usUnacked = len;
    uiDataReady = 0;  // Only needed for my application
    return fnSendTCP(HTTP_session->OwnerTCPSocket, (unsigned char*)&HTTP_Tx->tTCP_Header, len, 0) > 0;
}
    // End New Code
    else if (cFileName != 0) {                                                // predefined internal file is to be displayed
#endif
        if ((HTTP_session->ucState > HTTP_STATE_ACTIVE) || (*cFileName < '0')) { // if we have just authenticated, always do start page
            *cFileName = '0';                                            // default start side
        }
        ucFile = uOpenFile(cFileName);                                   // open the file to be displayed
        HTTP_session->ucState = HTTP_STATE_ACTIVE;

And in my HTML page I have:
Code: [Select]
//////////////////////////////////////////////////////////////////////////////////////
// Request input file
//////////////////////////////////////////////////////////////////////////////////////
function makeRequest(url)
{
var http_request = false;

    if (window.XMLHttpRequest)
{
// Mozilla, Safari,...
alert('XMLHttpRequest');
        http_request = new XMLHttpRequest();
        if (http_request.overrideMimeType)
{
        http_request.overrideMimeType('text/xml');
        }
    }
else if (window.ActiveXObject)
{ // IE
  try
{
alert('In Microsoft.XMLHTTP');
        http_request = new ActiveXObject("Microsoft.XMLHTTP");
        }
catch (e)
{
            try
{
alert('Msxml2.XMLHTTP');
            http_request = new ActiveXObject("Msxml2.XMLHTTP");
            }
catch (e)
{
alert('In catch case...bad');
}
        }
    }

    if (!http_request)
{
        alert('Giving up :( Cannot create an XMLHTTP instance');
        return false;
    }

    http_request.onreadystatechange = function() { alertContents(http_request); };
    http_request.open("GET", url, true);
    http_request.send("");
}

//////////////////////////////////////////////////////////////////////////////////////
// Infinite loop with delay
//////////////////////////////////////////////////////////////////////////////////////
function loop()
{
alert( 'Calling makeRequest' );
makeRequest("XML?HBValue");
//setTimeout("loop()",1000);
//setTimeout("loop()",10);
}

So it's pretty much exactly what BitCoder said, just figured another example couldn't hurt...

Thanks for all the help guys.


edit: I noticed that I was not returning valid XML because I was not adding the <value></value> tags on.  I've updated the MakeXmlDocument function above.
« Last Edit: April 30, 2008, 03:00:54 AM by jharvey »

Offline akorud

  • Newbie
  • *
  • Posts: 31
    • View Profile
Just my 2 cents: I've found it's easier to pass response in JSON format (especially if you have more values to pass). This article should give you an idea what do I mean http://www.subbu.org/weblogs/main/2006/08/json_vs_xml_1.html

Regards,
Andriy Korud

Offline hervé

  • Jr. Member
  • **
  • Posts: 98
    • View Profile
I tried to modify fnDoWebPage() like jharvey, and test it with the following url http://192.168.167.205/XML?Dummy
The problem is that it does not send back the content generated by MakeXmlDocument()
On wireshark I got the following :

GET /XML?Dummy HTTP/1.1
Host: 192.168.167.205
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.8) Gecko/20100722 AskTbTRL2/3.7.0.231 Firefox/3.6.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive

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

GET /favicon.ico HTTP/1.1
Host: 192.168.167.205
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; fr; rv:1.9.2.8) Gecko/20100722 AskTbTRL2/3.7.0.231 Firefox/3.6.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 115
Connection: keep-alive
HTTP/1.1 200 OK
Connection: close
Cache-Control: no-cache                         
............ .h.......(....... ..... .......................................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...............................{...{...{...{...{...{...{...{...{...................:...:...I........<..>...
...G...M.......Z}..%.......E;...
...2



I never asked for GET /favicon.ico HTTP/1.1
How is it possible to have this command looged?

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3238
    • View Profile
    • uTasker
Hi Hervé

The favicon is requested automatically by the browser so that it can display it in the URL line. Often the browser stops doing this once it knows the content and the side is not changed.

Check out the following document for a method of working with AJAX files: http://www.utasker.com/docs/uTasker/uTaskerUserFiles.PDF
via the User File System.

Regards

Mark