Micro:bit - Getting On WiFi |
Written by Harry Fairhead | ||||||||
Monday, 11 July 2022 | ||||||||
Page 5 of 7
A Web ServerThe 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 has sent and passes it on via the serial port to 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 all the clients that try to connect. The solution to this problem is that the ESP8266 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", 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" + 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 multi-connection 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:
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 timestamps 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> Of course, in a real sensor you would add live data into the HTML in place of the 81% and 23.5C. Notice that it would be 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, this time with 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; }; } 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%,"air temperature":23.5C} |
||||||||
Last Updated ( Tuesday, 12 July 2022 ) |