µTasker Forum
µTasker Forum => µTasker general => Topic started by: BitCoder on January 22, 2008, 09:05:38 AM
-
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?
-
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
-
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
-
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
-
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)
<body onload="setInterval('document.location.reload()', 2000 )">
Meta refresh can cause problems
-
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:
<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:
<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:
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:
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
-
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?
-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
-
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
-
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:
<value>£vH0</value>
I then added a case for 'H' to the fnInsertString function in webInterface.c:
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:
static unsigned char ucLastHB;
I set this variable in application.c:
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
-
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
-
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.
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.
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.
-
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:
extern char ucLastHB[500] = {0};
The following basically inserts all elements from index 0 to the first 0 valued element.
1) In http.c:
// 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:
//////////////////////////////////////////////////////////////////////////////////////
// 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.
-
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 (http://www.subbu.org/weblogs/main/2006/08/json_vs_xml_1.html)
Regards,
Andriy Korud
-
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?
-
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
-
Hi Mark,
Thanks a lot, it's so simple when you explain.
When I try with the "normal code" I got an answer from the web server through the user files (ajax1)..
When I intercept inside fnDoWebPage() I got no answer
Is it enough to call fnSendTCP() with usLen set to the data length of ucTCP_Message ?
Thanks.
-
Hi Hervé
Calling fnSendTCP() will always send data if the socket is open. However when answering in this case the data should be inserted after an HTTP header which will be sent by the HTTP server. This means that it falls under "dynamic content generation". fnSendTCP() is use by the dynamic content generation code but together with other mechanisms.
If possible I would use the built-in mechanism. I am sure that it is not the only method but is is probably the simplest.
Regards
Mark
-
Hi Mark
I will manage with the built-in mechanism.
When I test the counter with http://192.168.167.205/XML?Dummy, it increases every two.
Then I put a breakpoint on
fnBufferDec(ucTestCounter++, 0, cValue);and it's called twice effectively.
Do you know why ?
Regards
-
Hi Hervé
Yes I know why this happens. It is because the dynamic insertion (can) call the function more than once as it is building TCP frames.
If I remember correctly it sometimes calls the code to generate the content and doesn't send it since the complete frame doesn't fit into the TCP frame. Then it has to call it again for the next frame.
The same happens when a TCP repetition is made and ff you have multiple sessions monitoring the variable it will also get incremented much faster since it will be incremented by each TCP connection...
Therefore the test case is in fact not a very representative test since it uses the content generation to increment the variable although the content generation will not necessarily take place just once.
The idea was to show a very simple case of variables being modified - in a real example the variable wouldn't (normally) not be changed due to the fact that the content was generated but due to other, unrelated factors (like seconds passing, inputs being detected, etc.). In fact it would be better to display the value of the system Tick for example:
In webInterface.c, fnInsertString():
case '4':
fnBufferDec(uTaskerSystemTick, 0, cValue); // display the present TICK value
cPtr = cValue;
break;
Regards
Mark
-
Hi Mark,
I'm using <Dummy>£vX4</Dummy> now, thanks.
1/ Still have some question : cucXML_header never seems to be use, is it right?
2/ I'm trying to get the XML response on IE6, but in fact it works only when using responseText not responseXML : The format is not correct for IE, may be; do you note the same behaviour ? (I had no problem with FF...)
Thanks.
-
Hi Hervé
1) I don't know where you have cucXML_header from. In which code is that?
2) I have to admit to not having used this technology much and so I don't have much experience with it and also don't know many details about it. When I try the http://192.168.0.3/XML?Dummy test there are however differences in behavior between firefox and IE8 (I don't know about IE6).
Firefox displays <Dummy>4232</Dummy> and IE8 display 4232 (that is, just the number).
Therefore there are certainly compatibility issues involved to get the best performance. I hope that someone with more knowledge can help.
Regards
Mark
-
Folks,
Ok, this is an old topic but as the server I wrote for the NE64 about 4 years ago does as has been described in this topic, I figured I would describe how it is down...obviously without and Ajax, et al processing. If I repeat something from this topic here, I apologize.
Specifically, one can place more than one page in a page's HTML. For the purpose here, the second complete page is placed at the bottom something like this:
</form>
<form name="Comm" method="get" target="Comm">
<input type="hidden" value="0" name="Start1">
</form></body>
</html>
Now as one can see, it is hidden. So my server and pages perform a constant heartbeat to our device to make sure to the page that the server is still there. On the server side, this allows the server to close the session if the heartbeat is not received.
The is also used so that we never refresh any page after it has been presented. I did not have enough RAM space on the NE64 for the largest IP packet possible so the page presentation was slow. The background updates are fast though since the page is not refreshed and the only stuff passed back/forth is thr request as usual and then just the data change for the response.
Each page has a script to handle the hidden page response, which basically calls the same script that processes the page's original data values and updates the form. Also some of the pages actually do not populate any fields from the base page's HTML but rather call the hidden page to send the data to populate the primary page with. This model speeds up the presentation as my server does not handle dual-sends per browser ACK or parallel connections/requests as uTasker does very nicely.
The server just knows how to handle this special page by its name/action set. It does the processing/data update and sends back the hidden page.
That's it.
Of course it is the responsibility of the server to handle all this in the fashion desired. One other item helped in that the primary origin page name is included in the request made for the hidden page so that the server knows what to do for that page. Otherwise, all of the page processing on my server is handled by the same code.
In any case, I am in the process of implementing this in uTasker v1.4 and will let you know. As far as I can see, it should work as long as I can inhibit any std processing that might get in the way. At this point i have not seen any, but I have not gone super deep into this.
To all, take care.
- jon :D
-
Folks,
One thing I forgot was the page itself. Here is an idea of how it can be done and will work with what I described in the previous post:
<script language="javascript">window.parent.handleResponse(error indication or other flag/value here,"PagNameHere","data vales to process here")</script>
That is the entire page. The returned indicator is optional. The page name in this page allows for the base page to have a script to handle say all base pages and populate accordingly. Please understand that the only real important thing here is that the returned hidden page script here must call the parent for the proper processing to occur.
Once again hope this is useful.
Take care.
- jon :D