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


microbcover

Getting a Web Page

Now that we have so many functions we can tackle the two standard tasks in using the TCP stack - getting and sending data as a client and as a server. 

First we tackle the problem of acting as a client. This isn't as common a requirement as you might expect because most of the time devices like the micro:bit are used to supply data to other servers, not the other way round. However, it is worth seeing how it is done. 

It doesn't matter if you are implementing a client or a server you make use of sockets which represent the basic TCP connection. What you do with this connection is up to you. For example, if you send HTTP headers on an appropriate port then you can fetch or deliver a web page i.e. HTTP over TCP. However, what data you actually send and receive over a socket connection is up to you and/or the protocol you are trying to use. 

Hence the first thing we have to do is set up a socket connection between the client, i.e. the micro:bit, and the server.

int getWebPageWiFi(ManagedString URL,
                        ManagedString page) {
 ManagedString cmd = "AT+CIPSTART=\"TCP\",\"" +
                                  URL + "\",80\r\n";
 uBit.serial.send(cmd, SYNC_SPINWAIT);
 if (waitForWiFi("OK", 100, 20) == 0) return 0;

You pass the URL to the function as an IP address or as a full URL, but the device looks up domain names using a fixed set of DNS servers. It is recommended that you use an IP address especially when testing. The CIPSTART command opens a socket to the specified IP address and port. You can also specify a TCP or UDP connection:

AT+CIPSTART=type, IP, port

In this case we open port 80 on the specified IP address. If it works you will get back a message something like:

Connect
AT+CIPSTART="TCP","192.168.253.23",80
CONNECT
OK

Now we have a socket open we can send some data to the server and wait for some data to be sent back to us. This is the most difficult part of using the micro:bit on the web. If you request a web page then it is fairly likely that the data you get back is going to be too much to hold in memory. You either have to load a very small web page - a few hundred bytes - or process it on the fly as the data comes in. 

For this example the web page is served by a small sensor that returns a JSON temperature and Humidity reading - yes it is another micro:bit and the web server described in the next section. 

To send data over a socket you use CIPSEND which will send any data you specify to the server. As already made clear, what you send is a matter for what protocol you are using over the socket. In this case it is HTTP and we are going to send headers corresponding to a GET request for index.html

ManagedString http = "GET /index.html HTTP/1.0\r\n
            Host:192.168.253.23\r\n\r\n";

There are two headers:

GET /index.html HTTP/1.0
Host:192.168.253.23

An HTTP request always ends with two blank lines.

To send this request we use the CIPSEND command which specifies the number of characters that are to follow:

cmd = "AT+CIPSEND=" + ManagedString(http.length()) +
       "\r\n"; uBit.serial.send(cmd, SYNC_SPINWAIT);

Now we have to send the number of bytes/ characters that we specified in the CIPSEND but first we wait for a ">" to indicate that the device is ready to receive the data:

s = "";
retry = 40;
do {
 uBit.sleep(100);
 s = s + uBit.serial.read(500, ASYNC);
 retry--;
}
while (find(">", s) == 0 && retry != 0);    
uBit.serial.send(http, SYNC_SPINWAIT);

What happens next depends on the server. As a result of the HTTP GET the server will now send data over the WiFi link and the device will send this over the serial connection as soon as it gets it.  Notice we can't use the waitFor function because it might swap the serial lines and cause a spurious character to be transmitted. 

Notice that this data is not a direct response to a command and so the device prefixes it with: 

+IPD,len:

The +IPD makes it clear to the client that is a packet of data sent from the server. The len value gives the number of characters sent after the colon. 

In principle what your program should do next is sit in a polling loop looking for +IPD. It should then read the digits between the comma and the colon and convert this to an integer. Finally it should then read exactly that number of characters from the serial port. 

This can be done, but for a demonstration we simply read any data that is presented on the serial port for a reasonable amount of time in an attempt to capture all of the data. Notice that this is a fine balance between avoiding a buffer overrun because you don't read it often enough and not waiting long enough for all of the data. There is also the problem that you can run out of memory, about 700 bytes seems to be the limit for the program as presented. 

retry = 100;
do {
 uBit.sleep(100);
 s = s + uBit.serial.read(500, ASYNC);
 retry--;
} while (s.length() < 500 && retry != 0);
if (DEBUG)debug("\n\rPage\n\r" + s + "\n\r");
return 1;
}

The test website send a very small amount of data and the result is:

+IPD,17:HTTP/1.0 200 OK
+IPD,99:Server: BaseHTTP/0.6 Python/3.2.3
Date: Thu, 14 Jul 2016 16:42:37 GMT
Content-type: text/html
+IPD,127:<html><head><title>Temperature</title>
</head><body><p>Temperature:31.43
</br>{"humidity":0,"airtemperature":0}</p>
</body></html>CLOSED

You can see that there are three "packets" of data - 17 characters, 99 characters and finally 127 characters. In principle you could process the +IPD characters as they come in and work out how many characters to read. However, you still wouldn't know how many packets to expect. 

Reading data from the web or any other protocol is limited to only small amounts of data - the example above is typical. 



Last Updated ( Friday, 26 August 2016 )