In the first sequence of this project series, I regretted that the Nano V3 is not WLAN capable. To change this, first of all, an ESP32 and an ESP8266 were wirelessly connected via NRF24L01 modules. This finally resulted in a MicroPython module, nRF24simple.py. In the second episode, I brought the Nano to talk about NRF24L01 modules with the ESP8266 as well. Episode 3 describes the coupling of a Nano V3 to the minimalistic ESP8266-01 via an I2C bus. This was the basis to connect the ATmega328 microcontroller to the WLAN. The program esp_i2c_master.py compiled functions, which send commands to the Nano could send, read and write parallel ports, output PWM signals, and measure voltages. Sequence 4 brought the module arduino_i2c.py, in which the previous functions were integrated, improved, and supplemented by additional methods.
Today we will extend the project with the WLAN interface. So welcome to the series
MicroPython on the ESP32 and ESP8266
today
AVR goes ESP with MicropPython (Part 5) - WLAN
To make a device WLAN capable, two steps beyond the "economy class" are required. First, the connection to an access point has to be established. Alternatively, an ESP8266 or an ESP32 can be an access point itself. This has advantages and disadvantages. We will come to that later.
The fact that a device has logged into a network is not enough. We want to access the infrastructure of the device. This requires a client-server structure. In our case, the ESP8266-01 will be the server and mediator to the AVR, and the Nano will play. As clients, we will use a web browser as the first approach. The server will generate a web page with forms that we fill in and send back to the server.
Image 1: The steps to the WLAN class
Alternatively, in a second approach, we can build a UDP server that we can radio, for example, with a cell phone app.
If you decide to use your own access point on the ESP8266-01, this has the advantage that you do not need a local network structure with a router. You then set one up yourself. A disadvantage is that there is no internet access. Another disadvantage is that the cell phone must first register on the ESP8266-01 access point.
Hardware
The hardware is still the same as in the fourth episode. I don't include a PC or cell phone, although it won't work without them.
1 |
|
1 |
|
1 |
|
2 |
|
1 |
|
1 |
|
1 |
|
1 |
|
1 |
|
3 |
|
1 |
|
1 |
|
various |
|
2 |
suitable USB cables |
1 |
battery 4,5V or 5V wall adapter |
(*) alternative possibility further down in the text
Nothing has changed in the circuit either.
Figure 2: IO ports and PWM LED control
I don't want to miss to point out again that the ESP8266-01 can't stand voltages higher than 3.6V on its ports. To prevent it from going into nirvana, the jumper on the FTDI adapter must absolutely be jumpered to 3.3V and the I2C lines must go through a level shifter that can convert 5V to 3.3V and vice versa. An equivalent circuit for this, with single transistors, can be found in Sequence 4. When later in the production system the Nano V3 is also operated with 3.3V, the level converter can be omitted.
Image 3: Nano V3 as I2C slave - application circuit for 3.3V
In this episode, the ports of the ATMega328 microcontroller should again be able to be addressed in their respective full width. For the implementation of this intention, we use the class ARDINO from the module arduino_i2c.py. Here again, is the summary of the designations of ATMEL AVR and the Nano V3.
AVR Port |
PD0 RXD |
PD1 TXD |
PD2 |
PD3 |
PD4 |
PD5 |
PD6 |
PD7 |
Nano V3 |
D0 RXD |
D1 TXD |
D2 |
D3 |
D4 |
D5 |
D6 |
D7 |
AVR Port |
PB0 |
PB1 |
PB2 |
PB3 |
PB4 |
PB5 |
Nano V3 |
D8 |
D9 |
D10 |
D11 |
D12 |
D13 |
AVR Port |
PC0 |
PC1 |
PC2 |
PC3 |
PC4 |
PC5 |
|
|
Nano V3 |
A0 |
A1 |
A2 |
A3 |
A4 |
A5 |
A6* |
A7* |
Each port group has three registers, an output register called PORT, an input register PIN and a data direction register DDR. Our goal is to describe and query all three types in full width. Also single, specific bit operations should be possible. Commands and data travel over the I2C bus, the ESP8266-01 is the client, i.e. the master, which sends the SCL clocks.
Group C dances a little bit out of line. The C-pins serve their function as digital ports and also as inputs of the analog mutiplexer which in turn feeds the signals to the ADC (analog-to-digital converter). Today we will use the input A3 in this sense. We connect it to the operating voltage of the ESP8266-01. A4 and A5, aka PC4 and PC5, connect to the I2C hardware. On the Nano V3 the functions of this interface are realized by hardware, the I2C interface of the ESP8266-01 is a software solution.
We connect the RGB LED to IO lines of port C via three resistors. The pins PC0 (blue), PC1 (green), and PC2 (red) are used for this purpose. The single LED at port PD3 is connected to a PWM output and can therefore be dimmed.
The software
For flashing and programming the ESP32:
Thonny or
For the Nano V3:
arduino_as_slave.ino For communication with the ESP8266-01 and for processing commands
Used firmware for the ESP8266/ESP32:
Please choose a stable version (ESP8266-01 with 1MB Version 1.18 Status: 05.03.2022)
The MicroPython programs for the project:
arduino_i2c.py: Module with the class ARDUINO
ardutest.py : Test program for the module
arduino_goes_wlan_TCP.py: For communication with the Arduino via browser
arduino_goes_wlan_UDP.py: For communication with the Arduino via mobile app
Other software:
Packet transmitter Download page
Packet transmitter Windows Install Version
Packet Sender Windows Portable
Packet Sender Linux
MicroPython - Language - Modules and Programs
For the installation of Thonny you find here a detailed manual (English version). In it there is also a description of how the MicropythonFirmware (as of 02/05/2022) on the ESP chip. burned is burned.
MicroPython is an interpreter language. The main difference to the Arduino IDE, where you always and only flash whole programs, is that you only have to flash the MicroPython firmware once at the beginning to the ESP32 so that the controller understands MicroPython instructions. You can use Thonny, µPyCraft, or esptool.py to do this. For Thonny, I have described the process described here
At this point a few words about flashing the ESP8266-01. Unlike its bigger siblings, the ESP8266-01 does not have an automatic flashing function on board. Manual work is required here.
The flash process is divided into two parts, first erase flash memory and second transfer firmware. The following list is an excerpt from the Description of the flash process:
Once the firmware is flashed, you can casually talk to your controller one-on-one, test individual commands, and immediately see the response without having to compile and transfer an entire program first. In fact, that's what bothers me about the Arduino IDE. You simply save an enormous amount of time if you can do simple tests of the syntax and the hardware up to trying out and refining functions and whole program parts via the command line in advance before you knit a program out of it. For this purpose, I also like to create small test programs from time to time. As a kind of macro, they summarize recurring commands. From such program fragments sometimes whole applications are developed.
Autostart
If you want the program to start autonomously when the controller is switched on, copy the program text into a newly created blank file. Save this file as boot.py in the workspace and upload it to the ESP chip. The program will start automatically at the next reset or power-on.
Test programs
Manually, programs are started from the current editor window in the Thonny IDE via the F5 key. This is faster than clicking on the Start button, or via the menu Run. Only the modules used in the program must be in the flash of the ESP32.
In between times Arduino IDE again?
If you want to use the controller together with the Arduino IDE again later, simply flash the program in the usual way. However, the ESP32/ESP8266 will then have forgotten that it ever spoke MicroPython. Conversely, any Espressif chip that contains a compiled program from the Arduino IDE or the AT firmware or LUA or ... can easily be flashed with the MicroPython firmware. The process is always here described.
The XMitter program and the server construction kit
For the WLAN access of my ESPs, I put together a set of rudimentary Python scripts in a directory.
accesspoint.py, multiple_WLANs.py, single_WLAN.py, TCP webserver.py, UDP server_blank.py, UDP server.py
They have half the character of modules but are still not used as such because they need certain changes or adaptations from case to case.
Let's start with the compilation of a program. In the beginning, we will use the test program from episode 4 as a basis, ardutest.py which we immediately extend a little bit. From the module machine, we need the operation of the I2C interface and Pin. From arduino_i2c we get everything, including the constants defined there into our namespace (aka Scope).
# arduino_goes_wlan_TCP.py
# Rev1.1 - 02.03.2022
from machine import SoftI2C, Pin
from arduino_i2c import *
SCL=Pin(2)
SDA=Pin(0)
i2c=SoftI2C(scl=SCL, sda=SDA, freq=100000)
a=ARDUINO(i2c,u0=4.73)
a.writeIO(DDR,C,7)
rt=2; gn=1; bl=0
The choice of I2C pins is not very large: 0 and 2, there are only two. With this we instantiate the interface and right after that an ARDUINO object called a. We use this immediately to program the bits DDRC0, DDRC1, and DDRC2 of PORTC to output. In the following line we assign the colors.
The WLAN access
My module single_WLAN.py has to accept slight changes because I can't run an RGB LED directly on the ESP8266-01 due to chronic pin shortage. What is left looks like this. We start with a few more imports. The rest has plenty of comments, so further elaboration is probably not necessary. Just one thing, don't forget your own credentials to access the WLAN router at my SSID and myPass to enter.
# Module for contact attempts to a WLAN router
import os,sys # System and file instructions
from machine import reset
from time import sleep, ticks_ms
import network
def hexMac(byteMac):
"""
The hexMAC function takes the MAC address in bytecode
and forms a string from it for the return
"""
macString =""
for i in range(0,len(byteMac)):
macString += hex(byteMac[i])[2:]
if i <len(byteMac)-1 :
macString +="-"
return macString
# ***************** Connect to WLAN **********************
connectStatus = {
0: "STAT_IDLE",
1: "STAT_CONNECTING",
5: "STAT_GOT_IP",
2: "STAT_WRONG_PASSWORD",
3: "NO AP FOUND",
4: "STAT_CONNECT_FAIL",
}
mySSID = 'Here_goes_your_SSID'
myPass = 'Here_goes_your_Password'
# Be sure to turn off the AP interface
nac=network.WLAN(network.AP_IF)
nac.active(False)
nac=None
# We create a network interface instance
# We need the STATION interface
nic = network.WLAN(network.STA_IF)
nic.active(False) # reset first
# Query the MAC address to enter in the router,
# so that the release of the access can take place
MAC = nic.config('mac')
myID=hexMac(MAC)
print("Client ID",myID)
# We activate the network interface
nic.active(True)
# Establish the connection
# We set a static IP address
nic.ifconfig(("10.0.1.91","255.255.255.0","10.0.1.20","10.0.1.100"))
# Log on to the WLAN router
nic.connect(mySSID, myPass)
if not nic.isconnected():
# wait until the connection to the access point is established
while not nic.isconnected():
print("{}.".format(nic.status()),end='')
sleep(1) # we ask in seconds rhythm
# if connected, show connection status & config data
print("\nConnectionStatus: ",connectStatus[nic.status()])
if nic.isconnected():
# Was the configuration successful? Control
STAconf = nic.ifconfig()
print("STA-IP:\t\t",STAconf[0],"\nSTA-NETMASK:\t",\
STAconf[1],"\nSTA-GATEWAY:\t",STAconf[2] ,sep='')
One more line is important:
nic.ifconfig(("10.0.1.91","255.255.255.0","10.0.1.20","10.0.1.100"))
The addresses specified here must of course correspond to your local network. Assuming your router has the IP 192.168.12.1/24 = 192.168.12.1 Net-Mask 255.255.255.0. Further assuming the IP 192.168.12.55 in your network is still free, the router is at the same time the gateway to the Internet and also does the name resolution (DNS). Then this line should look like this:
nic.ifconfig(("192.168.12.55","255.255.255.0","192.168.12.1","192.168.12.1"))
Please note the double brackets. The outer pair belongs to the function call, the inner one combines the 4 strings into a tuple, as the method ifconfig() requires.
After this sequence has run, the terminal area should show an output of the following type.
>>> %Run -c $EDITOR_CONTENT
Constructor of Arduino-Interface
Arduino @ 0x24
Client-ID ec-fa-bc-bc-47-c4
#32 ets_task(4020ee60, 28, 3fff92d0, 10)
1.1.1.
Connection status: STAT_GOT_IP
STA-IP: 10.0.1.91
STA-NETMASK: 255.255.255.0
STA-GATEWAY: 10.0.1.20
So after three seconds, the connection was established. The IP address should always be fixed if the device is a server. You surely want to reach it always under the same URL and not have to guess every time which IP the DNS of the router has given the device.
At this point, we have now created the connection to the local network. The only thing missing is a tool to use this channel. We can basically choose one from a whole range of services, TCP web server, TCP client, UDP server, UDP client, MQTT, FTP, Telnet...
Let's start with a TCP web server, the UDP server comes next.
The TCP web server
This server is, just like the WLAN package, a file that I can easily include in applications. Depending on the environment, adjustments have to be made, like here. Because no RGB LED can be connected directly to the ESP8266-01, I delete all lines that would require that. In the end, this makes the code leaner and that is not wrong with the ESP8266.
A web server has to send a web page to the browser, which has to contain appropriate controls, whose data we need to change the settings on the Nano V3 we need. In this case, I decided to use three forms with separate send buttons. So there's a lot of HTML code that we'll look at first.
<HTML>
<HEAD>
<TITLE> Arduino control </TITLE>
<link rel="icon" href="data:;base64,=">
</HEAD>
<BODY>
<H1> ARDUINO-RC </H1>
<FORM METHOD=GET ACTION="http://10.0.1.91:9009">
LED control:
<input type=text name=led size= 8 maxlength=6 value="120">
<input type=submit value="FADE LED" name="LED">
</FORM>
<FORM METHOD=GET ACTION="http://10.0.1.91:9009">
Switching the RGB LED:<BR>
RED <INPUT TYPE="checkbox" NAME="RED" XXX><BR>
GRÜN <INPUT TYPE="checkbox" NAME="GREEN" XXX ><BR>
BLUE <INPUT TYPE="checkbox" NAME="BLUE" XXX ><BR>
<INPUT TYPE="submit" value="SET_RGB" name="RGB">
</FORM>
<FORM METHOD=GET ACTION="http://10.0.1.91:9009">
Operating voltage<INPUT TYPE="submit" value="GET_Ubat" name="UBAT"> is: XXX V
</FORM>
</BODY>
</HTML>
In black, you see the basic structure of an HTML page, the so-called tags. This text in the angle brackets is formatting instructions for the browser, which converts HTML documents into normally formatted text with images and links. HTML is the acronym for Hypertext Markup Language, which is a text-based, machine-readable language for formatting and structuring text. Most tags have an opening tag and a closing tag. The names are the same, except that the closing tag is preceded by a "/".
So the minimum frame for an HTML document looks like this:
<HTML>
<HEAD>
…
</HEAD>
<BODY>
…
</BODY>
</HTML>
The individual forms are shown in different shades of green. Each one starts by specifying a transmission method, here it is GET. ACTION specifies the destination address of the request. The input tags define certain form elements. We work with a text field, 3 checkboxes, and the submit buttons. In between there is normal text outside the tags. In the browser, it looks like this - when it's completely finished.
Image 4: Form in the browser
As an attentive reader, you will have noticed the five places marked in red in the HTML text. This is where variable content must be inserted. So the text has to be composed of several blocks. For this, I use three features of MicroPython.
- Text constants can be enclosed in quotation marks ('Text') or in quotation marks ('Text').
- Delimiters in the text become part of the text if they differ from the surrounding ones.
>>> a='This is a "special" property
>>> print(a)
This is a "special" property - Text constants that span multiple lines are enclosed in triple delimiters.
Normal quotation marks occur in the page text, so I have enclosed the text blocks in three quotation marks. This is what it looks like in the middle section.
….
<FORM METHOD=GET ACTION="http://10.0.1.91:9009">
Switching the RGB LED:<BR>
RED <INPUT TYPE="checkbox" NAME="RED"
'''
rgb_R=''
><BR>
GRÜN <INPUT TYPE="checkbox" NAME="GREEN"
'''
rgb_G=''
><BR>
BLUE <INPUT TYPE="checkbox" NAME="BLUE"
'''
….
All parts are stored in variables, so that at the end of the job routine they are webpage() job routine, they can be assembled with the variable elements.
def web_page(pos):
global pwm,chkR,chkG,chkB,voltage
if request.find("GET / HTTP")!=-1:
pwm="?"
if request.find("LED")!=-1:
start=request.find("=")
end=request.find("&")
pwm=int(request[start+1:end])
a.writeAnalog(3,pwm)
pwm=str(pwm)
if request.find("RGB")!=-1:
if request.find("RED")!=-1:
chkR=checked
a.setBit(PORT,C,rt,1)
else:
chkR=""
a.setBit(PORT,C,rt,0)
if request.find("GREEN")!=-1:
chkG=checked
a.setBit(PORT,C,gn,1)
else:
chkG=""
a.setBit(PORT,C,gn,0)
if request.find("BLUE")!=-1:
chkB=checked
a.setBit(PORT,C,bl,1)
else:
chkB=""
a.setBit(PORT,C,bl,0)
if request.find("UBAT")!=-1:
voltage=str(a.readAnalog(3,True,3))
response=head+pwm+fading+chkR+rgb_R+chkG+rgb_G+chkB+rgb_B+voltage+ubat
return reply
We start with the declaration of the variable pwm,chkR,chkG,chkB, and voltage as global because the values are to be changed in the function. If we do not do this, they are considered local variables. Their content is forgotten after leaving the function and is not passed outside.
The four if constructs knock the first 50 characters of the query for the occurrence of certain text passages. A request sent in the browser with 10.0.1.91:9009/ contains the passage "GET / HTTP". As a result, the server sends back a bare home page to the browser (Figure 2). If one or more of the checkboxes were activated and the SET_RGB button was clicked, then the browser sends the following request.
GET /?RED=on&GREEN=on&RGB=SET_RGB HTTP/1.1
Only the fields that were checked appear in the query string. So we search for RED, GREEN and BLUE, set the chkX -variables to checked or the empty string and turn the corresponding LED on or off by setting the setBit-command to the Nano V3.
Finds a UBAT in the request string, we get the value of the operating voltage of the ESP8266-01 and assign it to the variable voltage. Then the string for the complete page text is assembled and returned.
After discussing the prerequisites (aka preparatory actions) let's see what the main part of the server code does.
led=Pin(2,Pin.OUT,)
led.value(1) # LED at pin 2 follows negative logic
try:
import usocket as socket
except:
import socket
checked='checked="checked"'
pwm="0"
chkR=''
chkB=''
chkG=''
voltage="???"
We hijack the LED on the ESP8266-01 for output, import the socket-module and preallocate the global variables. The third "checked" should appear in quotes, so we enclose the entire string in single quotation marks.
Then we set up a socket as a receiving portal for requests. We already set up the IP address in the WIFI part: 10.0.1.91. Now we define a port number, 9009. We instantiate the socket object server from the IPv4 address family for TCP. The socket is told that the address 10.0.1.91:9009 is to be used repeatedly when the socket is restarted. The timeout for the receive loop is set to 0.1 seconds and then we bind the socket to the set address, as IP the existing one is used by the ' '. IP and port number form a tuple again, that's why the double round brackets. The server should respond to exactly one incoming request.
portNum=9009
print("Request server socket")
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
server.settimeout(0.1)
server.bind(('', portNum)) # bind to local IP and port number 9009
server.listen(1) Accept until to 1 incoming Request(n)
print("Receive requests on ",STAconf[0],":",portNum, sep='')
This brings us to the server loop.
Image 5: Client-server principle
The receive loop, which starts with server.accept() is exited after 0.1 seconds if no request has arrived. An exception is then thrown, which we catch with try - except. If we had not set a timeout (blocking socket), then the receive loop would block all subsequent actions until a request arrives. This would be bad for the case when in the infinite loop there is still further actions are to be performed cyclically in the endless loop. If a message has arrived, then there is server.accept() returns a communication socket c and the socket address of the sender of the message. The further conversation is handled via the communication socket c (brown).
while 1: # Infinite loop
try:
c, addr = server.accept() # Accept request
print('Got connection from {}:{}'.format(addr[0],addr[1]))
led.value(0)
# c is a bytes object and to be decoded as string
# so that string methods can be applied to it.
rawRequest=c.recv(1024)[0:50]
request = rawRequest.decode("utf-8")
print("*******Request**************XXX\n",request)
getQuery=request.find("GET /?")
getBlank=request.find("GET / ")
if getQuery == 0 or getBlank == 0:
print("**********GET-Position: {},{}\n\n".\
format(getQuery,getBlank))
response = web_page()
#print("---------------\n",response,"\n-------------")
c.send('HTTP/1.1 200 OK\n')
c.send('Content-Type: text/html\n')
c.sendall(response)
else:
print("#############\nQuery not valid\n#############")
response = web_page()
c.send('HTTP/1.1 200 OK\n')
c.send('Content-Type: text/html\n')
c.sendall(response)
sleep(0.1)
c.close()
except OSError:
pass
led.value(1)
From the receive buffer we read rawRequest=c.recv(1024)[0:50] the first 50 characters. The bytes object is read with request = rawRequest.decode("utf-8") into a string. The print commands are not essential, but tell us what the server process is doing. They can also be omitted.
A valid request must contain the characters "GET / " or "GET /?" at the very beginning at position 0. If this is the case, we pass the string to the parser web_page(). Its response goes to the variable response. We build a header for the page ('http/1.1 200 OK\n' ...) and then send the entire contents of the page to the requesting browser.
But if the received text starts with something else, then it is not a valid request in our sense. To make the browser happy, we simply send it the last current page again, since the data is still in the variables. We have already solved the browser's annoying request for a file called favicon.ico with the line
<link rel="icon" href="data:;base64,=">
blocked in the header of our HTML code.
After the communication is complete, we give the socket c a few more clocks to close the connection and then close the socket. There is nothing to handle as an exception if it was just a timeout. Exceptions based on other errors will still cause the program to terminate.
If Nano V3 and the ESP8266-01 are ready for use, we load the module arduino_i2c.py into the flash and open the file arduino_goes_wlan_TCP.py in an editor window of Thonny. After starting with F5, the server reports.
>>> %Run -c $EDITOR_CONTENT
Constructor of Arduino-Interface
Arduino @ 0x24
Client-ID ec-fa-bc-bc-47-c4
#52 ets_task(4020ee60, 28, 3fff92d0, 10)
1.1.1.
Connection status: STAT_GOT_IP
STA-IP: 10.0.1.91
STA-NETMASK: 255.255.255.0
STA-GATEWAY: 10.0.1.20
Request Server-Socket to
Receive Requests at 10.0.1.91:9009
We start the first request with the URL: http://10.0.1.91:9009. Using the form elements we can now define the actions on the Nano V3. Pat on the back! AVR went ESP!
The UDP server
The UDP server does not send back a web page, but UDP packets, which it laces and sends to UDP clients based on previously received messages or on its own "decision".
The whole part of the program, from setting up the ARDUINO-object to the end of the WLAN access, is identical to the one described under WLAN access described procedure.
Setting up the socket also differs only slightly from its TCP brother. This time, however, we set up an abort button as well and instantiate a UDP socket.
# udp_server.py
# rudimentary UDP server non-blocking
# a WLAN connection is required
# **********************************
#from machine import pin
import socket
button=Pin(0,Pin.IN,Pin.PULL_UP)
#
# Set up UDP server
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(('', 9009))
print("waiting on port 9009...")
s.settimeout(0.1)
The parser is a bit leaner, because it doesn't need to parse HTML code, but only command strings of the following structure, which is adapted to the class ARDUINO.
command: parameter1;parameter2
command |
parameter1 |
parameter2 |
FADE |
line |
value |
RGB |
(rgb) |
|
UBAT |
digits |
|
The incoming request is first separated at the ":" to separate the command. The if-elif structure checks for valid commands and initiates the examination of the parameters. The parsed contents are passed to the ARDUINO instance a in an appropriate form. In addition, a response is generated in each case. (rgb] states that any combination of the three characters can be specified in any order.
def doit(request):
reply="done"
command,job=request.split(":")
if command.upper()=="FADE":
line,value=job.split(";")
line=int(line); value=int(value)
a.writeAnalog(line,value)
elif command.upper()=="RGB": # gb
a.writeIO(DDR,C,0b111)
cols=0
job=job.upper()
if "B" in job:cols=1
if "G" in job:cols+=2
if "R" in job:cols+=4
a.writeIO(PORT,C,cols)
cols=a.readIO(PORT,C)[0] & 0b111
reply="RGB set to {:03b}".format(cols)
elif command.upper()=="UBAT":
dig=int(job)
value=a.readAnalog(3,digits=int(dig))
reply="ESP86-01 U0 = {}".format(value)
return reply
The server loop is also very simple. Here, too, the timeout exception is caught by try - except.
With UDP there is no communication socket as with TCP, because UDP works as an unsecured connection protocol without handshake and controls. Therefore the protocol is faster and uncomplicated, comparable to RS232. If the function recvfrom() detects the receipt of characters, the characters and the address of the sender are returned as a tuple. We extract the tuple, as with TCP, into the bytes object rec and the socket address adr. The byte object is decoded to the string and, if present, freed from interfering delimiters, \r = carriage return and \n line feed.
Then we specify the string doit() to parse and execute the commands. We encode the response again as a bytes object and send it back to the client. With further sendto() commands, we could send the same message to more recipients. This is sometimes quite useful for debugging purposes.
# Server loop
while 1:
gc.collect()
try:
# receive message
rec,adr=s.recvfrom(150)
rec=rec.decode().strip("\r\n")
print(rec,adr)
# parse message and
# trigger actions
answer=doit(rec)
# Send results encoded or as string
# it can be sent to multiple addresses
reply=answer.encode()
s.sendto(reply,adr)
rec=""
except OSError:
pass # timeout override
if button.value()==0:
sys.exit()
The last two lines allow a clean exit from the loop without Ctrl+C when the Flash key is pressed.
Nano V3 and ESP8266-01 are probably still built up from the previous attempt. Then we load the program arduino_goes_wlan_UDP.py into an editor window, the module arduino_i2c.py is already in the flash of the ESP8266-01. Well then, start with F5.
Yes - and how should I test this now? You probably ask yourself, because you can't do that with the browser. Well, but there is a great tool that handles both TCP and UDP as server and client (and even more). The best thing to do is to download the portable version of the program "Packet Sender and unpack it in a directory of your choice. A directory "PacketSenderPortable" will be created. Then start the file contained in it packetsender.exe.
Before you start, you must assign a custom port number for your PC.
Image 6: Packetsender Settings
Image 7: Assign port number for UDP
Then we prepare to send the first UDP command.
Image 8: The first UDP command
- The name is only important in case you want to save the command.
- Here we enter the command for the ESP8266-01.
- The address of the destination
- The port address of the destination
- The transmission protocol is UDP
- And off goes the mail!
The bottom two lines list our command and the response from ESP8266-01 in detail. Now you can test further commands. But one more candy is waiting for you.
The UDP Android app
Now of course it would be nice if the Nano V3 would not only be controllable via the PC. How about the cell phone? The MIT-AppInventor2 is a fine tool with which even people who have no idea about programming on the Android system can develop programs, excuse me, wanted to say apps, quite well and easily. Did you like to play with building blocks, wood or Lego in the past? Then you'll get along with AppInventor2 as well, because it only fits together what belongs together. You can find a tutorial to get started here.
The tool is not installed on the PC, you simply work via your browser. On the cell phone, the app is downloaded from the Google Playstore. MIT AI2 Companion is downloaded and installed. Thus, after establishing a connection with your PC, you can develop your own apps on it and see 1:1 how it looks on your phone. The programmed functions and processes can also be tested at the same time.
Image 9: Connect to AI Companion
If everything goes as you want, create an apk file that you can install permanently on your phone. This is what the screen of the app I built for our project looks like:
Image 10: Screenshot from the app:
And this is the design in the AppInventor2 window:
Image 11: Design of the app
And this is a (small) piece of the blueprint:
Image 12: A piece of the blueprint.
For the curious there is to the applet a aia-file. You can open this file in the MIT-AI2 via the Projects-menu to import, modify and add to them as you wish. Don't be confused by the variety of possibilities. Try to create small, manageable apps in the beginning. For each block, you can request help by right-clicking. If you use the list of my blogposts published so far, you will find more projects for which there is an app (for example Robot Car, Temperature monitor, Mosquito repellent), which you can take as an example.
For those in a hurry I also have something, the finished apk-filewhich you can transfer to your cell phone via Bluetooth, for example. There you start the file for installation. How to do this in detail is also described in the manual in the last section.
For the control to work, you must of course adapt the IP addresses to your network. For this, compare the chapter "The WLAN access". And one more thing, the UDP extension of Ulli's robot page has the same problem with the IP address as MicroPython. Only with MicroPython you can solve the problem by the line
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
but not in the mobile app. The problem occurs when you have to restart the listener object while the app is running, for example after a network failure. Then there is error message 2, and it says that the already used address (actually the socket address) cannot be assigned a second time. The culprit is not the IP address, but the port number. So close the app and restart it.
Another problem is again the socket address of the listener and again the culprit is the port number. The phone randomly dices itself a port number when sending a packet. The ESP8266-01 receives this number correctly and tries to send the result back to this socket. The sending also works, but the UDP listener in the cell phone has no idea that its own transmitter has assigned this port number and that as a result the listener should receive the packet. So we wait forever for the answer from ESP8266-01.
The problem does not occur with the packet transmitter, because it always sends packets with the identical port number for transmitter and receiver, which we have set in the program.
Image 13: The cell phone says: wrong house number
So we assign a fixed port number both in the ESP8266-01 and in the mobile app.
Image 14: Now the house number is correct
In the server loop it looks like this.
# Server loop
while 1:
gc.collect()
try:
# receive message
rec,adr=s.recvfrom(150)
rec=rec.decode().strip("\r\n")
print(rec,adr)
remoteIP,_= adr
# parse message and
# trigger actions
answer=doit(rec)
# Send results encoded or as string
# it can be sent to multiple addresses
reply=answer.encode()
s.sendto(reply,(remoteIP,9091))
rec=""
except OSError:
pass # timeout override
if button.value()==0:
sys.exit()
In the two files for the mobile app arduino_rc.aia and arduino_rc.apk the fixed port number is already taken into account, in the file arduino_goes_wlan_UDP.py as well. Only if you have entered the program text yourself, you still have to insert, respectively change the lines.
Image 15: Fixed port number at ESP8266
In the mobile app you can change the IP addresses and port numbers while the app is running. With the numeric keypad and an input function, changing the port number would also be possible on the ESP8266-01. With this suggestion the topic circle of this blog sequence closes.
Have fun with the WLAN - ESP8266-01 - AVR - Bridge and good luck exploring the MIT-AppInventor2.