kopia lustrzana https://github.com/peterhinch/micropython-samples
Add temporary dir uasyncio_iostream.
rodzic
123adeda98
commit
a439064774
|
@ -0,0 +1,284 @@
|
|||
import uerrno
|
||||
import uselect as select
|
||||
import usocket as _socket
|
||||
from uasyncio.core import *
|
||||
|
||||
|
||||
DEBUG = 0
|
||||
log = None
|
||||
|
||||
def set_debug(val):
|
||||
global DEBUG, log
|
||||
DEBUG = val
|
||||
if val:
|
||||
import logging
|
||||
log = logging.getLogger("uasyncio")
|
||||
|
||||
# add_writer causes read failure if passed the same sock instance as was passed
|
||||
# to add_reader. Cand we fix this by maintaining two object maps?
|
||||
class PollEventLoop(EventLoop):
|
||||
|
||||
def __init__(self, runq_len=16, waitq_len=16):
|
||||
EventLoop.__init__(self, runq_len, waitq_len)
|
||||
self.poller = select.poll()
|
||||
self.rdobjmap = {}
|
||||
self.wrobjmap = {}
|
||||
self.flags = {}
|
||||
|
||||
# Remove registration of sock for reading or writing.
|
||||
def _unregister(self, sock, objmap, flag):
|
||||
# If StreamWriter.awrite() wrote entire buf on 1st pass sock will never
|
||||
# have been registered. So test for presence in .flags.
|
||||
if id(sock) in self.flags:
|
||||
flags = self.flags[id(sock)]
|
||||
if flags & flag: # flag is currently registered
|
||||
flags &= ~flag
|
||||
if flags:
|
||||
self.flags[id(sock)] = flags
|
||||
self.poller.register(sock, flags)
|
||||
else:
|
||||
del self.flags[id(sock)]
|
||||
self.poller.unregister(sock)
|
||||
del objmap[id(sock)]
|
||||
|
||||
# Additively register sock for reading or writing
|
||||
def _register(self, sock, flag):
|
||||
if id(sock) in self.flags:
|
||||
self.flags[id(sock)] |= flag
|
||||
else:
|
||||
self.flags[id(sock)] = flag
|
||||
self.poller.register(sock, self.flags[id(sock)])
|
||||
|
||||
def add_reader(self, sock, cb, *args):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("add_reader%s", (sock, cb, args))
|
||||
self._register(sock, select.POLLIN)
|
||||
if args:
|
||||
self.rdobjmap[id(sock)] = (cb, args)
|
||||
else:
|
||||
self.rdobjmap[id(sock)] = cb
|
||||
|
||||
def remove_reader(self, sock):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("remove_reader(%s)", sock)
|
||||
self._unregister(sock, self.rdobjmap, select.POLLIN)
|
||||
|
||||
def add_writer(self, sock, cb, *args):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("add_writer%s", (sock, cb, args))
|
||||
self._register(sock, select.POLLOUT)
|
||||
if args:
|
||||
self.wrobjmap[id(sock)] = (cb, args)
|
||||
else:
|
||||
self.wrobjmap[id(sock)] = cb
|
||||
|
||||
def remove_writer(self, sock):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("remove_writer(%s)", sock)
|
||||
self._unregister(sock, self.wrobjmap, select.POLLOUT)
|
||||
|
||||
def wait(self, delay):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("poll.wait(%d)", delay)
|
||||
# We need one-shot behavior (second arg of 1 to .poll())
|
||||
res = self.poller.ipoll(delay, 1)
|
||||
#log.debug("poll result: %s", res)
|
||||
for sock, ev in res:
|
||||
if ev & select.POLLOUT:
|
||||
cb = self.wrobjmap[id(sock)]
|
||||
# Test code. Invalidate objmap: this ensures an exception is thrown
|
||||
# rather than exhibiting weird behaviour when testing.
|
||||
self.wrobjmap[id(sock)] = None # TEST
|
||||
if DEBUG and __debug__:
|
||||
log.debug("Calling IO callback: %r", cb)
|
||||
if isinstance(cb, tuple):
|
||||
cb[0](*cb[1])
|
||||
else:
|
||||
cb.pend_throw(None)
|
||||
self.call_soon(cb)
|
||||
if ev & select.POLLIN:
|
||||
cb = self.rdobjmap[id(sock)]
|
||||
self.rdobjmap[id(sock)] = None # TEST
|
||||
if ev & (select.POLLHUP | select.POLLERR):
|
||||
# These events are returned even if not requested, and
|
||||
# are sticky, i.e. will be returned again and again.
|
||||
# If the caller doesn't do proper error handling and
|
||||
# unregister this sock, we'll busy-loop on it, so we
|
||||
# as well can unregister it now "just in case".
|
||||
self.remove_reader(sock)
|
||||
if DEBUG and __debug__:
|
||||
log.debug("Calling IO callback: %r", cb)
|
||||
if isinstance(cb, tuple):
|
||||
cb[0](*cb[1])
|
||||
else:
|
||||
cb.pend_throw(None)
|
||||
self.call_soon(cb)
|
||||
|
||||
|
||||
class StreamReader:
|
||||
|
||||
def __init__(self, polls, ios=None):
|
||||
if ios is None:
|
||||
ios = polls
|
||||
self.polls = polls
|
||||
self.ios = ios
|
||||
|
||||
def read(self, n=-1):
|
||||
while True:
|
||||
yield IORead(self.polls)
|
||||
res = self.ios.read(n)
|
||||
if res is not None:
|
||||
break
|
||||
# This should not happen for real sockets, but can easily
|
||||
# happen for stream wrappers (ssl, websockets, etc.)
|
||||
#log.warn("Empty read")
|
||||
yield IOReadDone(self.polls)
|
||||
return res
|
||||
|
||||
def readexactly(self, n):
|
||||
buf = b""
|
||||
while n:
|
||||
yield IORead(self.polls)
|
||||
res = self.ios.read(n)
|
||||
assert res is not None
|
||||
if not res:
|
||||
break
|
||||
buf += res
|
||||
n -= len(res)
|
||||
yield IOReadDone(self.polls)
|
||||
return buf
|
||||
|
||||
def readline(self):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("StreamReader.readline()")
|
||||
buf = b""
|
||||
while True:
|
||||
yield IORead(self.polls)
|
||||
res = self.ios.readline()
|
||||
assert res is not None
|
||||
if not res:
|
||||
break
|
||||
buf += res
|
||||
if buf[-1] == 0x0a:
|
||||
break
|
||||
if DEBUG and __debug__:
|
||||
log.debug("StreamReader.readline(): %s", buf)
|
||||
yield IOReadDone(self.polls)
|
||||
return buf
|
||||
|
||||
def aclose(self):
|
||||
yield IOReadDone(self.polls)
|
||||
self.ios.close()
|
||||
|
||||
def __repr__(self):
|
||||
return "<StreamReader %r %r>" % (self.polls, self.ios)
|
||||
|
||||
|
||||
class StreamWriter:
|
||||
|
||||
def __init__(self, s, extra):
|
||||
self.s = s
|
||||
self.extra = extra
|
||||
|
||||
def awrite(self, buf, off=0, sz=-1):
|
||||
# This method is called awrite (async write) to not proliferate
|
||||
# incompatibility with original asyncio. Unlike original asyncio
|
||||
# whose .write() method is both not a coroutine and guaranteed
|
||||
# to return immediately (which means it has to buffer all the
|
||||
# data), this method is a coroutine.
|
||||
if sz == -1:
|
||||
sz = len(buf) - off
|
||||
if DEBUG and __debug__:
|
||||
log.debug("StreamWriter.awrite(): spooling %d bytes", sz)
|
||||
while True:
|
||||
res = self.s.write(buf, off, sz)
|
||||
# If we spooled everything, return immediately
|
||||
if res == sz:
|
||||
if DEBUG and __debug__:
|
||||
log.debug("StreamWriter.awrite(): completed spooling %d bytes", res)
|
||||
yield IOWriteDone(self.s)
|
||||
return
|
||||
if res is None:
|
||||
res = 0
|
||||
if DEBUG and __debug__:
|
||||
log.debug("StreamWriter.awrite(): spooled partial %d bytes", res)
|
||||
assert res < sz
|
||||
off += res
|
||||
sz -= res
|
||||
yield IOWrite(self.s)
|
||||
#assert s2.fileno() == self.s.fileno()
|
||||
if DEBUG and __debug__:
|
||||
log.debug("StreamWriter.awrite(): can write more")
|
||||
|
||||
# Write piecewise content from iterable (usually, a generator)
|
||||
def awriteiter(self, iterable):
|
||||
for buf in iterable:
|
||||
yield from self.awrite(buf)
|
||||
|
||||
def aclose(self):
|
||||
yield IOWriteDone(self.s)
|
||||
self.s.close()
|
||||
|
||||
def get_extra_info(self, name, default=None):
|
||||
return self.extra.get(name, default)
|
||||
|
||||
def __repr__(self):
|
||||
return "<StreamWriter %r>" % self.s
|
||||
|
||||
|
||||
def open_connection(host, port, ssl=False):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("open_connection(%s, %s)", host, port)
|
||||
ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
|
||||
ai = ai[0]
|
||||
s = _socket.socket(ai[0], ai[1], ai[2])
|
||||
s.setblocking(False)
|
||||
try:
|
||||
s.connect(ai[-1])
|
||||
except OSError as e:
|
||||
if e.args[0] != uerrno.EINPROGRESS:
|
||||
raise
|
||||
if DEBUG and __debug__:
|
||||
log.debug("open_connection: After connect")
|
||||
yield IOWrite(s)
|
||||
# if __debug__:
|
||||
# assert s2.fileno() == s.fileno()
|
||||
if DEBUG and __debug__:
|
||||
log.debug("open_connection: After iowait: %s", s)
|
||||
if ssl:
|
||||
print("Warning: uasyncio SSL support is alpha")
|
||||
import ussl
|
||||
s.setblocking(True)
|
||||
s2 = ussl.wrap_socket(s)
|
||||
s.setblocking(False)
|
||||
return StreamReader(s, s2), StreamWriter(s2, {})
|
||||
return StreamReader(s), StreamWriter(s, {})
|
||||
|
||||
|
||||
def start_server(client_coro, host, port, backlog=10):
|
||||
if DEBUG and __debug__:
|
||||
log.debug("start_server(%s, %s)", host, port)
|
||||
ai = _socket.getaddrinfo(host, port, 0, _socket.SOCK_STREAM)
|
||||
ai = ai[0]
|
||||
s = _socket.socket(ai[0], ai[1], ai[2])
|
||||
s.setblocking(False)
|
||||
|
||||
s.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
|
||||
s.bind(ai[-1])
|
||||
s.listen(backlog)
|
||||
while True:
|
||||
if DEBUG and __debug__:
|
||||
log.debug("start_server: Before accept")
|
||||
yield IORead(s)
|
||||
if DEBUG and __debug__:
|
||||
log.debug("start_server: After iowait")
|
||||
s2, client_addr = s.accept()
|
||||
s2.setblocking(False)
|
||||
if DEBUG and __debug__:
|
||||
log.debug("start_server: After accept: %s", s2)
|
||||
extra = {"peername": client_addr}
|
||||
yield client_coro(StreamReader(s2), StreamWriter(s2, extra))
|
||||
|
||||
|
||||
import uasyncio.core
|
||||
uasyncio.core._event_loop_class = PollEventLoop
|
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
* This file is part of the MicroPython project, http://micropython.org/
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2014 Damien P. George
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#include "py/mpconfig.h"
|
||||
#if MICROPY_PY_USELECT
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "py/runtime.h"
|
||||
#include "py/obj.h"
|
||||
#include "py/objlist.h"
|
||||
#include "py/stream.h"
|
||||
#include "py/mperrno.h"
|
||||
#include "py/mphal.h"
|
||||
|
||||
// Flags for poll()
|
||||
#define FLAG_ONESHOT (1)
|
||||
|
||||
/// \module select - Provides select function to wait for events on a stream
|
||||
///
|
||||
/// This module provides the select function.
|
||||
|
||||
typedef struct _poll_obj_t {
|
||||
mp_obj_t obj;
|
||||
mp_uint_t (*ioctl)(mp_obj_t obj, mp_uint_t request, mp_uint_t arg, int *errcode);
|
||||
mp_uint_t flags;
|
||||
mp_uint_t flags_ret;
|
||||
} poll_obj_t;
|
||||
|
||||
STATIC void poll_map_add(mp_map_t *poll_map, const mp_obj_t *obj, mp_uint_t obj_len, mp_uint_t flags, bool or_flags) {
|
||||
for (mp_uint_t i = 0; i < obj_len; i++) {
|
||||
mp_map_elem_t *elem = mp_map_lookup(poll_map, mp_obj_id(obj[i]), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
|
||||
if (elem->value == NULL) {
|
||||
// object not found; get its ioctl and add it to the poll list
|
||||
const mp_stream_p_t *stream_p = mp_get_stream_raise(obj[i], MP_STREAM_OP_IOCTL);
|
||||
poll_obj_t *poll_obj = m_new_obj(poll_obj_t);
|
||||
poll_obj->obj = obj[i];
|
||||
poll_obj->ioctl = stream_p->ioctl;
|
||||
poll_obj->flags = flags;
|
||||
poll_obj->flags_ret = 0;
|
||||
elem->value = poll_obj;
|
||||
} else {
|
||||
// object exists; update its flags
|
||||
if (or_flags) {
|
||||
((poll_obj_t*)elem->value)->flags |= flags;
|
||||
} else {
|
||||
((poll_obj_t*)elem->value)->flags = flags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// poll each object in the map
|
||||
STATIC mp_uint_t poll_map_poll(mp_map_t *poll_map, mp_uint_t *rwx_num) {
|
||||
mp_uint_t n_ready = 0;
|
||||
for (mp_uint_t i = 0; i < poll_map->alloc; ++i) {
|
||||
if (!MP_MAP_SLOT_IS_FILLED(poll_map, i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
poll_obj_t *poll_obj = (poll_obj_t*)poll_map->table[i].value;
|
||||
int errcode;
|
||||
mp_int_t ret = poll_obj->ioctl(poll_obj->obj, MP_STREAM_POLL, poll_obj->flags, &errcode);
|
||||
poll_obj->flags_ret = ret;
|
||||
|
||||
if (ret == -1) {
|
||||
// error doing ioctl
|
||||
mp_raise_OSError(errcode);
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
// object is ready
|
||||
n_ready += 1;
|
||||
if (rwx_num != NULL) {
|
||||
if (ret & MP_STREAM_POLL_RD) {
|
||||
rwx_num[0] += 1;
|
||||
}
|
||||
if (ret & MP_STREAM_POLL_WR) {
|
||||
rwx_num[1] += 1;
|
||||
}
|
||||
if ((ret & ~(MP_STREAM_POLL_RD | MP_STREAM_POLL_WR)) != 0) {
|
||||
rwx_num[2] += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return n_ready;
|
||||
}
|
||||
|
||||
/// \function select(rlist, wlist, xlist[, timeout])
|
||||
STATIC mp_obj_t select_select(uint n_args, const mp_obj_t *args) {
|
||||
// get array data from tuple/list arguments
|
||||
size_t rwx_len[3];
|
||||
mp_obj_t *r_array, *w_array, *x_array;
|
||||
mp_obj_get_array(args[0], &rwx_len[0], &r_array);
|
||||
mp_obj_get_array(args[1], &rwx_len[1], &w_array);
|
||||
mp_obj_get_array(args[2], &rwx_len[2], &x_array);
|
||||
|
||||
// get timeout
|
||||
mp_uint_t timeout = -1;
|
||||
if (n_args == 4) {
|
||||
if (args[3] != mp_const_none) {
|
||||
#if MICROPY_PY_BUILTINS_FLOAT
|
||||
float timeout_f = mp_obj_get_float(args[3]);
|
||||
if (timeout_f >= 0) {
|
||||
timeout = (mp_uint_t)(timeout_f * 1000);
|
||||
}
|
||||
#else
|
||||
timeout = mp_obj_get_int(args[3]) * 1000;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// merge separate lists and get the ioctl function for each object
|
||||
mp_map_t poll_map;
|
||||
mp_map_init(&poll_map, rwx_len[0] + rwx_len[1] + rwx_len[2]);
|
||||
poll_map_add(&poll_map, r_array, rwx_len[0], MP_STREAM_POLL_RD, true);
|
||||
poll_map_add(&poll_map, w_array, rwx_len[1], MP_STREAM_POLL_WR, true);
|
||||
poll_map_add(&poll_map, x_array, rwx_len[2], MP_STREAM_POLL_ERR | MP_STREAM_POLL_HUP, true);
|
||||
|
||||
mp_uint_t start_tick = mp_hal_ticks_ms();
|
||||
rwx_len[0] = rwx_len[1] = rwx_len[2] = 0;
|
||||
for (;;) {
|
||||
// poll the objects
|
||||
mp_uint_t n_ready = poll_map_poll(&poll_map, rwx_len);
|
||||
|
||||
if (n_ready > 0 || (timeout != -1 && mp_hal_ticks_ms() - start_tick >= timeout)) {
|
||||
// one or more objects are ready, or we had a timeout
|
||||
mp_obj_t list_array[3];
|
||||
list_array[0] = mp_obj_new_list(rwx_len[0], NULL);
|
||||
list_array[1] = mp_obj_new_list(rwx_len[1], NULL);
|
||||
list_array[2] = mp_obj_new_list(rwx_len[2], NULL);
|
||||
rwx_len[0] = rwx_len[1] = rwx_len[2] = 0;
|
||||
for (mp_uint_t i = 0; i < poll_map.alloc; ++i) {
|
||||
if (!MP_MAP_SLOT_IS_FILLED(&poll_map, i)) {
|
||||
continue;
|
||||
}
|
||||
poll_obj_t *poll_obj = (poll_obj_t*)poll_map.table[i].value;
|
||||
if (poll_obj->flags_ret & MP_STREAM_POLL_RD) {
|
||||
((mp_obj_list_t*)list_array[0])->items[rwx_len[0]++] = poll_obj->obj;
|
||||
}
|
||||
if (poll_obj->flags_ret & MP_STREAM_POLL_WR) {
|
||||
((mp_obj_list_t*)list_array[1])->items[rwx_len[1]++] = poll_obj->obj;
|
||||
}
|
||||
if ((poll_obj->flags_ret & ~(MP_STREAM_POLL_RD | MP_STREAM_POLL_WR)) != 0) {
|
||||
((mp_obj_list_t*)list_array[2])->items[rwx_len[2]++] = poll_obj->obj;
|
||||
}
|
||||
}
|
||||
mp_map_deinit(&poll_map);
|
||||
return mp_obj_new_tuple(3, list_array);
|
||||
}
|
||||
MICROPY_EVENT_POLL_HOOK
|
||||
}
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_select_select_obj, 3, 4, select_select);
|
||||
|
||||
/// \class Poll - poll class
|
||||
|
||||
typedef struct _mp_obj_poll_t {
|
||||
mp_obj_base_t base;
|
||||
mp_map_t poll_map;
|
||||
short iter_cnt;
|
||||
short iter_idx;
|
||||
int flags;
|
||||
// callee-owned tuple
|
||||
mp_obj_t ret_tuple;
|
||||
} mp_obj_poll_t;
|
||||
|
||||
/// \method register(obj[, eventmask])
|
||||
STATIC mp_obj_t poll_register(uint n_args, const mp_obj_t *args) {
|
||||
mp_obj_poll_t *self = args[0];
|
||||
mp_uint_t flags;
|
||||
if (n_args == 3) {
|
||||
flags = mp_obj_get_int(args[2]);
|
||||
} else {
|
||||
flags = MP_STREAM_POLL_RD | MP_STREAM_POLL_WR;
|
||||
}
|
||||
poll_map_add(&self->poll_map, &args[1], 1, flags, false);
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_register_obj, 2, 3, poll_register);
|
||||
|
||||
/// \method unregister(obj)
|
||||
STATIC mp_obj_t poll_unregister(mp_obj_t self_in, mp_obj_t obj_in) {
|
||||
mp_obj_poll_t *self = self_in;
|
||||
mp_map_lookup(&self->poll_map, mp_obj_id(obj_in), MP_MAP_LOOKUP_REMOVE_IF_FOUND);
|
||||
// TODO raise KeyError if obj didn't exist in map
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_2(poll_unregister_obj, poll_unregister);
|
||||
|
||||
/// \method modify(obj, eventmask)
|
||||
STATIC mp_obj_t poll_modify(mp_obj_t self_in, mp_obj_t obj_in, mp_obj_t eventmask_in) {
|
||||
mp_obj_poll_t *self = self_in;
|
||||
mp_map_elem_t *elem = mp_map_lookup(&self->poll_map, mp_obj_id(obj_in), MP_MAP_LOOKUP);
|
||||
if (elem == NULL) {
|
||||
mp_raise_OSError(MP_ENOENT);
|
||||
}
|
||||
((poll_obj_t*)elem->value)->flags = mp_obj_get_int(eventmask_in);
|
||||
return mp_const_none;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_3(poll_modify_obj, poll_modify);
|
||||
|
||||
STATIC mp_uint_t poll_poll_internal(uint n_args, const mp_obj_t *args) {
|
||||
mp_obj_poll_t *self = args[0];
|
||||
|
||||
// work out timeout (its given already in ms)
|
||||
mp_uint_t timeout = -1;
|
||||
int flags = 0;
|
||||
if (n_args >= 2) {
|
||||
if (args[1] != mp_const_none) {
|
||||
mp_int_t timeout_i = mp_obj_get_int(args[1]);
|
||||
if (timeout_i >= 0) {
|
||||
timeout = timeout_i;
|
||||
}
|
||||
}
|
||||
if (n_args >= 3) {
|
||||
flags = mp_obj_get_int(args[2]);
|
||||
}
|
||||
}
|
||||
|
||||
self->flags = flags;
|
||||
|
||||
mp_uint_t start_tick = mp_hal_ticks_ms();
|
||||
mp_uint_t n_ready;
|
||||
for (;;) {
|
||||
// poll the objects
|
||||
n_ready = poll_map_poll(&self->poll_map, NULL);
|
||||
if (n_ready > 0 || (timeout != -1 && mp_hal_ticks_ms() - start_tick >= timeout)) {
|
||||
break;
|
||||
}
|
||||
MICROPY_EVENT_POLL_HOOK
|
||||
}
|
||||
|
||||
return n_ready;
|
||||
}
|
||||
|
||||
STATIC mp_obj_t poll_poll(uint n_args, const mp_obj_t *args) {
|
||||
mp_obj_poll_t *self = args[0];
|
||||
mp_uint_t n_ready = poll_poll_internal(n_args, args);
|
||||
|
||||
// one or more objects are ready, or we had a timeout
|
||||
mp_obj_list_t *ret_list = mp_obj_new_list(n_ready, NULL);
|
||||
n_ready = 0;
|
||||
for (mp_uint_t i = 0; i < self->poll_map.alloc; ++i) {
|
||||
if (!MP_MAP_SLOT_IS_FILLED(&self->poll_map, i)) {
|
||||
continue;
|
||||
}
|
||||
poll_obj_t *poll_obj = (poll_obj_t*)self->poll_map.table[i].value;
|
||||
if (poll_obj->flags_ret != 0) {
|
||||
mp_obj_t tuple[2] = {poll_obj->obj, MP_OBJ_NEW_SMALL_INT(poll_obj->flags_ret)};
|
||||
ret_list->items[n_ready++] = mp_obj_new_tuple(2, tuple);
|
||||
if (self->flags & FLAG_ONESHOT) {
|
||||
// Don't poll next time, until new event flags will be set explicitly
|
||||
poll_obj->flags &= ~(poll_obj->flags_ret & (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR));
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret_list;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_poll_obj, 1, 3, poll_poll);
|
||||
|
||||
STATIC mp_obj_t poll_ipoll(size_t n_args, const mp_obj_t *args) {
|
||||
mp_obj_poll_t *self = MP_OBJ_TO_PTR(args[0]);
|
||||
|
||||
if (self->ret_tuple == MP_OBJ_NULL) {
|
||||
self->ret_tuple = mp_obj_new_tuple(2, NULL);
|
||||
}
|
||||
|
||||
int n_ready = poll_poll_internal(n_args, args);
|
||||
self->iter_cnt = n_ready;
|
||||
self->iter_idx = 0;
|
||||
|
||||
return args[0];
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(poll_ipoll_obj, 1, 3, poll_ipoll);
|
||||
|
||||
STATIC mp_obj_t poll_iternext(mp_obj_t self_in) {
|
||||
mp_obj_poll_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
if (self->iter_cnt == 0) {
|
||||
return MP_OBJ_STOP_ITERATION;
|
||||
}
|
||||
|
||||
self->iter_cnt--;
|
||||
|
||||
for (mp_uint_t i = self->iter_idx; i < self->poll_map.alloc; ++i) {
|
||||
self->iter_idx++;
|
||||
if (!MP_MAP_SLOT_IS_FILLED(&self->poll_map, i)) {
|
||||
continue;
|
||||
}
|
||||
poll_obj_t *poll_obj = (poll_obj_t*)self->poll_map.table[i].value;
|
||||
if (poll_obj->flags_ret != 0) {
|
||||
mp_obj_tuple_t *t = MP_OBJ_TO_PTR(self->ret_tuple);
|
||||
t->items[0] = poll_obj->obj;
|
||||
t->items[1] = MP_OBJ_NEW_SMALL_INT(poll_obj->flags_ret);
|
||||
if (self->flags & FLAG_ONESHOT) {
|
||||
// Don't poll next time, until new event flags will be set explicitly
|
||||
poll_obj->flags &= ~(poll_obj->flags_ret & (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR));
|
||||
}
|
||||
return MP_OBJ_FROM_PTR(t);
|
||||
}
|
||||
}
|
||||
|
||||
assert(!"inconsistent number of poll active entries");
|
||||
self->iter_cnt = 0;
|
||||
return MP_OBJ_STOP_ITERATION;
|
||||
}
|
||||
|
||||
STATIC const mp_rom_map_elem_t poll_locals_dict_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR_register), MP_ROM_PTR(&poll_register_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_unregister), MP_ROM_PTR(&poll_unregister_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_modify), MP_ROM_PTR(&poll_modify_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&poll_poll_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_ipoll), MP_ROM_PTR(&poll_ipoll_obj) },
|
||||
};
|
||||
STATIC MP_DEFINE_CONST_DICT(poll_locals_dict, poll_locals_dict_table);
|
||||
|
||||
STATIC const mp_obj_type_t mp_type_poll = {
|
||||
{ &mp_type_type },
|
||||
.name = MP_QSTR_poll,
|
||||
.getiter = mp_identity_getiter,
|
||||
.iternext = poll_iternext,
|
||||
.locals_dict = (void*)&poll_locals_dict,
|
||||
};
|
||||
|
||||
/// \function poll()
|
||||
STATIC mp_obj_t select_poll(void) {
|
||||
mp_obj_poll_t *poll = m_new_obj(mp_obj_poll_t);
|
||||
poll->base.type = &mp_type_poll;
|
||||
mp_map_init(&poll->poll_map, 0);
|
||||
poll->iter_cnt = 0;
|
||||
poll->ret_tuple = MP_OBJ_NULL;
|
||||
return poll;
|
||||
}
|
||||
MP_DEFINE_CONST_FUN_OBJ_0(mp_select_poll_obj, select_poll);
|
||||
|
||||
STATIC const mp_rom_map_elem_t mp_module_select_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_uselect) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_select), MP_ROM_PTR(&mp_select_select_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_poll), MP_ROM_PTR(&mp_select_poll_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_POLLIN), MP_ROM_INT(MP_STREAM_POLL_RD) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_POLLOUT), MP_ROM_INT(MP_STREAM_POLL_WR) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_POLLERR), MP_ROM_INT(MP_STREAM_POLL_ERR) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_POLLHUP), MP_ROM_INT(MP_STREAM_POLL_HUP) },
|
||||
};
|
||||
|
||||
STATIC MP_DEFINE_CONST_DICT(mp_module_select_globals, mp_module_select_globals_table);
|
||||
|
||||
const mp_obj_module_t mp_module_uselect = {
|
||||
.base = { &mp_type_module },
|
||||
.globals = (mp_obj_dict_t*)&mp_module_select_globals,
|
||||
};
|
||||
|
||||
#endif // MICROPY_PY_USELECT
|
|
@ -0,0 +1,191 @@
|
|||
# aswitch.py Switch and pushbutton classes for asyncio
|
||||
# Delay_ms A retriggerable delay class. Can schedule a coro on timeout.
|
||||
# Switch Simple debounced switch class for normally open grounded switch.
|
||||
# Pushbutton extend the above to support logical state, long press and
|
||||
# double-click events
|
||||
# Tested on Pyboard but should run on other microcontroller platforms
|
||||
# running MicroPython and uasyncio.
|
||||
|
||||
# The MIT License (MIT)
|
||||
#
|
||||
# Copyright (c) 2017 Peter Hinch
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to deal
|
||||
# in the Software without restriction, including without limitation the rights
|
||||
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
# copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
try:
|
||||
import asyncio_priority as asyncio
|
||||
except ImportError:
|
||||
import uasyncio as asyncio
|
||||
import utime as time
|
||||
from asyn import launch
|
||||
# launch: run a callback or initiate a coroutine depending on which is passed.
|
||||
|
||||
|
||||
class Delay_ms(object):
|
||||
def __init__(self, func=None, args=(), can_alloc=True, duration=1000):
|
||||
self.func = func
|
||||
self.args = args
|
||||
self.can_alloc = can_alloc
|
||||
self.duration = duration # Default duration
|
||||
self.tstop = None # Not running
|
||||
self.loop = asyncio.get_event_loop()
|
||||
if not can_alloc:
|
||||
self.loop.create_task(self._run())
|
||||
|
||||
async def _run(self):
|
||||
while True:
|
||||
if self.tstop is None: # Not running
|
||||
await asyncio.sleep_ms(0)
|
||||
else:
|
||||
await self.killer()
|
||||
|
||||
def stop(self):
|
||||
self.tstop = None
|
||||
|
||||
def trigger(self, duration=0): # Update end time
|
||||
if duration <= 0:
|
||||
duration = self.duration
|
||||
if self.can_alloc and self.tstop is None: # No killer task is running
|
||||
self.tstop = time.ticks_add(time.ticks_ms(), duration)
|
||||
# Start a task which stops the delay after its period has elapsed
|
||||
self.loop.create_task(self.killer())
|
||||
self.tstop = time.ticks_add(time.ticks_ms(), duration)
|
||||
|
||||
def running(self):
|
||||
return self.tstop is not None
|
||||
|
||||
async def killer(self):
|
||||
twait = time.ticks_diff(self.tstop, time.ticks_ms())
|
||||
while twait > 0: # Must loop here: might be retriggered
|
||||
await asyncio.sleep_ms(twait)
|
||||
if self.tstop is None:
|
||||
break # Return if stop() called during wait
|
||||
twait = time.ticks_diff(self.tstop, time.ticks_ms())
|
||||
if self.tstop is not None and self.func is not None:
|
||||
launch(self.func, self.args) # Timed out: execute callback
|
||||
self.tstop = None # Not running
|
||||
|
||||
class Switch(object):
|
||||
debounce_ms = 50
|
||||
def __init__(self, pin):
|
||||
self.pin = pin # Should be initialised for input with pullup
|
||||
self._open_func = False
|
||||
self._close_func = False
|
||||
self.switchstate = self.pin.value() # Get initial state
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self.switchcheck()) # Thread runs forever
|
||||
|
||||
def open_func(self, func, args=()):
|
||||
self._open_func = func
|
||||
self._open_args = args
|
||||
|
||||
def close_func(self, func, args=()):
|
||||
self._close_func = func
|
||||
self._close_args = args
|
||||
|
||||
# Return current state of switch (0 = pressed)
|
||||
def __call__(self):
|
||||
return self.switchstate
|
||||
|
||||
async def switchcheck(self):
|
||||
loop = asyncio.get_event_loop()
|
||||
while True:
|
||||
state = self.pin.value()
|
||||
if state != self.switchstate:
|
||||
# State has changed: act on it now.
|
||||
self.switchstate = state
|
||||
if state == 0 and self._close_func:
|
||||
launch(self._close_func, self._close_args)
|
||||
elif state == 1 and self._open_func:
|
||||
launch(self._open_func, self._open_args)
|
||||
# Ignore further state changes until switch has settled
|
||||
await asyncio.sleep_ms(Switch.debounce_ms)
|
||||
|
||||
class Pushbutton(object):
|
||||
debounce_ms = 50
|
||||
long_press_ms = 1000
|
||||
double_click_ms = 400
|
||||
def __init__(self, pin):
|
||||
self.pin = pin # Initialise for input
|
||||
self._true_func = False
|
||||
self._false_func = False
|
||||
self._double_func = False
|
||||
self._long_func = False
|
||||
self.sense = pin.value() # Convert from electrical to logical value
|
||||
self.buttonstate = self.rawstate() # Initial state
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self.buttoncheck()) # Thread runs forever
|
||||
|
||||
def press_func(self, func, args=()):
|
||||
self._true_func = func
|
||||
self._true_args = args
|
||||
|
||||
def release_func(self, func, args=()):
|
||||
self._false_func = func
|
||||
self._false_args = args
|
||||
|
||||
def double_func(self, func, args=()):
|
||||
self._double_func = func
|
||||
self._double_args = args
|
||||
|
||||
def long_func(self, func, args=()):
|
||||
self._long_func = func
|
||||
self._long_args = args
|
||||
|
||||
# Current non-debounced logical button state: True == pressed
|
||||
def rawstate(self):
|
||||
return bool(self.pin.value() ^ self.sense)
|
||||
|
||||
# Current debounced state of button (True == pressed)
|
||||
def __call__(self):
|
||||
return self.buttonstate
|
||||
|
||||
async def buttoncheck(self):
|
||||
loop = asyncio.get_event_loop()
|
||||
if self._long_func:
|
||||
longdelay = Delay_ms(self._long_func, self._long_args)
|
||||
if self._double_func:
|
||||
doubledelay = Delay_ms()
|
||||
while True:
|
||||
state = self.rawstate()
|
||||
# State has changed: act on it now.
|
||||
if state != self.buttonstate:
|
||||
self.buttonstate = state
|
||||
if state:
|
||||
# Button is pressed
|
||||
if self._long_func and not longdelay.running():
|
||||
# Start long press delay
|
||||
longdelay.trigger(Pushbutton.long_press_ms)
|
||||
if self._double_func:
|
||||
if doubledelay.running():
|
||||
launch(self._double_func, self._double_args)
|
||||
else:
|
||||
# First click: start doubleclick timer
|
||||
doubledelay.trigger(Pushbutton.double_click_ms)
|
||||
if self._true_func:
|
||||
launch(self._true_func, self._true_args)
|
||||
else:
|
||||
# Button release
|
||||
if self._long_func and longdelay.running():
|
||||
# Avoid interpreting a second click as a long push
|
||||
longdelay.stop()
|
||||
if self._false_func:
|
||||
launch(self._false_func, self._false_args)
|
||||
# Ignore state changes until switch has settled
|
||||
await asyncio.sleep_ms(Pushbutton.debounce_ms)
|
|
@ -0,0 +1,25 @@
|
|||
# Test of uasyncio stream I/O using UART
|
||||
# Author: Peter Hinch
|
||||
# Copyright Peter Hinch 2017 Released under the MIT license
|
||||
# Link X1 and X2 to test.
|
||||
|
||||
import uasyncio as asyncio
|
||||
from pyb import UART
|
||||
uart = UART(4, 9600)
|
||||
|
||||
async def sender():
|
||||
swriter = asyncio.StreamWriter(uart, {})
|
||||
while True:
|
||||
await swriter.awrite('Hello uart\n')
|
||||
await asyncio.sleep(2)
|
||||
|
||||
async def receiver():
|
||||
sreader = asyncio.StreamReader(uart)
|
||||
while True:
|
||||
res = await sreader.readline()
|
||||
print('Recieved', res)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(sender())
|
||||
loop.create_task(receiver())
|
||||
loop.run_forever()
|
|
@ -0,0 +1,106 @@
|
|||
# auart_hd.py
|
||||
# Author: Peter Hinch
|
||||
# Copyright Peter Hinch 2018 Released under the MIT license
|
||||
|
||||
# Demo of running a half-duplex protocol to a device. The device never sends
|
||||
# unsolicited messages. An example is a communications device which responds
|
||||
# to AT commands.
|
||||
# The master sends a message to the device, which may respond with one or more
|
||||
# lines of data. The master assumes that the device has sent all its data when
|
||||
# a timeout has elapsed.
|
||||
|
||||
# In this test a physical device is emulated by the DEVICE class
|
||||
# To test link X1-X4 and X2-X3
|
||||
|
||||
from pyb import UART
|
||||
import uasyncio as asyncio
|
||||
import aswitch
|
||||
|
||||
# Dummy device waits for any incoming line and responds with 4 lines at 1 second
|
||||
# intervals.
|
||||
class DEVICE():
|
||||
def __init__(self, uart_no = 4):
|
||||
self.uart = UART(uart_no, 9600)
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.swriter = asyncio.StreamWriter(self.uart, {})
|
||||
self.sreader = asyncio.StreamReader(self.uart)
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self._run())
|
||||
|
||||
async def _run(self):
|
||||
responses = ['Line 1', 'Line 2', 'Line 3', 'Goodbye']
|
||||
while True:
|
||||
res = await self.sreader.readline()
|
||||
for response in responses:
|
||||
await self.swriter.awrite("{}\r\n".format(response))
|
||||
# Demo the fact that the master tolerates slow response.
|
||||
await asyncio.sleep_ms(300)
|
||||
|
||||
# The master's send_command() method sends a command and waits for a number of
|
||||
# lines from the device. The end of the process is signified by a timeout, when
|
||||
# a list of lines is returned. This allows line-by-line processing.
|
||||
# A special test mode demonstrates the behaviour with a non-responding device. If
|
||||
# None is passed, no commend is sent. The master waits for a response which never
|
||||
# arrives and returns an empty list.
|
||||
class MASTER():
|
||||
def __init__(self, uart_no = 2, timeout=4000):
|
||||
self.uart = UART(uart_no, 9600)
|
||||
self.timeout = timeout
|
||||
self.loop = asyncio.get_event_loop()
|
||||
self.swriter = asyncio.StreamWriter(self.uart, {})
|
||||
self.sreader = asyncio.StreamReader(self.uart)
|
||||
self.delay = aswitch.Delay_ms()
|
||||
self.response = []
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(self._recv())
|
||||
|
||||
async def _recv(self):
|
||||
while True:
|
||||
res = await self.sreader.readline()
|
||||
self.response.append(res) # Append to list of lines
|
||||
self.delay.trigger(self.timeout) # Got something, retrigger timer
|
||||
|
||||
async def send_command(self, command):
|
||||
self.response = [] # Discard any pending messages
|
||||
if command is None:
|
||||
print('Timeout test.')
|
||||
else:
|
||||
await self.swriter.awrite("{}\r\n".format(command))
|
||||
print('Command sent:', command)
|
||||
self.delay.trigger(self.timeout) # Re-initialise timer
|
||||
while self.delay.running():
|
||||
await asyncio.sleep(1) # Wait for 4s after last msg received
|
||||
return self.response
|
||||
|
||||
async def test():
|
||||
print('This test takes 10s to complete.')
|
||||
for cmd in ['Run', None]:
|
||||
print()
|
||||
res = await master.send_command(cmd)
|
||||
# can use b''.join(res) if a single string is required.
|
||||
if res:
|
||||
print('Result is:')
|
||||
for line in res:
|
||||
print(line.decode('UTF8'), end='')
|
||||
else:
|
||||
print('Timed out waiting for result.')
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
master = MASTER()
|
||||
device = DEVICE()
|
||||
loop.run_until_complete(test())
|
||||
|
||||
# Expected output
|
||||
# >>> import auart_hd
|
||||
# This test takes 10s to complete.
|
||||
#
|
||||
# Command sent: Run
|
||||
# Result is:
|
||||
# Line 1
|
||||
# Line 2
|
||||
# Line 3
|
||||
# Goodbye
|
||||
#
|
||||
# Timeout test.
|
||||
# Timed out waiting for result.
|
||||
# >>>
|
|
@ -0,0 +1,54 @@
|
|||
# iomiss.py Test for missed reads. The bug was fixed by disabling interrupts in
|
||||
# ioctl().
|
||||
import io, pyb
|
||||
import uasyncio as asyncio
|
||||
import micropython
|
||||
micropython.alloc_emergency_exception_buf(100)
|
||||
|
||||
MP_STREAM_POLL = const(3)
|
||||
MP_STREAM_POLL_RD = const(1)
|
||||
|
||||
class MyIO(io.IOBase):
|
||||
def __init__(self):
|
||||
self.ready = False
|
||||
self.count = 0
|
||||
tim = pyb.Timer(4)
|
||||
tim.init(freq=1)
|
||||
tim.callback(self.setready)
|
||||
|
||||
def ioctl(self, req, arg):
|
||||
if req == MP_STREAM_POLL and (arg & MP_STREAM_POLL_RD):
|
||||
state = pyb.disable_irq()
|
||||
r = self.ready
|
||||
self.ready = False
|
||||
pyb.enable_irq(state)
|
||||
return r
|
||||
return 0
|
||||
|
||||
def readline(self):
|
||||
return '{}\n'.format(self.count)
|
||||
|
||||
def setready(self, t):
|
||||
self.count += 1
|
||||
self.ready = True
|
||||
|
||||
myio = MyIO()
|
||||
|
||||
async def receiver():
|
||||
last = None
|
||||
nmissed = 0
|
||||
sreader = asyncio.StreamReader(myio)
|
||||
while True:
|
||||
res = await sreader.readline()
|
||||
print('Recieved {} Missed {}'.format(res, nmissed))
|
||||
ires = int(res)
|
||||
if last is not None:
|
||||
if last != ires -1:
|
||||
print('Missed {}'.format(ires - 1))
|
||||
nmissed += 1
|
||||
last = ires
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(receiver())
|
||||
loop.run_forever()
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# iotest1.py Test PR #3836. User class write() performs unbuffered writing.
|
||||
|
||||
import io, pyb
|
||||
import uasyncio as asyncio
|
||||
import micropython
|
||||
micropython.alloc_emergency_exception_buf(100)
|
||||
|
||||
MP_STREAM_POLL_RD = const(1)
|
||||
MP_STREAM_POLL_WR = const(4)
|
||||
MP_STREAM_POLL = const(3)
|
||||
MP_STREAM_ERROR = const(-1)
|
||||
|
||||
def printbuf(this_io):
|
||||
for ch in this_io.wbuf[:this_io.wprint_len]:
|
||||
print(chr(ch), end='')
|
||||
|
||||
class MyIO(io.IOBase):
|
||||
def __init__(self):
|
||||
self.ready_rd = False
|
||||
self.ready_wr = False
|
||||
self.wbuf = bytearray(100) # Write buffer
|
||||
self.wprint_len = 0
|
||||
self.widx = 0
|
||||
self.wch = b''
|
||||
self.rbuf = b'ready\n' # Read buffer
|
||||
pyb.Timer(4, freq = 1, callback = self.do_input)
|
||||
pyb.Timer(5, freq = 10, callback = self.do_output)
|
||||
|
||||
# Read callback: emulate asynchronous input from hardware.
|
||||
# Typically would put bytes into a ring buffer and set .ready_rd.
|
||||
def do_input(self, t):
|
||||
self.ready_rd = True # Data is ready to read
|
||||
|
||||
# Write timer callback. Emulate hardware: if there's data in the buffer
|
||||
# write some or all of it
|
||||
def do_output(self, t):
|
||||
if self.wch:
|
||||
self.wbuf[self.widx] = self.wch
|
||||
self.widx += 1
|
||||
if self.wch == ord('\n'):
|
||||
self.wprint_len = self.widx # Save for schedule
|
||||
micropython.schedule(printbuf, self)
|
||||
self.widx = 0
|
||||
self.wch = b''
|
||||
|
||||
|
||||
def ioctl(self, req, arg): # see ports/stm32/uart.c
|
||||
ret = MP_STREAM_ERROR
|
||||
if req == MP_STREAM_POLL:
|
||||
ret = 0
|
||||
if arg & MP_STREAM_POLL_RD:
|
||||
if self.ready_rd:
|
||||
ret |= MP_STREAM_POLL_RD
|
||||
if arg & MP_STREAM_POLL_WR:
|
||||
if not self.wch:
|
||||
ret |= MP_STREAM_POLL_WR # Ready if no char pending
|
||||
return ret
|
||||
|
||||
def readline(self):
|
||||
self.ready_rd = False
|
||||
return self.rbuf
|
||||
|
||||
def write(self, buf, off, sz):
|
||||
self.wch = buf[off] # A real driver would trigger hardware to write a char
|
||||
return 1 # No. of bytes written. uasyncio waits on ioctl write ready
|
||||
|
||||
myio = MyIO()
|
||||
|
||||
async def receiver():
|
||||
sreader = asyncio.StreamReader(myio)
|
||||
while True:
|
||||
res = await sreader.readline()
|
||||
print('Received', res)
|
||||
|
||||
async def sender():
|
||||
swriter = asyncio.StreamWriter(myio, {})
|
||||
await asyncio.sleep(5)
|
||||
count = 0
|
||||
while True:
|
||||
count += 1
|
||||
tosend = 'Wrote Hello MyIO {}\n'.format(count)
|
||||
await swriter.awrite(tosend.encode('UTF8'))
|
||||
# Once this has occurred reading stops. ioctl keeps being called with arg == 0
|
||||
# which normally occurs once only after a read
|
||||
# IOWriteDone is never yielded: is this right?
|
||||
await asyncio.sleep(2)
|
||||
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(receiver())
|
||||
loop.create_task(sender())
|
||||
loop.run_forever()
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
# iotest2.py Test PR #3836. User class write() performs buffered writing.
|
||||
# This works as expected.
|
||||
|
||||
import io, pyb
|
||||
import uasyncio as asyncio
|
||||
import micropython
|
||||
micropython.alloc_emergency_exception_buf(100)
|
||||
|
||||
MP_STREAM_POLL_RD = const(1)
|
||||
MP_STREAM_POLL_WR = const(4)
|
||||
MP_STREAM_POLL = const(3)
|
||||
MP_STREAM_ERROR = const(-1)
|
||||
|
||||
def printbuf(this_io):
|
||||
for ch in this_io.wbuf[:this_io.wprint_len]:
|
||||
print(chr(ch), end='')
|
||||
this_io.wbuf = b''
|
||||
|
||||
class MyIO(io.IOBase):
|
||||
def __init__(self):
|
||||
self.ready_rd = False
|
||||
self.ready_wr = False
|
||||
self.wbuf = b''
|
||||
self.wprint_len = 0
|
||||
self.ridx = 0
|
||||
self.rbuf = b'ready\n' # Read buffer
|
||||
pyb.Timer(4, freq = 1, callback = self.do_input)
|
||||
pyb.Timer(5, freq = 10, callback = self.do_output)
|
||||
|
||||
# Read callback: emulate asynchronous input from hardware.
|
||||
# Typically would put bytes into a ring buffer and set .ready_rd.
|
||||
def do_input(self, t):
|
||||
self.ready_rd = True # Data is ready to read
|
||||
|
||||
# Write timer callback. Emulate hardware: if there's data in the buffer
|
||||
# write some or all of it
|
||||
def do_output(self, t):
|
||||
if self.wbuf:
|
||||
self.wprint_len = self.wbuf.find(b'\n') + 1
|
||||
micropython.schedule(printbuf, self)
|
||||
|
||||
|
||||
def ioctl(self, req, arg): # see ports/stm32/uart.c
|
||||
# print('ioctl', req, arg)
|
||||
ret = MP_STREAM_ERROR
|
||||
if req == MP_STREAM_POLL:
|
||||
ret = 0
|
||||
if arg & MP_STREAM_POLL_RD:
|
||||
if self.ready_rd:
|
||||
ret |= MP_STREAM_POLL_RD
|
||||
if arg & MP_STREAM_POLL_WR:
|
||||
if not self.wch:
|
||||
ret |= MP_STREAM_POLL_WR # Ready if no char pending
|
||||
return ret
|
||||
|
||||
# Test of device that produces one character at a time
|
||||
def readline(self):
|
||||
self.ready_rd = False
|
||||
ch = self.rbuf[self.ridx]
|
||||
if ch == ord('\n'):
|
||||
self.ridx = 0
|
||||
else:
|
||||
self.ridx += 1
|
||||
return chr(ch)
|
||||
|
||||
def write(self, buf, off, sz):
|
||||
self.wbuf = buf[:]
|
||||
return sz # No. of bytes written. uasyncio waits on ioctl write ready
|
||||
|
||||
myio = MyIO()
|
||||
|
||||
async def receiver():
|
||||
sreader = asyncio.StreamReader(myio)
|
||||
while True:
|
||||
res = await sreader.readline()
|
||||
print('Received', res)
|
||||
|
||||
async def sender():
|
||||
swriter = asyncio.StreamWriter(myio, {})
|
||||
await asyncio.sleep(5)
|
||||
count = 0
|
||||
while True:
|
||||
count += 1
|
||||
tosend = 'Wrote Hello MyIO {}\n'.format(count)
|
||||
await swriter.awrite(tosend.encode('UTF8'))
|
||||
await asyncio.sleep(2)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(receiver())
|
||||
loop.create_task(sender())
|
||||
loop.run_forever()
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
# iotest3.py Test PR #3836. User class write() performs unbuffered writing.
|
||||
|
||||
# This test was to demonstrate the workround to the original issue by having
|
||||
# separate read and write classes.
|
||||
# With modified moduselect.c and uasyncio.__init__.py the test is probably
|
||||
# irrelevant.
|
||||
|
||||
import io, pyb
|
||||
import uasyncio as asyncio
|
||||
import micropython
|
||||
micropython.alloc_emergency_exception_buf(100)
|
||||
|
||||
MP_STREAM_POLL_RD = const(1)
|
||||
MP_STREAM_POLL_WR = const(4)
|
||||
MP_STREAM_POLL = const(3)
|
||||
MP_STREAM_ERROR = const(-1)
|
||||
|
||||
class MyIOR(io.IOBase):
|
||||
def __init__(self):
|
||||
self.ready_rd = False
|
||||
self.rbuf = b'ready\n' # Read buffer
|
||||
pyb.Timer(4, freq = 1, callback = self.do_input)
|
||||
|
||||
# Read callback: emulate asynchronous input from hardware.
|
||||
# Typically would put bytes into a ring buffer and set .ready_rd.
|
||||
def do_input(self, t):
|
||||
self.ready_rd = True # Data is ready to read
|
||||
|
||||
def ioctl(self, req, arg): # see ports/stm32/uart.c
|
||||
ret = MP_STREAM_ERROR
|
||||
if req == MP_STREAM_POLL:
|
||||
ret = 0
|
||||
if not arg:
|
||||
print('ioctl arg 0')
|
||||
if arg & MP_STREAM_POLL_RD:
|
||||
if self.ready_rd:
|
||||
ret |= MP_STREAM_POLL_RD
|
||||
return ret
|
||||
|
||||
def readline(self):
|
||||
self.ready_rd = False
|
||||
return self.rbuf
|
||||
|
||||
# MyIOW emulates a write-only device which can only handle one character at a
|
||||
# time. The write() method is called by uasyncio. A real driver would cause the
|
||||
# hardware to write a character. By setting .wch it causes the ioctl to report
|
||||
# a not ready status.
|
||||
# Some time later an asynchronous event occurs, indicating that the hardware
|
||||
# has written a character and is ready for another. In this demo this is done
|
||||
# by the timer callback do_output(), which clears .wch so that ioctl returns
|
||||
# a ready status. For the demo it stores the characters in .wbuf for printing.
|
||||
|
||||
def printbuf(this_io):
|
||||
for x in range(this_io.wprint_len):
|
||||
print(chr(this_io.wbuf[x]), end = '')
|
||||
|
||||
class MyIOW(io.IOBase):
|
||||
def __init__(self):
|
||||
self.wbuf = bytearray(20) # Buffer for printing
|
||||
self.wprint_len = 0
|
||||
self.widx = 0
|
||||
self.wch = b''
|
||||
wtim = pyb.Timer(5, freq = 10, callback = self.do_output)
|
||||
|
||||
# Write timer callback. Emulate hardware: if there's data in the buffer
|
||||
# write some or all of it
|
||||
def do_output(self, t):
|
||||
if self.wch:
|
||||
self.wbuf[self.widx] = self.wch
|
||||
self.widx += 1
|
||||
if self.wch == ord('\n'):
|
||||
self.wprint_len = self.widx # Save for schedule
|
||||
micropython.schedule(printbuf, self)
|
||||
self.widx = 0
|
||||
self.wch = b''
|
||||
|
||||
def ioctl(self, req, arg): # see ports/stm32/uart.c
|
||||
ret = MP_STREAM_ERROR
|
||||
if req == MP_STREAM_POLL:
|
||||
ret = 0
|
||||
if arg & MP_STREAM_POLL_WR:
|
||||
if not self.wch:
|
||||
ret |= MP_STREAM_POLL_WR # Ready if no char pending
|
||||
return ret
|
||||
|
||||
def write(self, buf, off, sz):
|
||||
self.wch = buf[off] # A real driver would trigger hardware to write a char
|
||||
return 1 # No. of bytes written. uasyncio waits on ioctl write ready
|
||||
|
||||
myior = MyIOR()
|
||||
myiow = MyIOW()
|
||||
|
||||
async def receiver():
|
||||
sreader = asyncio.StreamReader(myior)
|
||||
while True:
|
||||
res = await sreader.readline()
|
||||
print('Received', res)
|
||||
|
||||
async def sender():
|
||||
swriter = asyncio.StreamWriter(myiow, {})
|
||||
count = 0
|
||||
while True:
|
||||
count += 1
|
||||
tosend = 'Wrote Hello MyIO {}\n'.format(count)
|
||||
await swriter.awrite(tosend.encode('UTF8'))
|
||||
await asyncio.sleep(2)
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(receiver())
|
||||
loop.create_task(sender())
|
||||
loop.run_forever()
|
|
@ -0,0 +1,108 @@
|
|||
# iotest4.py Test PR #3836.
|
||||
# User class write() performs unbuffered writing.
|
||||
# For simplicity this uses buffered read: unbuffered is tested by iotest2.py.
|
||||
|
||||
# This test was to demonstrate the original issue.
|
||||
# With modified moduselect.c and uasyncio.__init__.py the test now passes.
|
||||
|
||||
# iotest4.test() uses separate read and write objects.
|
||||
# iotest4.test(False) uses a common object (failed without the mod).
|
||||
|
||||
|
||||
import io, pyb
|
||||
import uasyncio as asyncio
|
||||
import micropython
|
||||
micropython.alloc_emergency_exception_buf(100)
|
||||
|
||||
MP_STREAM_POLL_RD = const(1)
|
||||
MP_STREAM_POLL_WR = const(4)
|
||||
MP_STREAM_POLL = const(3)
|
||||
MP_STREAM_ERROR = const(-1)
|
||||
|
||||
def printbuf(this_io):
|
||||
for ch in this_io.wbuf[:this_io.wprint_len]:
|
||||
print(chr(ch), end='')
|
||||
|
||||
class MyIO(io.IOBase):
|
||||
def __init__(self, read=False, write=False):
|
||||
self.ready_rd = False # Read and write not ready
|
||||
self.wch = b''
|
||||
if read:
|
||||
self.rbuf = b'ready\n' # Read buffer
|
||||
pyb.Timer(4, freq = 1, callback = self.do_input)
|
||||
if write:
|
||||
self.wbuf = bytearray(100) # Write buffer
|
||||
self.wprint_len = 0
|
||||
self.widx = 0
|
||||
pyb.Timer(5, freq = 10, callback = self.do_output)
|
||||
|
||||
# Read callback: emulate asynchronous input from hardware.
|
||||
# Typically would put bytes into a ring buffer and set .ready_rd.
|
||||
def do_input(self, t):
|
||||
self.ready_rd = True # Data is ready to read
|
||||
|
||||
# Write timer callback. Emulate hardware: if there's data in the buffer
|
||||
# write some or all of it
|
||||
def do_output(self, t):
|
||||
if self.wch:
|
||||
self.wbuf[self.widx] = self.wch
|
||||
self.widx += 1
|
||||
if self.wch == ord('\n'):
|
||||
self.wprint_len = self.widx # Save for schedule
|
||||
micropython.schedule(printbuf, self)
|
||||
self.widx = 0
|
||||
self.wch = b''
|
||||
|
||||
|
||||
def ioctl(self, req, arg): # see ports/stm32/uart.c
|
||||
ret = MP_STREAM_ERROR
|
||||
if req == MP_STREAM_POLL:
|
||||
ret = 0
|
||||
if arg & MP_STREAM_POLL_RD:
|
||||
if self.ready_rd:
|
||||
ret |= MP_STREAM_POLL_RD
|
||||
if arg & MP_STREAM_POLL_WR:
|
||||
if not self.wch:
|
||||
ret |= MP_STREAM_POLL_WR # Ready if no char pending
|
||||
return ret
|
||||
|
||||
# Emulate a device with buffered read. Return the buffer, falsify read ready
|
||||
# Read timer sets ready.
|
||||
def readline(self):
|
||||
self.ready_rd = False
|
||||
return self.rbuf
|
||||
|
||||
# Emulate unbuffered hardware which writes one character: uasyncio waits
|
||||
# until hardware is ready for the next. Hardware ready is emulated by write
|
||||
# timer callback.
|
||||
def write(self, buf, off, sz):
|
||||
self.wch = buf[off] # Hardware starts to write a char
|
||||
return 1 # 1 byte written. uasyncio waits on ioctl write ready
|
||||
|
||||
async def receiver(myior):
|
||||
sreader = asyncio.StreamReader(myior)
|
||||
while True:
|
||||
res = await sreader.readline()
|
||||
print('Received', res)
|
||||
|
||||
async def sender(myiow):
|
||||
swriter = asyncio.StreamWriter(myiow, {})
|
||||
await asyncio.sleep(5)
|
||||
count = 0
|
||||
while True:
|
||||
count += 1
|
||||
tosend = 'Wrote Hello MyIO {}\n'.format(count)
|
||||
await swriter.awrite(tosend.encode('UTF8'))
|
||||
await asyncio.sleep(2)
|
||||
|
||||
def test(good=True):
|
||||
if good:
|
||||
myior = MyIO(read=True)
|
||||
myiow = MyIO(write=True)
|
||||
else:
|
||||
myior = MyIO(read=True, write=True)
|
||||
myiow = myior
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(receiver(myior))
|
||||
loop.create_task(sender(myiow))
|
||||
loop.run_forever()
|
Ładowanie…
Reference in New Issue