kopia lustrzana https://github.com/peterhinch/micropython-samples
152 wiersze
5.5 KiB
Python
152 wiersze
5.5 KiB
Python
# client_w.py Demo of a resilient asynchronous full-duplex ESP8266 client
|
|
|
|
# Released under the MIT licence.
|
|
# Copyright (C) Peter Hinch 2018
|
|
|
|
import usocket as socket
|
|
import uasyncio as asyncio
|
|
import ujson
|
|
import network
|
|
import utime
|
|
from machine import Pin
|
|
import primitives as asyn # Stripped-down asyn.py
|
|
# Get local config. ID is string of form '1\n'
|
|
from client_id import MY_ID, PORT, SERVER
|
|
|
|
|
|
class Client():
|
|
def __init__(self, timeout, loop):
|
|
self.timeout = timeout
|
|
self.led = Pin(2, Pin.OUT, value = 1)
|
|
self._sta_if = network.WLAN(network.STA_IF)
|
|
self._sta_if.active(True)
|
|
self.server = socket.getaddrinfo(SERVER, PORT)[0][-1] # server read
|
|
self.evfail = asyn.Event(100)
|
|
self.lock = asyn.Lock(100) # 100ms pause
|
|
self.connects = 0 # Connect count
|
|
self.sock = None
|
|
loop.create_task(self._run(loop))
|
|
|
|
# Make an attempt to connect to WiFi. May not succeed.
|
|
async def _connect(self, s):
|
|
print('Connecting to WiFi')
|
|
s.active(True)
|
|
s.connect() # ESP8266 remembers connection.
|
|
# Break out on fail or success.
|
|
while s.status() == network.STAT_CONNECTING:
|
|
await asyncio.sleep(1)
|
|
t = utime.ticks_ms()
|
|
print('Checking WiFi stability for {}ms'.format(2 * self.timeout))
|
|
# Timeout ensures stable WiFi and forces minimum outage duration
|
|
while s.isconnected() and utime.ticks_diff(utime.ticks_ms(), t) < 2 * self.timeout:
|
|
await asyncio.sleep(1)
|
|
|
|
async def _run(self, loop):
|
|
s = self._sta_if
|
|
while True:
|
|
while not s.isconnected(): # Try until stable for 2*server timeout
|
|
await self._connect(s)
|
|
print('WiFi OK')
|
|
self.sock = socket.socket()
|
|
try:
|
|
self.sock.connect(self.server)
|
|
self.sock.setblocking(False)
|
|
await self.send(self.sock, MY_ID) # Can throw OSError
|
|
except OSError:
|
|
pass
|
|
else:
|
|
self.evfail.clear()
|
|
loop.create_task(asyn.Cancellable(self.reader)())
|
|
loop.create_task(asyn.Cancellable(self.writer)())
|
|
loop.create_task(asyn.Cancellable(self._keepalive)())
|
|
await self.evfail # Pause until something goes wrong
|
|
await asyn.Cancellable.cancel_all()
|
|
self.close() # Close sockets
|
|
print('Fail detected. Coros stopped, disconnecting.')
|
|
s.disconnect()
|
|
await asyncio.sleep(1)
|
|
while s.isconnected():
|
|
await asyncio.sleep(1)
|
|
|
|
@asyn.cancellable
|
|
async def reader(self):
|
|
c = self.connects # Count and transmit successful connects
|
|
try:
|
|
while True:
|
|
r = await self.readline() # OSError on fail
|
|
if c == self.connects: # If read succeeded
|
|
self.connects += 1 # update connect count
|
|
d = ujson.loads(r)
|
|
print('Got data', d)
|
|
except OSError:
|
|
self.evfail.set()
|
|
|
|
@asyn.cancellable
|
|
async def writer(self):
|
|
data = [0, 0]
|
|
try:
|
|
while True:
|
|
data[0] = self.connects # Send connection count
|
|
async with self.lock:
|
|
await self.send(self.sock, '{}\n'.format(ujson.dumps(data)))
|
|
print('Sent data', data)
|
|
data[1] += 1 # Packet counter
|
|
await asyncio.sleep(5)
|
|
except OSError:
|
|
self.evfail.set()
|
|
|
|
@asyn.cancellable
|
|
async def _keepalive(self):
|
|
tim = self.timeout * 2 // 3 # Ensure >= 1 keepalives in server t/o
|
|
try:
|
|
while True:
|
|
await asyncio.sleep_ms(tim)
|
|
async with self.lock:
|
|
await self.send(self.sock, '\n')
|
|
except OSError:
|
|
self.evfail.set()
|
|
|
|
# Read a line from nonblocking socket: reads can return partial data which
|
|
# are joined into a line. Blank lines are keepalive packets which reset
|
|
# the timeout: readline() pauses until a complete line has been received.
|
|
async def readline(self):
|
|
line = b''
|
|
start = utime.ticks_ms()
|
|
while True:
|
|
if line.endswith(b'\n'):
|
|
if len(line) > 1:
|
|
return line
|
|
line = b''
|
|
start = utime.ticks_ms() # Blank line is keepalive
|
|
self.led(not self.led())
|
|
await asyncio.sleep_ms(100) # nonzero wait seems empirically necessary
|
|
d = self.sock.readline()
|
|
if d == b'':
|
|
raise OSError
|
|
if d is not None:
|
|
line = b''.join((line, d))
|
|
if utime.ticks_diff(utime.ticks_ms(), start) > self.timeout:
|
|
raise OSError
|
|
|
|
async def send(self, s, d): # Write a line to either socket.
|
|
start = utime.ticks_ms()
|
|
while len(d):
|
|
ns = s.send(d) # OSError if client fails
|
|
d = d[ns:] # Possible partial write
|
|
await asyncio.sleep_ms(100)
|
|
if utime.ticks_diff(utime.ticks_ms(), start) > self.timeout:
|
|
raise OSError
|
|
|
|
def close(self):
|
|
print('Closing sockets.')
|
|
if isinstance(self.sock, socket.socket):
|
|
self.sock.close()
|
|
|
|
|
|
loop = asyncio.get_event_loop()
|
|
client = Client(1500, loop) # Server timeout set by server side app: 1.5s
|
|
try:
|
|
loop.run_forever()
|
|
finally:
|
|
client.close() # Close sockets in case of ctrl-C or bug
|