Skip to content

Commit 5d495ed

Browse files
committed
client_server: heartbeat is now a separate file.
1 parent b52b869 commit 5d495ed

File tree

4 files changed

+70
-16
lines changed

4 files changed

+70
-16
lines changed

TUTORIAL.md

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ asyncio and includes a section for complete beginners.
5454
7.4 [Testing](./TUTORIAL.md#74-testing)
5555
7.5 [A common error](./TUTORIAL.md#75-a-common-error) This can be hard to find.
5656
7.6 [Socket programming](./TUTORIAL.md#76-socket-programming)
57+
7.6.1 [WiFi issues](./TUTORIAL.md#761-wifi-issues)
5758
7.7 [Event loop constructor args](./TUTORIAL.md#77-event-loop-constructor-args)
5859
8. [Notes for beginners](./TUTORIAL.md#8-notes-for-beginners)
5960
8.1 [Problem 1: event loops](./TUTORIAL.md#81-problem-1:-event-loops)
@@ -1607,6 +1608,26 @@ I find it useful as-is but improvements are always welcome.
16071608

16081609
## 7.6 Socket programming
16091610

1611+
There are two basic approaches to socket programming under `uasyncio`. By
1612+
default sockets block until a specified read or write operation completes.
1613+
`uasyncio` supports blocking sockets by using `select.poll` to prevent them
1614+
from blocking the scheduler. In most cases it is simplest to use this
1615+
mechanism. Example client and server code may be found in the `client_server`
1616+
directory. The `userver` application uses `select.poll` explicitly to poll
1617+
the server socket. The client sockets use it implicitly in that the `uasyncio`
1618+
stream mechanism employs it.
1619+
1620+
Note that `socket.getaddrinfo` currently blocks. The time will be minimal in
1621+
the example code but if a DNS lookup is required the blocking period could be
1622+
substantial.
1623+
1624+
The second approach to socket programming is to use nonblocking sockets. This
1625+
adds complexity but is necessary in some applications, notably where
1626+
connectivity is via WiFi (see below).
1627+
1628+
At the time of writing (March 2019) support for TLS on nonblocking sockets is
1629+
under development. Its exact status is unknown (to me).
1630+
16101631
The use of nonblocking sockets requires some attention to detail. If a
16111632
nonblocking read is performed, because of server latency, there is no guarantee
16121633
that all (or any) of the requested data is returned. Likewise writes may not
@@ -1616,19 +1637,29 @@ Hence asynchronous read and write methods need to iteratively perform the
16161637
nonblocking operation until the required data has been read or written. In
16171638
practice a timeout is likely to be required to cope with server outages.
16181639

1619-
A further complication is that, at the time of writing, the ESP32 port has
1620-
issues which require rather unpleasant hacks for error-free operation.
1640+
A further complication is that the ESP32 port had issues which required rather
1641+
unpleasant hacks for error-free operation. I have not tested whether this is
1642+
still the case.
16211643

16221644
The file [sock_nonblock.py](./sock_nonblock.py) illustrates the sort of
16231645
techniques required. It is not a working demo, and solutions are likely to be
16241646
application dependent.
16251647

1626-
An alternative approach is to use blocking sockets with `StreamReader` and
1627-
`StreamWriter` instances to control polling.
1648+
### 7.6.1 WiFi issues
1649+
1650+
The `uasyncio` stream mechanism is not good at detecting WiFi outages. I have
1651+
found it necessary to use nonblocking sockets to achieve resilient operation
1652+
and client reconnection in the presence of outages.
16281653

16291654
[This doc](https://github.com/peterhinch/micropython-samples/blob/master/resilient/README.md)
16301655
describes issues I encountered in WiFi applications which keep sockets open for
1631-
long periods, and offers a solution.
1656+
long periods, and outlines a solution.
1657+
1658+
[This repo](https://github.com/peterhinch/micropython-mqtt.git) offers a
1659+
resilent asynchronous MQTT client which ensures message integrity over WiFi
1660+
outages. [This repo](https://github.com/peterhinch/micropython-iot.git)
1661+
provides a simple asynchronous full-duplex serial channel between a wirelessly
1662+
connected client and a wired server with guaranteed message delivery.
16321663

16331664
###### [Contents](./TUTORIAL.md#contents)
16341665

client_server/heartbeat.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# flash.py Heartbeat code for simple uasyncio-based echo server
2+
3+
# Released under the MIT licence
4+
# Copyright (c) Peter Hinch 2019
5+
6+
import uasyncio as asyncio
7+
from sys import platform
8+
9+
10+
async def heartbeat(tms):
11+
if platform == 'pyboard': # V1.x or D series
12+
from pyb import LED
13+
led = LED(1)
14+
elif platform == 'esp8266':
15+
from machine import Pin
16+
led = Pin(2, Pin.OUT, value=1)
17+
elif platform == 'linux':
18+
return # No LED
19+
else:
20+
raise OSError('Unsupported platform.')
21+
while True:
22+
if platform == 'pyboard':
23+
led.toggle()
24+
elif platform == 'esp8266':
25+
led(not led())
26+
await asyncio.sleep_ms(tms)

client_server/uclient.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import usocket as socket
77
import uasyncio as asyncio
88
import ujson
9+
from heartbeat import heartbeat # Optional LED flash
10+
911
server = '192.168.0.32'
1012
port = 8123
1113

@@ -41,6 +43,8 @@ def close():
4143
data[1] += 1
4244

4345
loop = asyncio.get_event_loop()
46+
# Optional fast heartbeat to confirm nonblocking operation
47+
loop.create_task(heartbeat(100))
4448
try:
4549
loop.run_until_complete(run())
4650
except KeyboardInterrupt:

client_server/userver.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,10 @@
77
import uasyncio as asyncio
88
import uselect as select
99
import ujson
10+
from heartbeat import heartbeat # Optional LED flash
1011

1112
class Server:
12-
@staticmethod
13-
async def flash(): # ESP8266 only: demo that it is nonblocking
14-
from machine import Pin
15-
pin = Pin(2, Pin.OUT)
16-
while True:
17-
pin(not pin())
18-
await asyncio.sleep_ms(100)
19-
20-
async def run(self, loop, port=8123, led=True):
13+
async def run(self, loop, port=8123):
2114
addr = socket.getaddrinfo('0.0.0.0', port, 0, socket.SOCK_STREAM)[0][-1]
2215
s_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # server socket
2316
s_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
@@ -28,8 +21,6 @@ async def run(self, loop, port=8123, led=True):
2821
poller = select.poll()
2922
poller.register(s_sock, select.POLLIN)
3023
client_id = 1 # For user feedback
31-
if led:
32-
loop.create_task(self.flash())
3324
while True:
3425
res = poller.poll(1) # 1ms block
3526
if res: # Only s_sock is polled
@@ -62,6 +53,8 @@ def close(self):
6253
sock.close()
6354

6455
loop = asyncio.get_event_loop()
56+
# Optional fast heartbeat to confirm nonblocking operation
57+
loop.create_task(heartbeat(100))
6558
server = Server()
6659
try:
6760
loop.run_until_complete(server.run(loop))

0 commit comments

Comments
 (0)