Micro:bit - Getting On WiFi
Written by Harry Fairhead   
Monday, 11 July 2022
Article Index
Micro:bit - Getting On WiFi
AT Commands
Utilities
Configuring WiFi
A Web Server
Full Listing
Summary

Configuring WiFi

The first thing we need to configure is the operating mode. The ESP8266 can operate as an access point, i.e. it can allow other devices to connect to it. However, in most cases you will want it to work in client mode, connecting to your existing WiFi.

Mode

A function to set its operating mode is:

int modeWiFi(int mode) {
  ManagedString cmd="AT+CWMODE_CUR="+
ManagedString(mode)+"\r\n"; uBit.serial.send(cmd, SYNC_SPINWAIT); return waitForWiFi("OK", 200, 10); }

Notice that now we are constructing the command to send to the device in a separate step. If you try and do this on the fly in the function call, the rate of transmission of the serial data varies and the device can time out. In practice, construct all commands to send to the device first and then send them.

In this case the command is:

AT+CWMODE_CUR=n

where n is 1 for client, 2 for access point and 3 for both. If you want to make the change permanent then change CUR to DEF and the setting is stored in Flash. Older devices do not support CWMODE_CUR. Simply change it to CWMODE, which is deprecated.

To set client you would call:

modeWiFi(1);

and see OK sent back.

Scan

The scan function is one that everyone wants to try out, but in practice it isn't very useful. Why would you want a list of WiFi networks? There are some applications for this function, but not as many as you might think. In most cases, you simply want to connect to a known WiFi network, which is what we do in the next section.

The scan command is easy - just send AT+CWLAP and the device sends you a complete list of WiFi networks. The problem is that scanning takes a long time and often hangs. This is one case where we really need to specify a long timeout:

int scanWiFi() {
    uBit.serial.send("AT+CWLAP\r\n", SYNC_SPINWAIT);
    return waitForWiFi("OK", 500, 50);
}

Sometimes the loop completes with a full list, sometimes it doesn't. It depends on the WiFi networks that are available and how they reply to requests.

Connecting to WiFi

Our final and most useful functions connect the device to a known WiFi network. All you have to do is supply the SSID and password. There are other versions of the command that allow you to specify the connection more precisely, but this general form is the most useful.

Connection to a network takes a while and there is quite a lot of data sent back, so we need to use the retry count loop introduced in the scan function:

int connectWiFi(ManagedString ssid, ManagedString pass) {
 ManagedString cmd="AT+
CWJAP_CUR=\""+ssid+"\",\""+pass+"\"\r\n"; uBit.serial.send(cmd, SYNC_SPINWAIT); return waitForWiFi("OK", 200, 20); }

If you have an older device you might need to change CWJAP_CUR to the deprecated CWJAP command. There is also a CWJAP_DEF command that will save the connection in the Flash memory. The connection is made using:

connectWiFi("myWiFi","myPassword");

After a few seconds you should see:

AT+CWJAP_CUR="myWiFi","myPassword"
WIFI DISCONNECT
WIFI CONNECTED
WIFI GOT IP
OK

Once you are connected and the "WIFI GOT IP" message has been received you can ask what the IP address is:

int getIPWiFi() {
    uBit.serial.send("AT+CIFSR\r\n", SYNC_SPINWAIT);
    return waitForWiFi("OK", 200, 10);
}

Of course, if you really need to know the IP address within a program you need to extract it from the string. The device replies with:

IP address:
AT+CIFSR
+CIFSR:STAIP,"192.168.253.4"
+CIFSR:STAMAC,"5c:cf:7f:16:97:ab"
OK

which makes it very easy to get the IP address even without the help of a regular expression.

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 whether 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. The sensor is another micro:bit and the web server is 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 whatever 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\nHost:192.168.253.23\r\n\r\n";

There are two headers:

GET /index.html HTTP/1.0
Host:192.168.253.23

Note that an HTTP request always ends with a blank line.

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. 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 on the V1 - 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 sends 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 ( Tuesday, 12 July 2022 )