ESP8266 and ESP32 are popular WiFi development boards. These microcontroller boards are ideal for building the Internet of Things (IoT). We cover how to connect ESP8266 and ESP32 (or any MicroPython port) with a network connection for use as a WiFi station here. We use the MicroPython network module.
This network module provides a driver for creating a network interface. The interface depends on the network medium (WiFi or Ethernet) and the specific port hardware for that medium. Using a network interface such as WLAN (WiFi) or Ethernet, ESP8266/ESP32 or any MicroPython port can connect to the Internet. The same module is also useful for managing certain router settings.
MicroPython socket or Usocket module is required to access different network services. Examples of network services include World Wide Web (WWW), Domain Name Lookup (DNS), email, file server, time services, VoIP, wireless sensor network, directory services, file sharing, file server, instant messaging, network management. protocols, and there are others.
Once the ESP8266/ESP32 is connected to the internet via WiFi or Ethernet (using a network module), they can be used for various IoT-based applications. For IoT, ESP8266 and ESP32 connect to Internet/network services through low-level access, which is provided through basic socket support from the underlying operating systems/firmware.
For example, MicroPython provides socket andusocket modules for socket-level network programming of supported ports and boards. The socket module is a reimplementation of a subset of the CPython module with the same name. The Usocket module is an extended implementation of MicroPython's built-in socket module.
With the help of sockets, ESP8266/ESP32 can be configured as a TCP/IP server or client. The cards can also be configured as a UDP server or client. ESP8266/ESP32 or any MicroPython port can be used for any network function like web server or client.
In this article, we will cover the basics of Transmission Control Protocol/Internet Protocol (TCP/IP) and UDP networks. We will also discuss how the ESP8266/ESP32 can be configured as a web server or client within a wide area network using the socket module. Finally, we will test a simple ESP8266/ESP32 IoT application as a web server.
What is a Python socket?
Sockets are used for low-level network programming. In programming, a socket is the endpoint of communication between two programs running over a network. They facilitate two-way communication between two networked devices. A typical sockets application is a client-server application where one of the networked devices is a server and the other is a client.
Sockets provide inter-process communication (IPC), which means that sockets (or endpoints) communicate within a process – between processes on one or more machines. Unix domain sockets communicate between processes on the same machine. Internet sockets, Berkley sockets or BSD sockets communicate between processes on different machines. The network in which two devices are involved in socket-level communication can be a local area network (LAN), a wide area network (WAN), or the Internet.
The CPython socket module facilitates an interface to the Berkeley socket API that maps directly to system calls at the operating system/firmware level. The MicroPython socket module is a re-implemented subset of the same module.
It is important to note that in networking, the term “socket” refers to the network port and IP address of a device. The port is the endpoint that specifies the type of network service or specific process. It is a 16-bit identifier for the combined network protocol and network service. The network protocol can be TCP/IP or UDP.
This table lists some of the commonly used port numbers for different TCP/IP and UDP network services.
The Internet Protocol (IP) address is a numerical identifier of a device connected to the computer network that uses IP for communication. The combination of an IP address and a port (i.e. socket) number is a unique identifier of:
- The source/destination device on the computer network
- The network protocol (IP protocol) used for communication
- The network service/process involved in the communication.
Socket = IP Address + Network Port Number
MicroPython sockets refer to programming sockets – not network sockets (IP address and port number). However, some socket API methods take network socket (IP address and port number) as parameters, such as the connect method. The parameter is accepted as a separate hostname and port number.
Low-Level vs. High-Level Access high level access
Standard Python provides two types of access to computer networks: low-level and high-level.
- Low-level access is provided by sockets. Sockets use system calls from the underlying operating system/firmware to communicate with the device on the other end of the network. Sockets can implement both connection-oriented and connectionless protocols.
- High-level access is provided by implementing Internet protocols at the application layer such as HTTP, FTP, DNS, etc.
MicroPython provides only low-level access to network programming because its target devices are microcontrollers and embedded controllers. Directly implementing high-level access is resource-intensive and not feasible with platforms such as microcontrollers.
MicroPython easily configures ports with low-level access support as servers or clients. Socket programming even allows a server to communicate with multiple clients simultaneously.
TCP/IP Network Protocols vs. UDP
Two widely used network protocols are TCP and UDP. Both protocols are part of the TCP/IP suite, a set of layered protocols used for communication over the Internet. This suite is based on a client-server model where clients send requests to the server. The server can serve requests from multiple clients.
There are four layers of TCP/IP:
- Application layer – protocols like HTTP, HTTPS and FTP are implemented.
- Transport Layer – Data is communicated as a datagram using TCP.
- Network layer – the internet connection is established via IP, where source and destination devices are identified by IP addresses.
- Data Link Layer – Data is transmitted over the Internet as bits to the destination device.
TCP and UDP are the two transport layer protocols. Sockets can use TCP and UDP to configure client-server. TCP is the most reliable protocol as it extensively checks for errors. For this reason, it is used by several application layer protocols, such as HTTP, HTTPS, FTP, SMTP, etc.
One benefit of UDP is that it uses fewer resources. But overall it is less reliable with limited error checking. Services like VoIP often use UDP.
Most IoT devices rely on the TCP protocol to communicate data between a TCP server and TCP clients, particularly where the HTTP, HTTPS, or SMTP protocols function at the application layer. IoT cards are often configured as a TCP server or TCP client.
Addressing families
Source and destination devices are identified by IP addresses at the network layer. However, there are different types of IP addresses. Each type is recognized by its address family.
Defining a socket
In network programming, a socket is defined by a constructor method, socket.socket , which takes two parameters. One of the parameters is an address family, specified by the identifiers mentioned in the table above. The second parameter is the transport protocol, TCP or UDP.
The TCP network is specified by socket.SOCK_STREAM. The UDP network is specified by socket.SOCK_DGRAM. A typical socket definition in Python is:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
If the AF_INET address family is used, the IPv4 address is specified with a host name and port number (that is, by a network socket). The hostname can be a string representing an Internet site/location or the numeric IP address of a site/device. The port number is an integer that specifies the network service. For example, port number specifies HTTP, 443 specifies HTTPS, etc.
TCP server or client?
When designing an IoT application, one of the first questions is whether the MicroPython IoT port/board should be configured as a TCP server or client. In the client-server model, a client makes a request to the server and the server fulfills that request. The server cannot initiate communication.
This is an important consideration when configuring an IoT device on a network. The device must be configured as a TCP server if it is designed for operational control operations or other components. This ensures that it can receive control commands from client devices such as a computer, cell phone or remote control.
The IoT device must be configured as a TCP client if control operations depend on feedback from an IoT server or cloud platform. The same applies if the device is designed to display or manipulate data from a remote IoT server/cloud.
For example, the board must be configured as a TCP server if the device operates a MicroPython port relay/IoT board through a web page. Instead, it must be configured as a TCP client if the MicroPython port/IoT card display time is received from a time server.
The socket module
The MicroPython socket module provides access to the Internet or the Berkeley/BSD socket interface. The MicroPython firmware socket module directly implements a file-like interface (stream). This is different from CPython's socket module and is why socket objects do not need to be converted to file-like objects using the makefile function.
The socket module is imported into a MicroPython script using this instruction:
import socket
The module contains a socket class that includes all methods for creating and configuring an internet socket interface. The constructor method is called to do this. It has this prototype:
class socket.socket(af=AF_INET, type=SOCK_STREAM, proto=IPPROTO_TCP, /)
The constructor method takes three parameters:
- The address family determines the type of IP address used at the network layer.
- The socket type determines the selection of TCP or UDP networking.
- The protocol number determines the type of TCP or UDP protocol to be used.
The protocol number is optional and should be avoided. It is normally omitted by MicroPython ports and is automatically selected by socket type.
The following socket module method configures a MicroPython port as a TCP server…
socket.bind(address) : binds the socket to an IP address. The socket should not be bound yet.
socket.listen : Allows the TCP server to accept connections. A backlog parameter can be passed to this method, which specifies the number of unaccepted connections before refusing new connections. If the parameter is not provided, any reasonable value will be chosen by the server.
socket.accept : allows the server to accept a new connection. The socket must already be bound to an IP address and be listening for connections. This method returns a pair of values: conn and address. The conn is a new socket object used to transmit and receive data over the new connection. The address is the IP address of the device at the other end of the network with which data is communicated.
The following socket module method configures a MicroPython port as a TCP client…
socket.connect(address) : connects the client socket to a remote server socket. The IP address of the remote server socket is passed as a parameter.
The following socket module methods manage data communication on the TCP server and client.
socket.send(bytes) : used to send data to the other socket and must be connected to a remote socket. The method returns the number of bytes sent to the remote socket. Both server and client sockets use this method.
socket.sendto(bytes, address) : used to send data to a remote socket, which is determined by the IP address passed as a parameter. The socket must not yet be connected to the remote socket. This method is most useful on a TCP server, which can interact with multiple clients.
socket.sendall(bytes) : used to send all data to the remote socket, transmitting it in consecutive chunks. The socket must be connected to the remote socket. The behavior of this method for non-blocking sockets is undefined.
socket.write(buf) : sends a buffer of bytes to the remote socket. The socket must be connected to a remote socket. This method writes all data to the remote socket following a “no short write” policy. This poses no problem for blocking sockets to which data is always sent successfully. For non-blocking sockets, this method returns the number of bytes sent — which may be less than the actual buffer size. It is known how many bytes are successfully sent to the socket without blocking.
socket.recv(bufsize) : receives data from the remote socket. It returns a byte object that represents the received data. The maximum size of received data is determined by the size of the buffer object that is passed as a parameter.
socket.recvfrom(bufsize) : receives data from the remote socket. The socket must be connected to a remote socket. The method returns the byte object — representing the received data and IP address — of the remote socket. This method is most useful on a TCP server, which can connect to multiple clients.
socket.read((size)) : reads from the remote socket up to the number of bytes, specified as the size parameter. If no size parameter is specified, it reads all data from the remote socket until it receives EOF (that is, until the remote socket is closed). This method follows a “no short reads” policy that allows you to read all requested data. For non-blocking sockets, this method still cannot read all the data.
socket.readinto(buf(,nbytes)) : reads data from the remote socket into a buffer object. If the bytes parameter is specified, it reads only the specified number of bytes into the buffer. This method returns the number of bytes read and stored in the buffer object and follows a “no short reads” policy like the socket.read method.
socket.readline : Reads a line terminated by a newline character from the remote socket. It returns the line read from the remote socket.
socket.close : Closes a socket and frees the resources held by it. Once a socket is closed, any future operations will fail. When a socket is closed, the remote socket can receive an EOF indication as long as it is supported by the protocol. But sockets are automatically closed when garbage is collected and must be closed explicitly to improve efficiency.
The following socket module methods modify or change the behavior of the socket…
socket.setsockopt(level, optname, value) : Note that sockets default to standard behavior. A socket's behavior can be modified by changing various socket options. This method sets the value of a given socket option. The socket options that can be set with this method are provided as constants starting from SO_ or SOL_ in the socket module. The value that can be passed to the socket options is an integer or a byte object.
socket.setblocking(flag) : Sets a socket to blocking or non-blocking mode. If the flag is set to True, the socket is set to blocking mode. If the flag is set to False, the socket is set to non-blocking mode. This method serves as a shorthand for the socket.settimeout method. The socket's default mode is blocking. A blocking socket does not return to the program until the requested event completes. Non-blocking sockets respond immediately to the program, revealing whether the requested action has been completed or not. A blocking socket returns an error number if the requested event/action is in progress.
socket.settimeout(value) : Sets a timeout for blocking sockets. The timeout is specified as a non-negative floating point value. If the timeout value is set to '0', the socket will be placed in non-blocking mode. If the timeout value is set to none, the socket is placed in blocking mode. This method is not available on all MicroPython ports/boards.
socket.makefile(mode='rb') : returns a file object associated with the socket. MicroPython's makefile method does not support errors, encoding, and newline parameters like CPython's makefile method. It also does not support stream buffering. If the buffer parameter is set, it will be ignored and considered '0' (i.e. no buffer). The only required parameter is 'mode'. Only rb, wb and rwb binary modes are supported. If a file object returned by this method is closed, the original socket is also closed.
The functions provided by the socket module are as follows…
socket.getaddrinfo : Converts a network socket (i.e. host/port) into a 5-tuple sequence that can be used to create a socket. The format of the 5-tuple is (family, type, proto, canonname, sockaddr). This method is often used to resolve textual hostnames. The following example prints the resolved IP address of engineersgarage.com.
import socket
sockaddr = socket.getaddrinfo('www.engineersgarage.com', 80)(0)(-1)
print(sockaddr)
When running the above code, the following IP address is returned after resolving the hostname…
socket.inet_ntop(af,bin_addr) : Converts a binary network address to a textual representation according to the specified address family. Here is a valid example of this function:
socket.inet_ntop(socket.AF_INET, b”\x7f\0\0\1″)
The result of this function call is: '127.0.0.1'
socket.inet_pton(af,txt_addr) : Converts a textual network address from the given address family into its binary representation. Here is a valid example of this function:
socket.inet_pton(socket.AF_INET, “127.0.0.1”)
The result of this function call is: b'\x7f\0\0\1′
The MicroPython socket module does not support socket.error exceptions.
The Usocket module
MicroPython's Usocket module is similar to the socket module, which can be integrated into the MicroPython port/board or installed from micropython-lib. If the socket module script is not found, use theusocket module script. Both work.
Configuring ESP8266/ESP32 as TCP client using sockets
The following MicroPython code configures ESP8266/ESP32 as a TCP client that reads the HTML source code of a web page. The web page must have HTTP protocol and not HTTPS protocol.
import network
station = network.WLAN(network.STA_IF)
if station.isconnected == True:
print(“Already connected”)
station.active(True)
station.connect(“SSID”, “PASSWORD”)
while station.isconnected == False:
to spend
print(“Connection successful”)
print(station.ifconfig)
def http_get(url):
import socket
_, _, host, path = url.split('/', 3)
addr = socket.getaddrinfo(host, 80)(0)(-1)
s = socket.socket
s.connect(address)
s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8'))
while True:
data = s.recv(200)
if data:
print(str(data, 'utf8'), end=”)
other:
to break
s.close
http_get('
You must replace the SSID and PASSWORD on line 7 with the SSID and network key of your own WiFi connection.
The result of the above MicroPython script is shown below.
Reading SSL-encrypted web pages
The ESP8266 configured above cannot read the HTML content of SSL-encrypted pages. MicroPython has little support for the SSL protocol. However, it is possible to read the contents of SSL encrypted pages using standard Python on a personal computer.
Try the following Python script on a Raspberry Pi or your computer.
def http_get(url):
import socket
import SSL
_, _, host, path = url.split('/', 3)
real_host = host.replace(“ “”).replace(“ “”).split(“/”)(0).split(“:”)(0)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
request = bytes('GET /%s HTTP/1.1\r\nHost: %s\r\n\r\n' % (path, host), 'utf8')
context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
s = context.wrap_socket(s, server_hostname = real_host)
s.connect((host_real, 443))
s.sendall(request)
contest = s.recv(5000).decode
print (contest)
as context! = b”:
contest = s.recv(5000).decode
print (contest)
s.close
http_get('
The Python script above reads the HTML source code of an SSL-encrypted web page from Engineergarage.com. Please note the website URL and engineersgarage.com use HTTPS protocols. The Python script runs a TCP client and returns the HTML content of the given URL, as shown in the images below.
Configuring ESP8266/ESP32 as TCP server using sockets
The following MicroPython script configures the ESP8266 as a TCP server. The ESP8266 server uses the IP address of the connected router. When the server is requested by a client, such as a computer connected to the same WiFi connection, it returns an HTML page.
import network
import machine
station = network.WLAN(network.STA_IF)
if station.isconnected == True:
print(“Already connected”)
station.active(True)
station.connect(“SSID”, “PASSWORD”)
while station.isconnected == False:
to spend
print(“Connection successful”)
print(station.ifconfig)
html = “””
Welcome to the ESP8266 server
“””
import socket
addr = socket.getaddrinfo('0.0.0.0', 80)(0)(-1)
s = socket.socket
s.bind(address)
s.listen(1)
print('listening', address)
while True:
cl, address = s.accept
print('client connected from', address)
cl_file = cl.makefile('rwb', 0)
while True:
line = cl_file.readline
if not line or line == b'\r\n':
to break
response=html
cl.send('HTTP/1.0 200 OK\r\nContent type: text/html\r\n\r\n')
cl.send(response)
cl.close
You must replace the SSID and PASSWORD in line 8 of the code above with the SSID and network key of your own WiFi connection. To run the server, load and run the above MicroPython script on the ESP8266 using uPyCraft IDE or Thonny IDE.
The screenshot below shows the ESP8266 server running and responding to a client.
To make a request to the ESP8266 server, type the IP address returned by the station.ifconfig method into your browser's address bar. The ESP8266 server will return an HTML page in response to your computer's browser.
The screenshot below is an example of a web page returned by the ESP8266 server.