1°/ Preambule
- esp8266 and esp32 have native wifi support, which is not the case of ATmega328
- An esp8266/esp32 can be used as a wifi client, a wifi access point, or both.
- A client just connects to an access point and receives an IP address.
- If a µC is used has an access point, it provides wifi connections and IP addresses (like a DHCP server), but it has not itself an IP address.
- Nevertheless, a µC can be an access point AND a client at a time.
- Whatever the case, a sketch replying on Wifi must include an header file that contains the wifi function declarations:
- for arduino/esp32 board, it is WiFi.h,
- for esp8266 boards, it is ESP8266WiFi.h
2°/ µC as a Wifi client
- The "common way" is to create a function that is called in setup().
- this function tries endlessly to establish the Wifi connection.
Exemple (for esp32) :
#include <WiFi.h>
...
void setupWifi() {
const char *ssid = "ssid_wifi_ap"; // the ssid of the AP
const char *password = "pass_ap"; // the password of the AP
WiFi.setAutoConnect(false); // see comments
ret = WiFi.setSleepMode(WIFI_NONE_SLEEP); // always active => big consumption
ret = WiFi.mode (WIFI_STA); // setup as a wifi client
ret = WiFi.begin(ssid,password); // try to connect
while (WiFi.status() != WL_CONNECTED) { // check connection
delay(500);
Serial.print(".");
}
// debug messages
Serial.print("Connected, IP address: ");
Serial.println(WiFi.localIP());
}
Remarks :
- setAutoConnect() is used to prevent the µC to automatically establish a connection with the last know AP (its credentials are kept in a kind of flash memory).
- By default, this automatic connection occurs just after the boot sequence, and it may prevent the µC to connect to the ssid given in the code if it differs from the last one used.
- This is why it is a good idea to disable this automatic connection, unless you are sure that your µC will always connect to the same ssid.
3°/ µC as a Wifi access point+client
- Since the AP will act as a DHCP server, we must set variables to configure the network address, the network mask, ...
- In the following example, we assume that the AP will provide addresses like 192.168.0.XXX
- We also must choose a wifi channel for the Wifi network.
- Note that the Wifi norm defines 13 channels that correspond to 13 frequencies around 2.4GHz. The problem is that 2 wifi networks cannot use channels that are too close unless it produces a lot of interference, and thus a lot of packet loss. The least gap is 4.
- An esp8266/esp32 is not really powerful, so the maximum number of clients is 4 by default. Moreover, if it is itself a client, it has in charge the AP + its own code, it may rapidly crashes if it is overloaded. So, for a reliable solution, it is better to use a dedicated AP. But if we just want to communicate between 2 µC, it is sufficient and simple.
Exemple (for esp32):
#include <WiFi.h>
...
void setupAP() {
IPAddress local_IP(192,168,0,1);
IPAddress gateway(192,168,0,1);
IPAddress subnet(255,255,255,0);
ret = WiFi.mode(WIFI_AP_STA); // AP + client
int channel = 1; // choose between 1 and 13
const char *ssid = "ssid_wifi_ap";
const char *password = "pass_ap";
// CAUTION: on other µC, 2 following lines may be exchanged to work
ret = WiFi.softAP(ssid, password, channel);
ret = WiFi.softAPConfig(local_IP, gateway, subnet);
// debug messages
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
}
4°/ TCP connection & communication
- Once a Wifi connection is established and the µC has an IP address, it is possible to create a TCP connection.
- A µC can act as a TCP client, or a TCP server.
- When the TCP connection is established, we can communicate by sending/receiving bytes.
4.1°/ µC as a TCP Client
- Once again, we use a dedicated function to establish the connection with the server. This function tries to initialize an instance of WiFiClient class, using the connect() method. Note that it is a good idea to use a variable that store the state of this connection.
- After connection is established, we can use print() to send lines of text, but unfortunately, there is no native method to read lines, only read() that reads one byte.
- The examples below shows a (bit imperfect) workaround to get a line of text.
Exemple (for esp32):
#include <WiFi.h>
...
WiFiClient client;
byte connState;
const char* ipServ = "192.168.0.1";
int portServ = 12345;
byte buf[1024];
int idx = 0;
...
String readLine() {
char buffer[1024];
memset(buffer,0,1024); // reset buffer
int index = 0;
char c;
while(true) {
while (!client.available()) {} // wait for smthg to read
while ((index < 1023) && (client.available())) {
c = client.read();
if (c == '\n') return String(buffer); // end-of-line reached => return
buffer[index++] = c; // store the char
}
// prevent buffer overflow: return the whole buffer even if no \n encountered
if (index == 1023) return String(buffer);
}
}
void setup() {
Serial.begin(115200);
connState = 0;
// establish the wifi connection
...
}
bool doConnection() {
if (client.connect(ipServ, portServ)) {
connState = 1;
Serial.println("connected to server")
// special for esp8266: disable buffering small mesg
client.setNoDelay(true);
return true;
}
else {
connState = 0;
Serial.println("connection failed")
return false;
}
void loop() {
if (connState == 0) {
if (! doConnection()) { delay(1000); }
}
if (connState == 1) {
client.write(10); // write a byte equal to 10
client.println("10"); // write 3 bytes: ascii code of 1, ascii code of 0, ascii code of new_line
// wait something from the server
String msg = readLine();
Serial.println(msg);
}
}
Comments :
- reading from a WiFiClient is tedious because there is only one function : read(), that reads just a byte. Moreover, it is not blocking (which is not the case for classical processors with an OS like unix)
- Nevertheless, it is possible to obtain a blocking reception using : while(!client.available()) {}. On a µC, it works perfectly while on a classical processor, it is forbidden because it loads it at 100%.
4.2°/ µC as a TCP server
Exemple (for esp32=:
#include <WiFi.h>
...
WiFiServer server(12345);
WiFiClient client;
byte connState;
byte buf[1024];
int idx = 0;
...
void setup() {
Serial.begin(115200);
connState = 0;
// establish the wifi connection
...
}
void waitConnection() {
client = server.available(); // test if there is an incoming connection and if yes, accept it.
if (client) {
connState = 1;
Serial.println("client connected")
// special for esp8266: disable buffering small mesg
client.setNoDelay(true);
}
}
void loop() {
if (connState == 0) {
waitConnection();
}
if (connState == 1) {
// wait something from the client
while (!client.available()) {}
// read what is available
while (client.available()) {
buf[idx++] = client.read(); // read & store a byte
}
Serial.println(String(buf)); // print the content of buf as a string
for(int i=0;i<idx;i++) buf[i] = 0; // reset buf
}
}