次の方法で共有


Using XMLHttpRequest to pilot a Lego train dynamically in HTML 5

It’s a long time I did not write a blog post. I was very busy and had no time to write and code anything in the last weeks. I still have a lot of work but I need an intellectual break for the evening. So I do not write this post from a plane but from an hotel room. In my past blog posts I’ve explained how to pilot any Lego Power System with a Netduino using .NET Microframework.

In the HTTP Web server I’ve implemented, I command the train thru a URL with arguments. Those arguments are transformed into parameters which are given to a class. This class output a wave form into an infrared led amplified by a transistor. This is a simple and efficient way to command anything. I do the same for my sprinkler system.

Now if you want to pilot in a web interface multiple trains, and click on buttons or pictures to get an action without opening a new web page or refreshing the page, you need to do some Scripting in your HTML page. I’m not a web developer, I don’t like Scripting languages as they are not strict enough to write correct code and imply too many errors. They drastically increase your development time! I truly prefer a good language like C#, VB or even Java and I can go up to C/C++ Sourire Now, if I want to avoid any problem, I can jump into Eiffel Sourire OK, I won’t go up to there, I’ll stay with java script in an HTML5 page.

What I want is to call my command page in the background of the page and stay in the HTML page when I click on a button. There is a nice object in HTML which allow you to do that which is XMLHttpRequest. It is implemented in all decent browsers.

Here is the code that I generate dynamically (I’ll show you the code later) and I’ll explain you how it works:

 <html xmlns="https://www.w3.org/1999/xhtml"><head>
<title></title></head><body>
<SCRIPT language="JavaScript">
var xhr = new XMLHttpRequest();
function btnclicked(boxMSG, cmdSend) {
    boxMSG.innerHTML = "Waiting";
    xhr.open('GET', 'singlepwm.aspx?' + cmdSend + '&sec=');
    xhr.send(null);
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4)
        { boxMSG.innerHTML = xhr.responseText; } 
};
}
</SCRIPT>
<TABLE BORDER="0"><TR><TD>
<FORM>Super train</TD><TD>
<INPUT type="button" 
onClick="btnclicked(document.getElementById('train0'),
'pw=11&op=0&ch=254')" 
value="<"></TD><TD>
<INPUT type="button" 
onClick="btnclicked(document.getElementById('train0'),
'pw=8&op=0&ch=254')" value="Stop"></TD><TD>
<INPUT type="button" 
onClick="btnclicked(document.getElementById('train0'),
'pw=5&op=0&ch=254')" value=">"></TD><TD>
<span id='train0'></span></FORM></TD></TR>

In the script part of the page, I have created a simple script. Those lines of code is what is necessary to do a synchronous call of an HTTP page and display the result in the page.

I create an XMLHttpRequest object which I call xhr. The function call btnclicked takes 2 arguments. the first one is the element of the page I will put the results of the request. And the second one is the command (the parameters) to pass to the URL which will pilot the infrared led as explain previously.

The function is very simple. First, I put “Waiting” in the element. Then I open the XMLHttpRequest object. I open it with GET and pass the overall URL.

The request is done when the send function is called. It is a synchronous call, so it get to the onreadystatechange when it is finished.

Here, when you read the documentation, the readyState 4 mean that everything went well and you have your data back. My function return “OK” when everything is OK and “Problem” if there is a problem.

Now let have a look at the rest of the HTML page. I decided to create a form with button input. Each button input has a onClick event. This event can be linked to a script. So I will call the fucntion describe before and give a span element (here train0) and the URL. The URL is different depending if you want to train to go forward, backward or stop. And I put all this in a nice table top be able to have multiple trains.

The page with 4 trains looks like this:

image

I’ve clicked on the forward button, “Wainting” is displayed in span. And as soon as the command will finish, it will either display OK or Problem. In my case, Problem will be displayed as in the Emulator, there is no SPI port!

 // Start HTML document
strResp = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"
 \"https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">";
strResp += "<html xmlns=\"https://www.w3.org/1999/xhtml\">
<head><title></title></head><body>";
//creat the script part
strResp += "<SCRIPT language=\"JavaScript\">";
strResp += "var xhr = new XMLHttpRequest(); function btnclicked(boxMSG, cmdSend) 
{ boxMSG.innerHTML=\"Waiting\";";
strResp += "xhr.open('GET', 'singlepwm.aspx?' + cmdSend + '&" + securityKey + "');";
strResp += "xhr.send(null); xhr.onreadystatechange = function() 
{if (xhr.readyState == 4) {boxMSG.innerHTML=xhr.responseText;}};}";
strResp += "</SCRIPT>";
strResp = WebServer.OutPutStream(response, strResp);
// Create one section for each train
strResp += "<TABLE BORDER=\"0\">";
for (byte i = 0; i < myParamRail.NumberOfTrains; i++)
{
    strResp += "<TR><TD><FORM>" + myParamRail.Trains[i].TrainName + "</TD>
<TD><INPUT type=\"button\" 
onClick=\"btnclicked(document.getElementById('train" + i + "')
,'pw=" + (16 - myParamRail.Trains[i].Speed);
    strResp += "&op="+ myParamRail.Trains[i].RedBlue + "&ch=" + 
(myParamRail.Trains[i].Channel - 1) + "')\" value=\"<\"></TD>";
    strResp += "<TD><INPUT type=\"button\" 
onClick=\"btnclicked(document.getElementById('train" + i + "'),'pw=8";
    strResp += "&op=" + myParamRail.Trains[i].RedBlue + "&ch=" + 
(myParamRail.Trains[i].Channel - 1) + "')\" value=\"Stop\"></TD>";
    strResp += "<TD><INPUT type=\"button\" 
onClick=\"btnclicked(document.getElementById('train" + i + "'),
'pw=" + myParamRail.Trains[i].Speed;
    strResp += "&op=" + myParamRail.Trains[i].RedBlue + "&ch=" + 
(myParamRail.Trains[i].Channel - 1) + "')\" value=\">\"></TD>";
    strResp += "<TD><span id='train" + i + "'></span></FORM></TD></TR>"; 
    strResp = WebServer.OutPutStream(response, strResp);
}
strResp += "</TABLE><br><a href='all.aspx?" + securityKey + 
"'>Display all page</a>";
strResp += "</body></html>";
strResp = WebServer.OutPutStream(response, strResp);

The code to generate the HMTL page is here. As you see it is manually generated. The table which contains the name of the train, the backward, stop and forward button is created totally dynamically. Each train may have a different speed and use different channels so the URL is generated dynamically.

All this takes time to generate and to output in the Stream. So it’s much better to do it only 1 time and then call a simple and small function which will return just a bit of text.

Last part, on the server header, you have to make sure you add “Cache-Control: no-cache” in the response header. If you don’t do it, only the first request will be send to the netduino board back. XMLHttpRequest will consider that the page will never expire. So I’ve modified the header code of my HTTP Server like this:

 string header = "HTTP/1.1 200 OK\r\nContent-Type: text/html; 
charset=utf-8\r\nCache-Control: no-cache\r\nConnection: 
close\r\n\r\n";
connection.Send(Encoding.UTF8.GetBytes(header), header.Length, 
SocketFlags.None);

What is interesting there is that I can now pilot up to 8 Lego trains using this very simple interface and without having to refresh the page.

And the excellent news is that it is working from my Windows Phone so it’s even cool to pilot your Lego train from your Windows Phone. I haven’t tested from an Android or iPhone but I’m quite sure it will work the same way. Remember that all the code is sitting in a very small hardware with no OS, just .NET Microframework! As always, do not hesitate to send me your comments Sourire

Comments

  • Anonymous
    June 20, 2012
    Great blog.  Would be interesting to see how this could be used with Lego Mindstorms

  • Anonymous
    June 20, 2012
    Laurent, I'm assuming that you own a Netduino Plus, just because you're using TCP/IP. So, why don't store the whole page in the SD, then use the XHR for pulling our just dynamic data (i.e. AJAX)? I guess you should have at least two advantages: (1) a much less overhead for creating the string by the Netduino (consider the amount of heap required for that), and (2) the page could be much usable, 'cos you change only the HTML DOM nodes actually involved in the refresh. Also consider a SVG animation, driven by the incoming data via HTTP! Cheers

  • Anonymous
    June 24, 2012
    @Wharty, Mindstorms are more complicated as you'll need to connect thru USB or Bluetooth. And is is much much more complex. @MArio, yes it's the netduino plus version. And yes, using AJAX like this reduce drastically the overhead to create the text. I basically don't have any. Just a bit of analyse of the command string and that's it. And yes, I do consider an animation but I first need to have data incoming like the position of the trains and for the moment, it is Under development!

  • Anonymous
    June 17, 2013
    Salut on ma pistoné sur ton poste je cherche a faire exactement comme toi j'aurais voulu avoir un peux plus info stp:)