Micro:bit IoT In C - Getting On WiFi
Written by Harry Fairhead   
Saturday, 16 July 2016
Article Index
Micro:bit IoT In C - Getting On WiFi
Setup
Utility Functions
Getting A Web Page
A Web Server
Listing

A Web Server

The most common use for an internet connection on a small device like the micro:bit is to allow another device to request data. It is fairly easy to create a web server running on the ESP8266, but don't expect Apache or anything advanced. All you can reasonably do is accept a connection and send a web page or two back to the client. 

The key differences between client and server mode is that in server mode the device constantly "listening" for clients to make TCP connections on the port. When the device receives a connection it reads all of the data the client sends and passes it on via the serial port to the the micro:bit. This means that in server mode the micro:bit has to be constantly on the lookout for new data from the ESP8266. You can do this using an interrupt, but for simplicity this example uses a polling loop.

There is another difference between client mode and server mode - there can be multiple TCP connections from as many clients that try to connect. The solution to this problem is that the ESP8226 assigns each TCP socket connection an id number and this is what you need to use to make sure you send the data to the right place.

Let's see how it all works.

Assuming we are already connected to WiFi and have an IP address, we can set up a server quite easily. First we need to use the CIPMUX =1 to set the device into multiple connection mode. You cannot start a server if CIPMUX=0, the default single connection mode.  Once multiple connections are allowed you can create server using CIPSERVER=1,port.

In this example we are using port 80 the standard HTTP port but you can change this to anything you want.

int startServerWiFi() {  
 uBit.serial.send("AT+CIPMUX=1\r\n",
                              SYNC_SPINWAIT);
 if (waitForWiFi("OK", 100, 20) == 0) return 0;         
 uBit.serial.send("AT+CIPSERVER=1,80\r\n",
                              SYNC_SPINWAIT);
 if (waitForWiFi("OK", 100, 20) == 0) return 0;

If you run just this part of the program you will see the response:

AT+CIPMUX=1
OK
AT+CIPSERVER=1,80
no change
OK

Now we just have to wait for a client to make a connection and send some data.  This is done simply by reading the serial input and checking for "+IPD":

for (;;) {
  s="";
  do {
   uBit.sleep(100);
   if(s>500)s="";
   s = uBit.serial.read(500, ASYNC);
  } while (find("+IPD", s) == 0);
  if (DEBUG)debug("\n\rClient Connected\n\r" +
                                   s + "\n\r");

Notice the start of the outer infinite loop. What we are going to do is check for a connection, service the connection with some data and then go back to checking for another connection - hence the outer infinite loop, the server loop. 

Once we have a connection it will be formatted so that the id is just after the "+IPD". In multiconnection mode the received data has the format:

+IPD,id, rest of data

We now need to extract the id so we can use it to communicate with the client. This is just some standard string handling, but it is still messy:

int b = find("+IPD", s);
s = s.substring(b + 1, s.length());
b = find(",", s);
s = s.substring(b + 1, s.length());
b = find(",", s);
ManagedString id = s.substring(0, b );
if (DEBUG)debug("\n\rTCP id:" + id + "\n\r");

The algorithm is:

  • Find "+IPD" and trim the string so that it is the start.
  • Find the first comma and trim the string so that it is the start.
  • Find the next comma and extract the characters from the start of the string to the next comma. 

Now we have the id we can communicate with the client but first we need something to send. As with all HTTP transactions, we have to send some headers and then some data. There are a lot of possible headers you could send, but a reasonable minimum that works with most browsers is:

ManagedString headers = "HTTP/1.0 200 OK\r\n";
headers = headers + "Server: micro:bit\r\n";
headers = headers + "Content-type: text/html\r\n\r\n";

You can include time stamps and lots of other useful information, but this is simple and it works. Notice the blank line at the end of the headers - this is vital. The browser will ignore everything sent to it if you don't have a blank line at the end of the headers.

The html data is a simple JSON-style data object giving humidity and temperature:

ManagedString html = "<html><head>
          <title>Temperature</title></head>
 <body>{\"humidity\":81%,\"airtemperature\":23.5C}</p>
 </body></html>\r\n";
ManagedString data=headers+html;

Of course, in a real sensor you would add live data into the HTML in place of the 81% and 23.5%.

Notice that it is easier to combine the headers and the data into a single data string. The only reason for defining them as two separate strings is to make it easier to see what they contain.

Now we want to send the data to the client. This is just a matter of using the CIPSEND command again, only this time with the id specified as the first parameter:

CIPSEND=id,data length

and we wait for the response ">" before sending the data: Notice that we don't have to open a TCP socket as we did in the case of acting as a client. The TCP socket has already been opened by the client connecting to the server and when the transaction is complete we can close it. 

ManagedString cmd = "AT+CIPSEND="+id +","+
           ManagedString(data.length()) + "\r\n";
uBit.serial.send(cmd, SYNC_SPINWAIT);
s = "";
int retry = 40;
do {
 uBit.sleep(100);
 s = s + uBit.serial.read(500, ASYNC);
 retry--;
}while (find(">", s) == 0 && retry != 0);

Now, at last, we can send the data to the client:  uBit.serial.send(data, SYNC_SPINWAIT);
if (waitForWiFi("OK", 100, 100) == 0) return 0;
if (DEBUG)debug("\n\rData Sent\n\r");

and wait for it to complete.

Finally we close the connection and complete the loop to wait for another connection:

  cmd = "AT+CIPCLOSE=" + id + "\r\n";
  uBit.serial.send(cmd, SYNC_SPINWAIT);
  if (waitForWiFi("OK", 100, 100) == 0) return 0;
 };
}

If you now try it out with a main program something like:

int main() {
 uBit.init();
 initWiFi();
 modeWiFi(1);
 connectWiFi("ssid","pass");
 getIPWiFi();
 startServerWiFi();
}

You should now be able to connect to the IP address that is displayed and retrieve the web page that displays:

{"humidity":81%,"airtemperature":23.5C}

Where Next?

There are a lot of WiFi commands that haven't been covered in this chapter, but now that you have seen examples of most of

the basic types and encountered the typical problems that occur you should be able to implement any that you need. 

The biggest problem in working with the micro:bit in serial mode it the limited amount of memory. If your program seems to have a tendency to simply "go quiet" on you then suspect that you are creating a ManagedString that is just too large. This is not a big problem when it comes to acting as a server, but it is for a client trying to process a lot of data. 

There may also be better ways to handle the serial interaction between the two devices, but the method presented does seem to work reasonably well. If thing get in a mess then the retry limits usually reset the entire transaction if you wait long enough. 

There is still going to be the occasional unexplained crash and in this case the best solution is to use the soft reset command. It is also worth mentioning that the server program as presented does not actually handle multiple connections. It has to finish dealing with one connection before it can deal with a second. 

 



Last Updated ( Friday, 26 August 2016 )