kopia lustrzana https://github.com/micropython/micropython
Porównaj commity
22 Commity
dc5a6d54cc
...
2ccc77c285
Autor | SHA1 | Data |
---|---|---|
Jared Hancock | 2ccc77c285 | |
Damien George | e60e8079a7 | |
J. Neuschäfer | 7b050b366b | |
Damien George | 49af8cad49 | |
Damien George | 8a3546b3bd | |
Damien George | 84d6f8e8cb | |
Damien George | 967ad38ac7 | |
Damien George | d998ca78c8 | |
Damien George | 92b3b69648 | |
Damien George | 4c3f5f552b | |
Damien George | 9c7f0659e2 | |
Damien George | 45848f77ca | |
Jared Hancock | ed7ef9e48c | |
Jared Hancock | 70190b0c75 | |
Jared Hancock | fc03d95493 | |
Jared Hancock | b96f75cb95 | |
Jared Hancock | 35763b3970 | |
Jared Hancock | e9de433ecf | |
Jared Hancock | c1b0c7eca3 | |
Jared Hancock | 4364ad74ae | |
Jared Hancock | 26d0ea4006 | |
Jared Hancock | 2e371503d6 |
|
@ -156,7 +156,7 @@ static MP_DEFINE_CONST_OBJ_TYPE(
|
|||
// Task class
|
||||
|
||||
// This is the core asyncio context with cur_task, _task_queue and CancelledError.
|
||||
static mp_obj_t asyncio_context = MP_OBJ_NULL;
|
||||
mp_obj_t mp_asyncio_context = MP_OBJ_NULL;
|
||||
|
||||
static mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
|
||||
mp_arg_check_num(n_args, n_kw, 1, 2, false);
|
||||
|
@ -168,7 +168,7 @@ static mp_obj_t task_make_new(const mp_obj_type_t *type, size_t n_args, size_t n
|
|||
self->state = TASK_STATE_RUNNING_NOT_WAITED_ON;
|
||||
self->ph_key = MP_OBJ_NEW_SMALL_INT(0);
|
||||
if (n_args == 2) {
|
||||
asyncio_context = args[1];
|
||||
mp_asyncio_context = args[1];
|
||||
}
|
||||
return MP_OBJ_FROM_PTR(self);
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ static mp_obj_t task_cancel(mp_obj_t self_in) {
|
|||
return mp_const_false;
|
||||
}
|
||||
// Can't cancel self (not supported yet).
|
||||
mp_obj_t cur_task = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
|
||||
mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
|
||||
if (self_in == cur_task) {
|
||||
mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("can't cancel self"));
|
||||
}
|
||||
|
@ -195,7 +195,7 @@ static mp_obj_t task_cancel(mp_obj_t self_in) {
|
|||
self = MP_OBJ_TO_PTR(self->data);
|
||||
}
|
||||
|
||||
mp_obj_t _task_queue = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__task_queue));
|
||||
mp_obj_t _task_queue = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR__task_queue));
|
||||
|
||||
// Reschedule Task as a cancelled task.
|
||||
mp_obj_t dest[3];
|
||||
|
@ -218,7 +218,7 @@ static mp_obj_t task_cancel(mp_obj_t self_in) {
|
|||
task_queue_push(2, dest);
|
||||
}
|
||||
|
||||
self->data = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_CancelledError));
|
||||
self->data = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_CancelledError));
|
||||
|
||||
return mp_const_true;
|
||||
}
|
||||
|
@ -278,7 +278,7 @@ static mp_obj_t task_iternext(mp_obj_t self_in) {
|
|||
nlr_raise(self->data);
|
||||
} else {
|
||||
// Put calling task on waiting queue.
|
||||
mp_obj_t cur_task = mp_obj_dict_get(asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
|
||||
mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
|
||||
mp_obj_t args[2] = { self->state, cur_task };
|
||||
task_queue_push(2, args);
|
||||
// Set calling task's data to this task that it waits on, to double-link it.
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#if MICROPY_PY_NETWORK_WIZNET5K
|
||||
|
||||
#include "shared/netutils/netutils.h"
|
||||
#include "shared/runtime/softtimer.h"
|
||||
#include "extmod/modnetwork.h"
|
||||
#include "extmod/modmachine.h"
|
||||
#include "extmod/virtpin.h"
|
||||
|
@ -74,6 +75,14 @@
|
|||
#include "lib/wiznet5k/Internet/DNS/dns.h"
|
||||
#include "lib/wiznet5k/Internet/DHCP/dhcp.h"
|
||||
|
||||
// Poll WIZnet every 64ms by default (if not using interrupt pin)
|
||||
#define WIZNET5K_TICK_RATE_MS 64
|
||||
// In DHCP.c, RIP_MSG max size is defined as 312 + 256 (sadly, not in the header)
|
||||
#define MAX_DHCP_BUF_SIZE 568
|
||||
|
||||
// Soft timer for polling and running DHCP in the background.
|
||||
static soft_timer_entry_t mp_network_soft_timer;
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef printf
|
||||
|
@ -99,9 +108,9 @@ typedef struct _wiznet5k_obj_t {
|
|||
void (*spi_transfer)(mp_obj_base_t *obj, size_t len, const uint8_t *src, uint8_t *dest);
|
||||
mp_hal_pin_obj_t cs;
|
||||
mp_hal_pin_obj_t rst;
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
mp_hal_pin_obj_t pin_intn;
|
||||
bool use_interrupt;
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
uint8_t eth_frame[1514];
|
||||
uint32_t trace_flags;
|
||||
struct netif netif;
|
||||
|
@ -110,9 +119,20 @@ typedef struct _wiznet5k_obj_t {
|
|||
wiz_NetInfo netinfo;
|
||||
uint8_t socket_used;
|
||||
bool active;
|
||||
uint8_t *dhcp_buf;
|
||||
uint8_t dhcp_state;
|
||||
mp_int_t dhcp_socket;
|
||||
uint32_t dhcp_renew;
|
||||
#endif
|
||||
} wiznet5k_obj_t;
|
||||
|
||||
#if WIZNET5K_PROVIDED_STACK
|
||||
typedef struct _wiznet5k_socket_extra_t {
|
||||
byte remote_ip[4];
|
||||
mp_uint_t remote_port;
|
||||
} wiznet5k_socket_extra_t;
|
||||
#endif
|
||||
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
#define IS_ACTIVE(self) (self->netif.flags & NETIF_FLAG_UP)
|
||||
#else // WIZNET5K_PROVIDED_STACK
|
||||
|
@ -170,11 +190,7 @@ static void wiznet5k_get_mac_address(wiznet5k_obj_t *self, uint8_t mac[6]) {
|
|||
getSHAR(mac);
|
||||
}
|
||||
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
|
||||
void wiznet5k_try_poll(void);
|
||||
static void wiznet5k_lwip_init(wiznet5k_obj_t *self);
|
||||
|
||||
static mp_obj_t mpy_wiznet_read_int(mp_obj_t none_in) {
|
||||
(void)none_in;
|
||||
// Handle incoming data, unless the SPI bus is busy
|
||||
|
@ -197,6 +213,9 @@ static void wiznet5k_config_interrupt(bool enabled) {
|
|||
);
|
||||
}
|
||||
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
static void wiznet5k_lwip_init(wiznet5k_obj_t *self);
|
||||
|
||||
void wiznet5k_deinit(void) {
|
||||
for (struct netif *netif = netif_list; netif != NULL; netif = netif->next) {
|
||||
if (netif == &wiznet5k_obj.netif) {
|
||||
|
@ -350,6 +369,59 @@ void wiznet5k_poll(void) {
|
|||
|
||||
#if WIZNET5K_PROVIDED_STACK
|
||||
|
||||
static mp_int_t wiznet5k_allocate_socket(void) {
|
||||
// get first unused socket number
|
||||
for (mp_uint_t sn = 0; sn < _WIZCHIP_SOCK_NUM_; sn++) {
|
||||
if ((wiznet5k_obj.socket_used & (1 << sn)) == 0) {
|
||||
wiznet5k_obj.socket_used |= (1 << sn);
|
||||
return sn;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern uint8_t DHCP_SOCKET;
|
||||
static void wiznet5k_dhcp_poll(void) {
|
||||
if (wiznet5k_obj.dhcp_socket != -1) {
|
||||
wiznet5k_obj.dhcp_state = DHCP_run();
|
||||
}
|
||||
|
||||
if (wiznet5k_obj.dhcp_state == DHCP_IP_LEASED) {
|
||||
// Every few seconds, check in about renewing the IP
|
||||
if (wiznet5k_obj.dhcp_socket != -1) {
|
||||
// Release socket and reset timeout
|
||||
wiznet5k_obj.socket_used &= ~(1 << wiznet5k_obj.dhcp_socket);
|
||||
wiznet5k_obj.dhcp_socket = -1;
|
||||
|
||||
// Run about once per minute
|
||||
wiznet5k_obj.dhcp_renew = mp_hal_ticks_ms() + 60000;
|
||||
} else if (mp_hal_ticks_ms() > wiznet5k_obj.dhcp_renew) {
|
||||
mp_uint_t sn = wiznet5k_allocate_socket();
|
||||
if (sn != -1) {
|
||||
DHCP_SOCKET = sn;
|
||||
wiznet5k_obj.dhcp_socket = sn;
|
||||
wiznet5k_obj.dhcp_state = DHCP_run();
|
||||
}
|
||||
}
|
||||
} else if (wiznet5k_obj.dhcp_state == DHCP_FAILED || wiznet5k_obj.dhcp_state == DHCP_STOPPED) {
|
||||
if (wiznet5k_obj.dhcp_socket != -1) {
|
||||
wiznet5k_obj.socket_used &= ~(1 << wiznet5k_obj.dhcp_socket);
|
||||
wiznet5k_obj.dhcp_socket = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wiznet5k_try_poll(void) {
|
||||
// If using DHCP for the interface, periodically renew/update the address
|
||||
wiznet5k_dhcp_poll();
|
||||
|
||||
// There's really nothing to do here. The interrupt that triggered this will
|
||||
// release a WFE() wait and will trigger a poll() loop which will
|
||||
// wiznet5k_socket_ioctl will detect the readable or writeable state of the
|
||||
// respective socket(s).
|
||||
}
|
||||
|
||||
static void wiz_dhcp_assign(void) {
|
||||
getIPfromDHCP(wiznet5k_obj.netinfo.ip);
|
||||
getGWfromDHCP(wiznet5k_obj.netinfo.gw);
|
||||
|
@ -359,7 +431,7 @@ static void wiz_dhcp_assign(void) {
|
|||
}
|
||||
|
||||
static void wiz_dhcp_update(void) {
|
||||
;
|
||||
wiz_dhcp_assign();
|
||||
}
|
||||
|
||||
|
||||
|
@ -367,6 +439,22 @@ static void wiz_dhcp_conflict(void) {
|
|||
;
|
||||
}
|
||||
|
||||
// This is called by soft_timer and executes at PendSV level.
|
||||
static void mp_network_soft_timer_callback(soft_timer_entry_t *self) {
|
||||
wiznet5k_try_poll();
|
||||
}
|
||||
|
||||
static void mod_network_wiznet5k_poll_init(void) {
|
||||
soft_timer_static_init(
|
||||
&mp_network_soft_timer,
|
||||
SOFT_TIMER_MODE_PERIODIC,
|
||||
WIZNET5K_TICK_RATE_MS,
|
||||
mp_network_soft_timer_callback
|
||||
);
|
||||
|
||||
soft_timer_reinsert(&mp_network_soft_timer, WIZNET5K_TICK_RATE_MS);
|
||||
}
|
||||
|
||||
static void wiznet5k_init(void) {
|
||||
// Configure wiznet provided TCP / socket interface
|
||||
|
||||
|
@ -388,21 +476,46 @@ static void wiznet5k_init(void) {
|
|||
};
|
||||
wiznet5k_obj.netinfo = netinfo;
|
||||
|
||||
if (wiznet5k_obj.use_interrupt) {
|
||||
mp_hal_pin_input(wiznet5k_obj.pin_intn);
|
||||
wiznet5k_config_interrupt(true);
|
||||
wizchip_setinterruptmask(IK_SOCK_ALL);
|
||||
|
||||
#if _WIZCHIP_ == W5100S
|
||||
// Enable interrupt pin
|
||||
setMR2(getMR2() | MR2_G_IEN);
|
||||
#endif
|
||||
}
|
||||
|
||||
// register with network module
|
||||
mod_network_register_nic(&wiznet5k_obj);
|
||||
mod_network_wiznet5k_poll_init();
|
||||
|
||||
wiznet5k_obj.active = true;
|
||||
wiznet5k_obj.dhcp_socket = -1;
|
||||
wiznet5k_obj.dhcp_state = DHCP_STOPPED;
|
||||
}
|
||||
|
||||
static int wiznet5k_gethostbyname(mp_obj_t nic, const char *name, mp_uint_t len, uint8_t *out_ip) {
|
||||
uint8_t dns_ip[MOD_NETWORK_IPADDR_BUF_SIZE] = {8, 8, 8, 8};
|
||||
uint8_t *buf = m_new(uint8_t, MAX_DNS_BUF_SIZE);
|
||||
DNS_init(2, buf);
|
||||
mp_int_t sn = wiznet5k_allocate_socket();
|
||||
if (sn == -1) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
DNS_init(sn, buf);
|
||||
if (wiznet5k_obj.netinfo.dns[0]) {
|
||||
memcpy(dns_ip, wiznet5k_obj.netinfo.dns, MOD_NETWORK_IPADDR_BUF_SIZE);
|
||||
}
|
||||
mp_int_t ret = DNS_run(dns_ip, (uint8_t *)name, out_ip);
|
||||
m_del(uint8_t, buf, MAX_DNS_BUF_SIZE);
|
||||
|
||||
// NOTE: DNS_run will close the socket, so it just needs to be marked as
|
||||
// unused here and clear any interrupts.
|
||||
wiznet5k_obj.socket_used &= ~(1 << sn);
|
||||
wizchip_clrinterrupt(IK_SOCK_0 << sn);
|
||||
|
||||
if (ret == 1) {
|
||||
// success
|
||||
return 0;
|
||||
|
@ -432,18 +545,19 @@ static int wiznet5k_socket_socket(mod_network_socket_obj_t *socket, int *_errno)
|
|||
|
||||
if (socket->fileno == -1) {
|
||||
// get first unused socket number
|
||||
for (mp_uint_t sn = 0; sn < _WIZCHIP_SOCK_NUM_; sn++) {
|
||||
if ((wiznet5k_obj.socket_used & (1 << sn)) == 0) {
|
||||
wiznet5k_obj.socket_used |= (1 << sn);
|
||||
socket->fileno = sn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
socket->fileno = wiznet5k_allocate_socket();
|
||||
if (socket->fileno == -1) {
|
||||
// too many open sockets
|
||||
*_errno = MP_EMFILE;
|
||||
return -1;
|
||||
}
|
||||
|
||||
socket->_private = NULL;
|
||||
|
||||
// Enable data receive interrupt
|
||||
if (wiznet5k_obj.use_interrupt) {
|
||||
setSn_IMR(socket->fileno, (Sn_IR_RECV | Sn_IR_CON | Sn_IR_DISCON));
|
||||
}
|
||||
}
|
||||
|
||||
// WIZNET does not have a concept of pure "open socket". You need to know
|
||||
|
@ -461,12 +575,27 @@ static void wiznet5k_socket_close(mod_network_socket_obj_t *socket) {
|
|||
if (sn < _WIZCHIP_SOCK_NUM_) {
|
||||
wiznet5k_obj.socket_used &= ~(1 << sn);
|
||||
WIZCHIP_EXPORT(close)(sn);
|
||||
|
||||
// Disable receive interrupts
|
||||
if (wiznet5k_obj.use_interrupt) {
|
||||
setSn_IMR(sn, 0);
|
||||
}
|
||||
}
|
||||
|
||||
m_del(wiznet5k_socket_extra_t, socket->_private, 1);
|
||||
socket->_private = NULL;
|
||||
}
|
||||
|
||||
static int wiznet5k_socket_bind(mod_network_socket_obj_t *socket, byte *ip, mp_uint_t port, int *_errno) {
|
||||
uint8_t flag = 0;
|
||||
if (socket->timeout == 0) {
|
||||
// Setup non-blocking mode
|
||||
flag |= SOCK_IO_NONBLOCK;
|
||||
}
|
||||
|
||||
// open the socket in server mode (if port != 0)
|
||||
mp_int_t ret = WIZCHIP_EXPORT(socket)(socket->fileno, socket->type, port, 0);
|
||||
mp_int_t ret = WIZCHIP_EXPORT(socket)(socket->fileno, socket->type, port, flag);
|
||||
|
||||
if (ret < 0) {
|
||||
wiznet5k_socket_close(socket);
|
||||
*_errno = -ret;
|
||||
|
@ -531,22 +660,49 @@ static int wiznet5k_socket_connect(mod_network_socket_obj_t *socket, byte *ip, m
|
|||
return -1;
|
||||
}
|
||||
|
||||
// now connect
|
||||
MP_THREAD_GIL_EXIT();
|
||||
mp_int_t ret = WIZCHIP_EXPORT(connect)(socket->fileno, ip, port);
|
||||
MP_THREAD_GIL_ENTER();
|
||||
// WIZnet doesn't support connect on UDP sockets. Stash the remote
|
||||
// information for use with the ::send method, if used on this UDP socket.
|
||||
if (socket->type == Sn_MR_TCP) {
|
||||
// now connect
|
||||
MP_THREAD_GIL_EXIT();
|
||||
mp_int_t ret = WIZCHIP_EXPORT(connect)(socket->fileno, ip, port);
|
||||
MP_THREAD_GIL_ENTER();
|
||||
|
||||
if (ret < 0) {
|
||||
wiznet5k_socket_close(socket);
|
||||
*_errno = -ret;
|
||||
return -1;
|
||||
if (ret < 0) {
|
||||
wiznet5k_socket_close(socket);
|
||||
*_errno = -ret;
|
||||
return -1;
|
||||
} else if (ret == SOCK_BUSY) {
|
||||
*_errno = MP_EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
} else if (socket->type == Sn_MR_UDP) {
|
||||
// For POSIX usage of ::send later, stash the remote IP and port
|
||||
wiznet5k_socket_extra_t *extra = m_new_maybe(wiznet5k_socket_extra_t, 1);
|
||||
if (extra == NULL) {
|
||||
*_errno = MP_ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
memcpy(extra->remote_ip, ip, 4);
|
||||
extra->remote_port = port;
|
||||
socket->_private = extra;
|
||||
}
|
||||
|
||||
// success
|
||||
return 0;
|
||||
}
|
||||
|
||||
static mp_uint_t wiznet5k_socket_sendto(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, byte *ip, mp_uint_t port, int *_errno);
|
||||
static mp_uint_t wiznet5k_socket_send(mod_network_socket_obj_t *socket, const byte *buf, mp_uint_t len, int *_errno) {
|
||||
if (socket->type == Sn_MR_UDP) {
|
||||
if (socket->_private != NULL) {
|
||||
wiznet5k_socket_extra_t *extra = (wiznet5k_socket_extra_t *)socket->_private;
|
||||
return wiznet5k_socket_sendto(socket, buf, len, extra->remote_ip, extra->remote_port, _errno);
|
||||
}
|
||||
*_errno = MP_ENOTCONN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
MP_THREAD_GIL_EXIT();
|
||||
mp_int_t ret = WIZCHIP_EXPORT(send)(socket->fileno, (byte *)buf, len);
|
||||
MP_THREAD_GIL_ENTER();
|
||||
|
@ -556,11 +712,28 @@ static mp_uint_t wiznet5k_socket_send(mod_network_socket_obj_t *socket, const by
|
|||
wiznet5k_socket_close(socket);
|
||||
*_errno = -ret;
|
||||
return -1;
|
||||
} else if (ret == SOCK_BUSY) {
|
||||
uint8_t status = getSn_SR(socket->fileno);
|
||||
if (status == SOCK_ESTABLISHED || status == SOCK_CLOSE_WAIT) {
|
||||
*_errno = MP_EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static mp_uint_t wiznet5k_socket_recv(mod_network_socket_obj_t *socket, byte *buf, mp_uint_t len, int *_errno) {
|
||||
if (socket->timeout > 0) {
|
||||
mp_uint_t start = mp_hal_ticks_ms();
|
||||
while (getSn_SR(socket->fileno) == SOCK_ESTABLISHED && getSn_RX_RSR(socket->fileno) == 0) {
|
||||
if (mp_hal_ticks_ms() - start > socket->timeout) {
|
||||
*_errno = MP_ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
mp_event_wait_ms(1);
|
||||
}
|
||||
}
|
||||
|
||||
MP_THREAD_GIL_EXIT();
|
||||
mp_int_t ret = WIZCHIP_EXPORT(recv)(socket->fileno, buf, len);
|
||||
MP_THREAD_GIL_ENTER();
|
||||
|
@ -570,6 +743,14 @@ static mp_uint_t wiznet5k_socket_recv(mod_network_socket_obj_t *socket, byte *bu
|
|||
wiznet5k_socket_close(socket);
|
||||
*_errno = -ret;
|
||||
return -1;
|
||||
} else if (ret == SOCK_BUSY) {
|
||||
// NOTE: SOCK_BUSY is zero (0) which is confusing if the socket is closed
|
||||
// and at EOF
|
||||
uint8_t status = getSn_SR(socket->fileno);
|
||||
if (status == SOCK_ESTABLISHED || status == SOCK_CLOSE_WAIT) {
|
||||
*_errno = MP_EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
@ -590,12 +771,27 @@ static mp_uint_t wiznet5k_socket_sendto(mod_network_socket_obj_t *socket, const
|
|||
wiznet5k_socket_close(socket);
|
||||
*_errno = -ret;
|
||||
return -1;
|
||||
} else if (ret == SOCK_BUSY) {
|
||||
*_errno = MP_EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static mp_uint_t wiznet5k_socket_recvfrom(mod_network_socket_obj_t *socket, byte *buf, mp_uint_t len, byte *ip, mp_uint_t *port, int *_errno) {
|
||||
uint16_t port2;
|
||||
|
||||
if (socket->timeout > 0) {
|
||||
mp_uint_t start = mp_hal_ticks_ms();
|
||||
while (getSn_SR(socket->fileno) != SOCK_CLOSED && getSn_RX_RSR(socket->fileno) == 0) {
|
||||
if (mp_hal_ticks_ms() - start > socket->timeout) {
|
||||
*_errno = MP_ETIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
mp_event_wait_ms(1);
|
||||
}
|
||||
}
|
||||
|
||||
MP_THREAD_GIL_EXIT();
|
||||
mp_int_t ret = WIZCHIP_EXPORT(recvfrom)(socket->fileno, buf, len, ip, &port2);
|
||||
MP_THREAD_GIL_ENTER();
|
||||
|
@ -604,39 +800,78 @@ static mp_uint_t wiznet5k_socket_recvfrom(mod_network_socket_obj_t *socket, byte
|
|||
wiznet5k_socket_close(socket);
|
||||
*_errno = -ret;
|
||||
return -1;
|
||||
} else if (ret == SOCK_BUSY) {
|
||||
*_errno = MP_EAGAIN;
|
||||
return -1;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wiznet5k_socket_setsockopt(mod_network_socket_obj_t *socket, mp_uint_t level, mp_uint_t opt, const void *optval, mp_uint_t optlen, int *_errno) {
|
||||
// TODO
|
||||
*_errno = MP_EINVAL;
|
||||
return -1;
|
||||
switch (opt) {
|
||||
// level: SOL_SOCKET
|
||||
case MOD_NETWORK_SO_REUSEADDR:
|
||||
case MOD_NETWORK_SO_BROADCAST:
|
||||
// Implied/not-required in Wiznet sockets
|
||||
break;
|
||||
|
||||
default:
|
||||
*_errno = MP_EINVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wiznet5k_socket_settimeout(mod_network_socket_obj_t *socket, mp_uint_t timeout_ms, int *_errno) {
|
||||
// TODO
|
||||
*_errno = MP_EINVAL;
|
||||
return -1;
|
||||
|
||||
/*
|
||||
uint8_t arg;
|
||||
if (timeout_ms == 0) {
|
||||
// set non-blocking mode
|
||||
uint8_t arg = SOCK_IO_NONBLOCK;
|
||||
WIZCHIP_EXPORT(ctlsocket)(socket->fileno, CS_SET_IOMODE, &arg);
|
||||
arg = SOCK_IO_NONBLOCK;
|
||||
} else {
|
||||
arg = SOCK_IO_BLOCK;
|
||||
}
|
||||
*/
|
||||
|
||||
mp_int_t ret = WIZCHIP_EXPORT(ctlsocket)(socket->fileno, CS_SET_IOMODE, &arg);
|
||||
if (ret < 0) {
|
||||
*_errno = -ret;
|
||||
return -1;
|
||||
}
|
||||
|
||||
socket->timeout = timeout_ms;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wiznet5k_socket_ioctl(mod_network_socket_obj_t *socket, mp_uint_t request, mp_uint_t arg, int *_errno) {
|
||||
uint8_t sn = (uint8_t)socket->fileno;
|
||||
|
||||
if (request == MP_STREAM_POLL) {
|
||||
int ret = 0;
|
||||
if (arg & MP_STREAM_POLL_RD && getSn_RX_RSR(socket->fileno) != 0) {
|
||||
ret |= MP_STREAM_POLL_RD;
|
||||
if (sn < _WIZCHIP_SOCK_NUM_) {
|
||||
if (arg & MP_STREAM_POLL_RD && getSn_RX_RSR(sn) != 0) {
|
||||
ret |= MP_STREAM_POLL_RD;
|
||||
}
|
||||
if (arg & MP_STREAM_POLL_WR && getSn_TX_FSR(sn) != 0) {
|
||||
ret |= MP_STREAM_POLL_WR;
|
||||
}
|
||||
|
||||
uint8_t status = getSn_SR(sn);
|
||||
if (status == SOCK_CLOSE_WAIT || getSn_IR(sn) & Sn_IR_DISCON) {
|
||||
// Peer-closed socket is both readable and writable: read will
|
||||
// return EOF, write - error. Without this poll will hang on a
|
||||
// socket which was closed by peer.
|
||||
ret |= arg & (MP_STREAM_POLL_RD | MP_STREAM_POLL_WR);
|
||||
} else if (status == SOCK_CLOSED) {
|
||||
ret |= MP_STREAM_POLL_ERR;
|
||||
}
|
||||
} else {
|
||||
ret |= MP_STREAM_POLL_NVAL;
|
||||
}
|
||||
if (arg & MP_STREAM_POLL_WR && getSn_TX_FSR(socket->fileno) != 0) {
|
||||
ret |= MP_STREAM_POLL_WR;
|
||||
|
||||
if (wiznet5k_obj.use_interrupt) {
|
||||
setSn_IR(sn, (Sn_IR_RECV | Sn_IR_CON | Sn_IR_DISCON));
|
||||
}
|
||||
|
||||
return ret;
|
||||
} else {
|
||||
*_errno = MP_EINVAL;
|
||||
|
@ -645,23 +880,31 @@ static int wiznet5k_socket_ioctl(mod_network_socket_obj_t *socket, mp_uint_t req
|
|||
}
|
||||
|
||||
static void wiznet5k_dhcp_init(wiznet5k_obj_t *self) {
|
||||
uint8_t test_buf[2048];
|
||||
uint8_t ret = 0;
|
||||
uint8_t dhcp_retry = 0;
|
||||
self->dhcp_state = DHCP_STOPPED;
|
||||
self->dhcp_buf = m_new_maybe(uint8_t, MAX_DHCP_BUF_SIZE);
|
||||
if (self->dhcp_buf == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (ret != DHCP_IP_LEASED) {
|
||||
self->dhcp_socket = wiznet5k_allocate_socket();
|
||||
if (self->dhcp_socket == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
while (self->dhcp_state != DHCP_IP_LEASED) {
|
||||
mp_uint_t timeout = mp_hal_ticks_ms() + 3000;
|
||||
DHCP_init(1, test_buf);
|
||||
DHCP_init(self->dhcp_socket, self->dhcp_buf);
|
||||
|
||||
while (1) {
|
||||
ret = DHCP_run();
|
||||
if (ret == DHCP_IP_LEASED) {
|
||||
mpy_wiznet_yield();
|
||||
if (self->dhcp_state == DHCP_IP_LEASED) {
|
||||
break;
|
||||
} else if (ret == DHCP_FAILED || mp_hal_ticks_ms() > timeout) {
|
||||
} else if (self->dhcp_state == DHCP_FAILED || mp_hal_ticks_ms() > timeout) {
|
||||
dhcp_retry++;
|
||||
break;
|
||||
}
|
||||
mpy_wiznet_yield();
|
||||
}
|
||||
|
||||
if (dhcp_retry > 3) {
|
||||
|
@ -670,9 +913,11 @@ static void wiznet5k_dhcp_init(wiznet5k_obj_t *self) {
|
|||
}
|
||||
}
|
||||
|
||||
if (ret == DHCP_IP_LEASED) {
|
||||
if (self->dhcp_state == DHCP_IP_LEASED) {
|
||||
ctlnetwork(CN_GET_NETINFO, &self->netinfo);
|
||||
}
|
||||
|
||||
wizchip_clrinterrupt(IK_SOCK_0 << self->dhcp_socket);
|
||||
}
|
||||
|
||||
#endif // WIZNET5K_PROVIDED_STACK
|
||||
|
@ -686,11 +931,10 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
|
|||
mp_obj_base_t *spi;
|
||||
mp_hal_pin_obj_t cs;
|
||||
mp_hal_pin_obj_t rst;
|
||||
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
mp_hal_pin_obj_t pin_intn = (mp_hal_pin_obj_t)NULL;
|
||||
bool use_interrupt = false;
|
||||
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
// Bring down interface while configuring
|
||||
wiznet5k_obj.netif.flags = 0;
|
||||
#endif
|
||||
|
@ -713,7 +957,7 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
|
|||
|
||||
cs = mp_hal_get_pin_obj(mp_pin_make_new(NULL, 1, 0, (mp_obj_t[]) {MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIZNET_PIN_CS)}));
|
||||
rst = mp_hal_get_pin_obj(mp_pin_make_new(NULL, 1, 0, (mp_obj_t[]) {MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIZNET_PIN_RST)}));
|
||||
#if WIZNET5K_WITH_LWIP_STACK && defined(MICROPY_HW_WIZNET_PIN_INTN)
|
||||
#ifdef MICROPY_HW_WIZNET_PIN_INTN
|
||||
pin_intn = mp_hal_get_pin_obj(mp_pin_make_new(NULL, 1, 0, (mp_obj_t[]) {MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIZNET_PIN_INTN)}));
|
||||
use_interrupt = true;
|
||||
#endif
|
||||
|
@ -722,20 +966,14 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
|
|||
#endif
|
||||
{
|
||||
// If passing in args, must supply spi, pin_cs, pin_rst and optionally pin_intn
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
mp_arg_check_num(n_args, n_kw, 3, 4, false);
|
||||
#else
|
||||
mp_arg_check_num(n_args, n_kw, 3, 3, false);
|
||||
#endif
|
||||
spi = mp_hal_get_spi_obj(args[0]);
|
||||
cs = mp_hal_get_pin_obj(args[1]);
|
||||
rst = mp_hal_get_pin_obj(args[2]);
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
if (n_args > 3) {
|
||||
pin_intn = mp_hal_get_pin_obj(args[3]);
|
||||
use_interrupt = true;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
mp_hal_pin_output(cs);
|
||||
|
@ -748,9 +986,9 @@ static mp_obj_t wiznet5k_make_new(const mp_obj_type_t *type, size_t n_args, size
|
|||
wiznet5k_obj.spi_transfer = ((mp_machine_spi_p_t *)MP_OBJ_TYPE_GET_SLOT(spi->type, protocol))->transfer;
|
||||
wiznet5k_obj.cs = cs;
|
||||
wiznet5k_obj.rst = rst;
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
wiznet5k_obj.pin_intn = pin_intn;
|
||||
wiznet5k_obj.use_interrupt = use_interrupt;
|
||||
#if WIZNET5K_WITH_LWIP_STACK
|
||||
wiznet5k_obj.trace_flags = 0;
|
||||
#else // WIZNET5K_PROVIDED_STACK
|
||||
wiznet5k_obj.active = false;
|
||||
|
@ -894,6 +1132,11 @@ static mp_obj_t wiznet5k_ifconfig(size_t n_args, const mp_obj_t *args) {
|
|||
} else {
|
||||
// Set static IP addresses
|
||||
self->netinfo.dhcp = NETINFO_STATIC;
|
||||
self->dhcp_state = DHCP_STOPPED;
|
||||
if (self->dhcp_buf) {
|
||||
m_del(uint8_t, self->dhcp_buf, MAX_DHCP_BUF_SIZE);
|
||||
self->dhcp_buf = NULL;
|
||||
}
|
||||
mp_obj_t *items;
|
||||
mp_obj_get_array_fixed_n(args[1], 4, &items);
|
||||
netutils_parse_ipv4_addr(items[0], netinfo.ip, NETUTILS_BIG);
|
||||
|
|
|
@ -59,6 +59,12 @@
|
|||
|
||||
// options to control how MicroPython is built
|
||||
|
||||
// Due to the use of LTO and the unknown distance between nlr.o and nlrthumb.o code,
|
||||
// MCUs using the Thumb 1 instruction set must enable this NLR long jump feature.
|
||||
#if defined(NRF51822)
|
||||
#define MICROPY_NLR_THUMB_USE_LONG_JUMP (1)
|
||||
#endif
|
||||
|
||||
#ifndef MICROPY_VFS
|
||||
#define MICROPY_VFS (CORE_FEAT)
|
||||
#endif
|
||||
|
|
|
@ -218,6 +218,10 @@ extern const struct _mp_obj_type_t mod_network_nic_type_nina;
|
|||
#if MICROPY_PY_NETWORK_WIZNET5K
|
||||
extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k;
|
||||
#define MICROPY_HW_NIC_WIZNET5K { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) },
|
||||
// This Network interface requires the extended socket state for timeouts and non-blocking
|
||||
#ifndef MICROPY_PY_SOCKET_EXTENDED_STATE
|
||||
#define MICROPY_PY_SOCKET_EXTENDED_STATE (1)
|
||||
#endif
|
||||
#else
|
||||
#define MICROPY_HW_NIC_WIZNET5K
|
||||
#endif
|
||||
|
|
|
@ -202,6 +202,10 @@ extern const struct _mp_obj_type_t mp_network_cyw43_type;
|
|||
#if MICROPY_PY_NETWORK_WIZNET5K
|
||||
extern const struct _mp_obj_type_t mod_network_nic_type_wiznet5k;
|
||||
#define MICROPY_HW_NIC_WIZNET5K { MP_ROM_QSTR(MP_QSTR_WIZNET5K), MP_ROM_PTR(&mod_network_nic_type_wiznet5k) },
|
||||
// This Network interface requires the extended socket state for timeouts and non-blocking
|
||||
#ifndef MICROPY_PY_SOCKET_EXTENDED_STATE
|
||||
#define MICROPY_PY_SOCKET_EXTENDED_STATE (1)
|
||||
#endif
|
||||
#else
|
||||
#define MICROPY_HW_NIC_WIZNET5K
|
||||
#endif
|
||||
|
|
|
@ -22,6 +22,9 @@ BUILD ?= build-$(VARIANT)
|
|||
include ../../py/mkenv.mk
|
||||
include $(VARIANT_DIR)/mpconfigvariant.mk
|
||||
|
||||
# Use the default frozen manifest, variants may override this.
|
||||
FROZEN_MANIFEST ?= variants/manifest.py
|
||||
|
||||
# Qstr definitions (must come before including py.mk).
|
||||
QSTR_DEFS = qstrdefsport.h
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ export async function loadMicroPython(options) {
|
|||
{ heapsize: 1024 * 1024, linebuffer: true },
|
||||
options,
|
||||
);
|
||||
const Module = {};
|
||||
let Module = {};
|
||||
Module.locateFile = (path, scriptDirectory) =>
|
||||
url || scriptDirectory + path;
|
||||
Module._textDecoder = new TextDecoder();
|
||||
|
@ -83,11 +83,7 @@ export async function loadMicroPython(options) {
|
|||
Module.stderr = (c) => stderr(new Uint8Array([c]));
|
||||
}
|
||||
}
|
||||
const moduleLoaded = new Promise((r) => {
|
||||
Module.postRun = r;
|
||||
});
|
||||
_createMicroPythonModule(Module);
|
||||
await moduleLoaded;
|
||||
Module = await _createMicroPythonModule(Module);
|
||||
globalThis.Module = Module;
|
||||
proxy_js_init();
|
||||
const pyimport = (name) => {
|
||||
|
@ -131,23 +127,31 @@ export async function loadMicroPython(options) {
|
|||
},
|
||||
pyimport: pyimport,
|
||||
runPython(code) {
|
||||
const len = Module.lengthBytesUTF8(code);
|
||||
const buf = Module._malloc(len + 1);
|
||||
Module.stringToUTF8(code, buf, len + 1);
|
||||
const value = Module._malloc(3 * 4);
|
||||
Module.ccall(
|
||||
"mp_js_do_exec",
|
||||
"number",
|
||||
["string", "pointer"],
|
||||
[code, value],
|
||||
["pointer", "number", "pointer"],
|
||||
[buf, len, value],
|
||||
);
|
||||
Module._free(buf);
|
||||
return proxy_convert_mp_to_js_obj_jsside_with_free(value);
|
||||
},
|
||||
runPythonAsync(code) {
|
||||
const len = Module.lengthBytesUTF8(code);
|
||||
const buf = Module._malloc(len + 1);
|
||||
Module.stringToUTF8(code, buf, len + 1);
|
||||
const value = Module._malloc(3 * 4);
|
||||
Module.ccall(
|
||||
"mp_js_do_exec_async",
|
||||
"number",
|
||||
["string", "pointer"],
|
||||
[code, value],
|
||||
["pointer", "number", "pointer"],
|
||||
[buf, len, value],
|
||||
);
|
||||
Module._free(buf);
|
||||
return proxy_convert_mp_to_js_obj_jsside_with_free(value);
|
||||
},
|
||||
replInit() {
|
||||
|
@ -224,6 +228,16 @@ async function runCLI() {
|
|||
}
|
||||
});
|
||||
} else {
|
||||
// If the script to run ends with a running of the asyncio main loop, then inject
|
||||
// a simple `asyncio.run` hook that starts the main task. This is primarily to
|
||||
// support running the standard asyncio tests.
|
||||
if (contents.endsWith("asyncio.run(main())\n")) {
|
||||
const asyncio = mp.pyimport("asyncio");
|
||||
asyncio.run = async (task) => {
|
||||
await asyncio.create_task(task);
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
mp.runPython(contents);
|
||||
} catch (error) {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# MicroPython asyncio module, for use with webassembly port
|
||||
# MIT license; Copyright (c) 2024 Damien P. George
|
||||
|
||||
from .core import *
|
||||
from .funcs import wait_for, wait_for_ms, gather
|
||||
from .event import Event
|
||||
from .lock import Lock
|
||||
|
||||
__version__ = (3, 0, 0)
|
|
@ -0,0 +1,249 @@
|
|||
# MicroPython asyncio module, for use with webassembly port
|
||||
# MIT license; Copyright (c) 2019-2024 Damien P. George
|
||||
|
||||
from time import ticks_ms as ticks, ticks_diff, ticks_add
|
||||
import sys, js, jsffi
|
||||
|
||||
# Import TaskQueue and Task from built-in C code.
|
||||
from _asyncio import TaskQueue, Task
|
||||
|
||||
|
||||
################################################################################
|
||||
# Exceptions
|
||||
|
||||
|
||||
class CancelledError(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
class TimeoutError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
# Used when calling Loop.call_exception_handler.
|
||||
_exc_context = {"message": "Task exception wasn't retrieved", "exception": None, "future": None}
|
||||
|
||||
|
||||
################################################################################
|
||||
# Sleep functions
|
||||
|
||||
|
||||
# "Yield" once, then raise StopIteration
|
||||
class SingletonGenerator:
|
||||
def __init__(self):
|
||||
self.state = None
|
||||
self.exc = StopIteration()
|
||||
|
||||
def __iter__(self):
|
||||
return self
|
||||
|
||||
def __next__(self):
|
||||
if self.state is not None:
|
||||
_task_queue.push(cur_task, self.state)
|
||||
self.state = None
|
||||
return None
|
||||
else:
|
||||
self.exc.__traceback__ = None
|
||||
raise self.exc
|
||||
|
||||
|
||||
# Pause task execution for the given time (integer in milliseconds, uPy extension)
|
||||
# Use a SingletonGenerator to do it without allocating on the heap
|
||||
def sleep_ms(t, sgen=SingletonGenerator()):
|
||||
if cur_task is None:
|
||||
# Support top-level asyncio.sleep, via a JavaScript Promise.
|
||||
return jsffi.async_timeout_ms(t)
|
||||
assert sgen.state is None
|
||||
sgen.state = ticks_add(ticks(), max(0, t))
|
||||
return sgen
|
||||
|
||||
|
||||
# Pause task execution for the given time (in seconds)
|
||||
def sleep(t):
|
||||
return sleep_ms(int(t * 1000))
|
||||
|
||||
|
||||
################################################################################
|
||||
# Main run loop
|
||||
|
||||
asyncio_timer = None
|
||||
|
||||
|
||||
class ThenableEvent:
|
||||
def __init__(self, thenable):
|
||||
self.result = None # Result of the thenable
|
||||
self.waiting = None # Task waiting on completion of this thenable
|
||||
thenable.then(self.set)
|
||||
|
||||
def set(self, value):
|
||||
# Thenable/Promise is fulfilled, set result and schedule any waiting task.
|
||||
self.result = value
|
||||
if self.waiting:
|
||||
_task_queue.push(self.waiting)
|
||||
self.waiting = None
|
||||
_schedule_run_iter(0)
|
||||
|
||||
def remove(self, task):
|
||||
self.waiting = None
|
||||
|
||||
# async
|
||||
def wait(self):
|
||||
# Set the calling task as the task waiting on this thenable.
|
||||
self.waiting = cur_task
|
||||
# Set calling task's data to this object so it can be removed if needed.
|
||||
cur_task.data = self
|
||||
# Wait for the thenable to fulfill.
|
||||
yield
|
||||
# Return the result of the thenable.
|
||||
return self.result
|
||||
|
||||
|
||||
# Ensure the awaitable is a task
|
||||
def _promote_to_task(aw):
|
||||
return aw if isinstance(aw, Task) else create_task(aw)
|
||||
|
||||
|
||||
def _schedule_run_iter(dt):
|
||||
global asyncio_timer
|
||||
if asyncio_timer is not None:
|
||||
js.clearTimeout(asyncio_timer)
|
||||
asyncio_timer = js.setTimeout(_run_iter, dt)
|
||||
|
||||
|
||||
def _run_iter():
|
||||
global cur_task
|
||||
excs_all = (CancelledError, Exception) # To prevent heap allocation in loop
|
||||
excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop
|
||||
while True:
|
||||
# Wait until the head of _task_queue is ready to run
|
||||
t = _task_queue.peek()
|
||||
if t:
|
||||
# A task waiting on _task_queue; "ph_key" is time to schedule task at
|
||||
dt = max(0, ticks_diff(t.ph_key, ticks()))
|
||||
else:
|
||||
# No tasks can be woken so finished running
|
||||
cur_task = None
|
||||
return
|
||||
|
||||
if dt > 0:
|
||||
# schedule to call again later
|
||||
cur_task = None
|
||||
_schedule_run_iter(dt)
|
||||
return
|
||||
|
||||
# Get next task to run and continue it
|
||||
t = _task_queue.pop()
|
||||
cur_task = t
|
||||
try:
|
||||
# Continue running the coroutine, it's responsible for rescheduling itself
|
||||
exc = t.data
|
||||
if not exc:
|
||||
t.coro.send(None)
|
||||
else:
|
||||
# If the task is finished and on the run queue and gets here, then it
|
||||
# had an exception and was not await'ed on. Throwing into it now will
|
||||
# raise StopIteration and the code below will catch this and run the
|
||||
# call_exception_handler function.
|
||||
t.data = None
|
||||
t.coro.throw(exc)
|
||||
except excs_all as er:
|
||||
# Check the task is not on any event queue
|
||||
assert t.data is None
|
||||
# This task is done.
|
||||
if t.state:
|
||||
# Task was running but is now finished.
|
||||
waiting = False
|
||||
if t.state is True:
|
||||
# "None" indicates that the task is complete and not await'ed on (yet).
|
||||
t.state = None
|
||||
elif callable(t.state):
|
||||
# The task has a callback registered to be called on completion.
|
||||
t.state(t, er)
|
||||
t.state = False
|
||||
waiting = True
|
||||
else:
|
||||
# Schedule any other tasks waiting on the completion of this task.
|
||||
while t.state.peek():
|
||||
_task_queue.push(t.state.pop())
|
||||
waiting = True
|
||||
# "False" indicates that the task is complete and has been await'ed on.
|
||||
t.state = False
|
||||
if not waiting and not isinstance(er, excs_stop):
|
||||
# An exception ended this detached task, so queue it for later
|
||||
# execution to handle the uncaught exception if no other task retrieves
|
||||
# the exception in the meantime (this is handled by Task.throw).
|
||||
_task_queue.push(t)
|
||||
# Save return value of coro to pass up to caller.
|
||||
t.data = er
|
||||
elif t.state is None:
|
||||
# Task is already finished and nothing await'ed on the task,
|
||||
# so call the exception handler.
|
||||
|
||||
# Save exception raised by the coro for later use.
|
||||
t.data = exc
|
||||
|
||||
# Create exception context and call the exception handler.
|
||||
_exc_context["exception"] = exc
|
||||
_exc_context["future"] = t
|
||||
Loop.call_exception_handler(_exc_context)
|
||||
|
||||
|
||||
# Create and schedule a new task from a coroutine.
|
||||
def create_task(coro):
|
||||
if not hasattr(coro, "send"):
|
||||
raise TypeError("coroutine expected")
|
||||
t = Task(coro, globals())
|
||||
_task_queue.push(t)
|
||||
_schedule_run_iter(0)
|
||||
return t
|
||||
|
||||
|
||||
################################################################################
|
||||
# Event loop wrapper
|
||||
|
||||
|
||||
cur_task = None
|
||||
|
||||
|
||||
class Loop:
|
||||
_exc_handler = None
|
||||
|
||||
def create_task(coro):
|
||||
return create_task(coro)
|
||||
|
||||
def close():
|
||||
pass
|
||||
|
||||
def set_exception_handler(handler):
|
||||
Loop._exc_handler = handler
|
||||
|
||||
def get_exception_handler():
|
||||
return Loop._exc_handler
|
||||
|
||||
def default_exception_handler(loop, context):
|
||||
print(context["message"], file=sys.stderr)
|
||||
print("future:", context["future"], "coro=", context["future"].coro, file=sys.stderr)
|
||||
sys.print_exception(context["exception"], sys.stderr)
|
||||
|
||||
def call_exception_handler(context):
|
||||
(Loop._exc_handler or Loop.default_exception_handler)(Loop, context)
|
||||
|
||||
|
||||
def get_event_loop():
|
||||
return Loop
|
||||
|
||||
|
||||
def current_task():
|
||||
if cur_task is None:
|
||||
raise RuntimeError("no running event loop")
|
||||
return cur_task
|
||||
|
||||
|
||||
def new_event_loop():
|
||||
global _task_queue
|
||||
_task_queue = TaskQueue() # TaskQueue of Task instances.
|
||||
return Loop
|
||||
|
||||
|
||||
# Initialise default event loop.
|
||||
new_event_loop()
|
|
@ -104,7 +104,7 @@ void mp_js_do_import(const char *name, uint32_t *out) {
|
|||
}
|
||||
}
|
||||
|
||||
void mp_js_do_exec(const char *src, uint32_t *out) {
|
||||
void mp_js_do_exec(const char *src, size_t len, uint32_t *out) {
|
||||
// Collect at the top-level, where there are no root pointers from stack/registers.
|
||||
gc_collect_start();
|
||||
gc_collect_end();
|
||||
|
@ -112,7 +112,7 @@ void mp_js_do_exec(const char *src, uint32_t *out) {
|
|||
mp_parse_input_kind_t input_kind = MP_PARSE_FILE_INPUT;
|
||||
nlr_buf_t nlr;
|
||||
if (nlr_push(&nlr) == 0) {
|
||||
mp_lexer_t *lex = mp_lexer_new_from_str_len_dedent(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
|
||||
mp_lexer_t *lex = mp_lexer_new_from_str_len_dedent(MP_QSTR__lt_stdin_gt_, src, len, 0);
|
||||
qstr source_name = lex->source_name;
|
||||
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
|
||||
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, false);
|
||||
|
@ -125,9 +125,9 @@ void mp_js_do_exec(const char *src, uint32_t *out) {
|
|||
}
|
||||
}
|
||||
|
||||
void mp_js_do_exec_async(const char *src, uint32_t *out) {
|
||||
void mp_js_do_exec_async(const char *src, size_t len, uint32_t *out) {
|
||||
mp_compile_allow_top_level_await = true;
|
||||
mp_js_do_exec(src, out);
|
||||
mp_js_do_exec(src, len, out);
|
||||
mp_compile_allow_top_level_await = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -61,12 +61,27 @@ static mp_obj_t mp_jsffi_to_js(mp_obj_t arg) {
|
|||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_to_js_obj, mp_jsffi_to_js);
|
||||
|
||||
// *FORMAT-OFF*
|
||||
EM_JS(void, promise_with_timeout_ms, (double ms, uint32_t * out), {
|
||||
const ret = new Promise((resolve) => setTimeout(resolve, ms));
|
||||
proxy_convert_js_to_mp_obj_jsside(ret, out);
|
||||
});
|
||||
// *FORMAT-ON*
|
||||
|
||||
static mp_obj_t mp_jsffi_async_timeout_ms(mp_obj_t arg) {
|
||||
uint32_t out[PVN];
|
||||
promise_with_timeout_ms(mp_obj_get_float_to_d(arg), out);
|
||||
return proxy_convert_js_to_mp_obj_cside(out);
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_1(mp_jsffi_async_timeout_ms_obj, mp_jsffi_async_timeout_ms);
|
||||
|
||||
static const mp_rom_map_elem_t mp_module_jsffi_globals_table[] = {
|
||||
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_jsffi) },
|
||||
|
||||
{ MP_ROM_QSTR(MP_QSTR_JsProxy), MP_ROM_PTR(&mp_type_jsproxy) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_create_proxy), MP_ROM_PTR(&mp_jsffi_create_proxy_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_to_js), MP_ROM_PTR(&mp_jsffi_to_js_obj) },
|
||||
{ MP_ROM_QSTR(MP_QSTR_async_timeout_ms), MP_ROM_PTR(&mp_jsffi_async_timeout_ms_obj) },
|
||||
};
|
||||
static MP_DEFINE_CONST_DICT(mp_module_jsffi_globals, mp_module_jsffi_globals_table);
|
||||
|
||||
|
|
|
@ -346,6 +346,12 @@ typedef struct _jsproxy_gen_t {
|
|||
|
||||
mp_vm_return_kind_t jsproxy_gen_resume(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t *ret_val) {
|
||||
jsproxy_gen_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
|
||||
if (throw_value) {
|
||||
*ret_val = throw_value;
|
||||
return MP_VM_RETURN_EXCEPTION;
|
||||
}
|
||||
|
||||
switch (self->state) {
|
||||
case JSOBJ_GEN_STATE_WAITING:
|
||||
self->state = JSOBJ_GEN_STATE_COMPLETED;
|
||||
|
@ -468,9 +474,29 @@ static mp_obj_t jsproxy_new_gen(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
#if MICROPY_PY_ASYNCIO
|
||||
extern mp_obj_t mp_asyncio_context;
|
||||
#endif
|
||||
|
||||
static mp_obj_t jsproxy_getiter(mp_obj_t self_in, mp_obj_iter_buf_t *iter_buf) {
|
||||
mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
|
||||
if (has_attr(self->ref, "then")) {
|
||||
#if MICROPY_PY_ASYNCIO
|
||||
// When asyncio is running and the caller here is a task, wrap the JavaScript
|
||||
// thenable in a ThenableEvent, and get the task to wait on that event. This
|
||||
// decouples the task from the thenable and allows cancelling the task.
|
||||
if (mp_asyncio_context != MP_OBJ_NULL) {
|
||||
mp_obj_t cur_task = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_cur_task));
|
||||
if (cur_task != mp_const_none) {
|
||||
mp_obj_t thenable_event_class = mp_obj_dict_get(mp_asyncio_context, MP_OBJ_NEW_QSTR(MP_QSTR_ThenableEvent));
|
||||
mp_obj_t thenable_event = mp_call_function_1(thenable_event_class, self_in);
|
||||
mp_obj_t dest[2];
|
||||
mp_load_method(thenable_event, MP_QSTR_wait, dest);
|
||||
mp_obj_t wait_gen = mp_call_method_n_kw(0, 0, dest);
|
||||
return mp_getiter(wait_gen, iter_buf);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return jsproxy_new_gen(self_in, iter_buf);
|
||||
} else {
|
||||
return jsproxy_new_it(self_in, iter_buf);
|
||||
|
|
|
@ -296,10 +296,11 @@ EM_JS(void, js_then_resolve, (uint32_t * ret_value, uint32_t * resolve, uint32_t
|
|||
resolve_js(ret_value_js);
|
||||
});
|
||||
|
||||
EM_JS(void, js_then_reject, (uint32_t * resolve, uint32_t * reject), {
|
||||
EM_JS(void, js_then_reject, (uint32_t * ret_value, uint32_t * resolve, uint32_t * reject), {
|
||||
const ret_value_js = proxy_convert_mp_to_js_obj_jsside(ret_value);
|
||||
const resolve_js = proxy_convert_mp_to_js_obj_jsside(resolve);
|
||||
const reject_js = proxy_convert_mp_to_js_obj_jsside(reject);
|
||||
reject_js(null);
|
||||
reject_js(ret_value_js);
|
||||
});
|
||||
|
||||
// *FORMAT-OFF*
|
||||
|
@ -307,14 +308,34 @@ EM_JS(void, js_then_continue, (int jsref, uint32_t * py_resume, uint32_t * resol
|
|||
const py_resume_js = proxy_convert_mp_to_js_obj_jsside(py_resume);
|
||||
const resolve_js = proxy_convert_mp_to_js_obj_jsside(resolve);
|
||||
const reject_js = proxy_convert_mp_to_js_obj_jsside(reject);
|
||||
const ret = proxy_js_ref[jsref].then((x) => {py_resume_js(x, resolve_js, reject_js);}, reject_js);
|
||||
const ret = proxy_js_ref[jsref].then(
|
||||
(result) => {
|
||||
// The Promise is fulfilled on the JavaScript side. Take the result and
|
||||
// send it to the encapsulating generator on the Python side, so it
|
||||
// becomes the result of the "yield from" that deferred to this Promise.
|
||||
py_resume_js(result, null, resolve_js, reject_js);
|
||||
},
|
||||
(reason) => {
|
||||
// The Promise is rejected on the JavaScript side. Take the reason and
|
||||
// throw it into the encapsulating generator on the Python side.
|
||||
py_resume_js(null, reason, resolve_js, reject_js);
|
||||
},
|
||||
);
|
||||
proxy_convert_js_to_mp_obj_jsside(ret, out);
|
||||
});
|
||||
// *FORMAT-ON*
|
||||
|
||||
static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t value, mp_obj_t resolve, mp_obj_t reject) {
|
||||
static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t send_value, mp_obj_t throw_value, mp_obj_t resolve, mp_obj_t reject) {
|
||||
if (throw_value != MP_OBJ_NULL && throw_value != mp_const_none) {
|
||||
if (send_value == mp_const_none) {
|
||||
send_value = MP_OBJ_NULL;
|
||||
}
|
||||
} else {
|
||||
throw_value = MP_OBJ_NULL;
|
||||
}
|
||||
|
||||
mp_obj_t ret_value;
|
||||
mp_vm_return_kind_t ret_kind = mp_resume(self_in, value, MP_OBJ_NULL, &ret_value);
|
||||
mp_vm_return_kind_t ret_kind = mp_resume(self_in, send_value, throw_value, &ret_value);
|
||||
|
||||
uint32_t out_resolve[PVN];
|
||||
uint32_t out_reject[PVN];
|
||||
|
@ -335,17 +356,19 @@ static mp_obj_t proxy_resume_execute(mp_obj_t self_in, mp_obj_t value, mp_obj_t
|
|||
uint32_t out[PVN];
|
||||
js_then_continue(ref, out_py_resume, out_resolve, out_reject, out);
|
||||
return proxy_convert_js_to_mp_obj_cside(out);
|
||||
} else {
|
||||
// MP_VM_RETURN_EXCEPTION;
|
||||
js_then_reject(out_resolve, out_reject);
|
||||
nlr_raise(ret_value);
|
||||
} else { // ret_kind == MP_VM_RETURN_EXCEPTION;
|
||||
// Pass the exception through as an object to reject the promise (don't raise/throw it).
|
||||
uint32_t out_ret_value[PVN];
|
||||
proxy_convert_mp_to_js_obj_cside(ret_value, out_ret_value);
|
||||
js_then_reject(out_ret_value, out_resolve, out_reject);
|
||||
return mp_const_none;
|
||||
}
|
||||
}
|
||||
|
||||
static mp_obj_t resume_fun(size_t n_args, const mp_obj_t *args) {
|
||||
return proxy_resume_execute(args[0], args[1], args[2], args[3]);
|
||||
return proxy_resume_execute(args[0], args[1], args[2], args[3], args[4]);
|
||||
}
|
||||
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(resume_obj, 4, 4, resume_fun);
|
||||
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(resume_obj, 5, 5, resume_fun);
|
||||
|
||||
void proxy_c_to_js_resume(uint32_t c_ref, uint32_t *args) {
|
||||
nlr_buf_t nlr;
|
||||
|
@ -353,7 +376,7 @@ void proxy_c_to_js_resume(uint32_t c_ref, uint32_t *args) {
|
|||
mp_obj_t obj = proxy_c_get_obj(c_ref);
|
||||
mp_obj_t resolve = proxy_convert_js_to_mp_obj_cside(args + 1 * 3);
|
||||
mp_obj_t reject = proxy_convert_js_to_mp_obj_cside(args + 2 * 3);
|
||||
mp_obj_t ret = proxy_resume_execute(obj, mp_const_none, resolve, reject);
|
||||
mp_obj_t ret = proxy_resume_execute(obj, mp_const_none, mp_const_none, resolve, reject);
|
||||
nlr_pop();
|
||||
return proxy_convert_mp_to_js_obj_cside(ret, args);
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# The asyncio package is built from the standard implementation but with the
|
||||
# core scheduler replaced with a custom scheduler that uses the JavaScript
|
||||
# runtime (with setTimeout an Promise's) to contrtol the scheduling.
|
||||
|
||||
package(
|
||||
"asyncio",
|
||||
(
|
||||
"event.py",
|
||||
"funcs.py",
|
||||
"lock.py",
|
||||
),
|
||||
base_path="$(MPY_DIR)/extmod",
|
||||
opt=3,
|
||||
)
|
||||
|
||||
package(
|
||||
"asyncio",
|
||||
(
|
||||
"__init__.py",
|
||||
"core.py",
|
||||
),
|
||||
base_path="$(PORT_DIR)",
|
||||
opt=3,
|
||||
)
|
|
@ -1,3 +1,5 @@
|
|||
include("$(PORT_DIR)/variants/manifest.py")
|
||||
|
||||
require("abc")
|
||||
require("base64")
|
||||
require("collections")
|
||||
|
|
|
@ -587,6 +587,12 @@
|
|||
/*****************************************************************************/
|
||||
/* Python internal features */
|
||||
|
||||
// Use a special long jump in nlrthumb.c, which may be necessary if nlr.o and
|
||||
// nlrthumb.o are linked far apart from each other.
|
||||
#ifndef MICROPY_NLR_THUMB_USE_LONG_JUMP
|
||||
#define MICROPY_NLR_THUMB_USE_LONG_JUMP (0)
|
||||
#endif
|
||||
|
||||
// Whether to enable import of external modules
|
||||
// When disabled, only importing of built-in modules is supported
|
||||
// When enabled, a port must implement mp_import_stat (among other things)
|
||||
|
|
|
@ -38,6 +38,14 @@
|
|||
|
||||
__attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
|
||||
|
||||
// If you get a linker error here, indicating that a relocation doesn't
|
||||
// fit, try the following (in that order):
|
||||
//
|
||||
// 1. Ensure that nlr.o nlrthumb.o are linked closely together, i.e.
|
||||
// there aren't too many other files between them in the linker list
|
||||
// (PY_CORE_O_BASENAME in py/py.mk)
|
||||
// 2. Set -DMICROPY_NLR_THUMB_USE_LONG_JUMP=1 during the build
|
||||
//
|
||||
__asm volatile (
|
||||
"str r4, [r0, #12] \n" // store r4 into nlr_buf
|
||||
"str r5, [r0, #16] \n" // store r5 into nlr_buf
|
||||
|
@ -71,7 +79,7 @@ __attribute__((naked)) unsigned int nlr_push(nlr_buf_t *nlr) {
|
|||
"str lr, [r0, #8] \n" // store lr into nlr_buf
|
||||
#endif
|
||||
|
||||
#if !defined(__thumb2__)
|
||||
#if MICROPY_NLR_THUMB_USE_LONG_JUMP
|
||||
"ldr r1, nlr_push_tail_var \n"
|
||||
"bx r1 \n" // do the rest in C
|
||||
".align 2 \n"
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// Test asyncio.create_task(), and tasks waiting on a Promise.
|
||||
|
||||
const mp = await (await import(process.argv[2])).loadMicroPython();
|
||||
|
||||
globalThis.p0 = new Promise((resolve, reject) => {
|
||||
resolve(123);
|
||||
});
|
||||
|
||||
globalThis.p1 = new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
console.log("setTimeout resolved");
|
||||
resolve(456);
|
||||
}, 200);
|
||||
});
|
||||
|
||||
mp.runPython(`
|
||||
import js
|
||||
import asyncio
|
||||
|
||||
async def task(id, promise):
|
||||
print("task start", id)
|
||||
print("task await", id, await promise)
|
||||
print("task await", id, await promise)
|
||||
print("task end", id)
|
||||
|
||||
print("start")
|
||||
t1 = asyncio.create_task(task(1, js.p0))
|
||||
t2 = asyncio.create_task(task(2, js.p1))
|
||||
print("t1", t1.done(), t2.done())
|
||||
print("end")
|
||||
`);
|
||||
|
||||
// Wait for p1 to fulfill so t2 can continue.
|
||||
await globalThis.p1;
|
||||
|
||||
// Wait a little longer so t2 can complete.
|
||||
await new Promise((resolve, reject) => {
|
||||
setTimeout(resolve, 10);
|
||||
});
|
||||
|
||||
mp.runPython(`
|
||||
print("restart")
|
||||
print("t1", t1.done(), t2.done())
|
||||
`);
|
|
@ -0,0 +1,14 @@
|
|||
start
|
||||
t1 False False
|
||||
end
|
||||
task start 1
|
||||
task start 2
|
||||
task await 1 123
|
||||
task await 1 123
|
||||
task end 1
|
||||
setTimeout resolved
|
||||
task await 2 456
|
||||
task await 2 456
|
||||
task end 2
|
||||
restart
|
||||
t1 True True
|
|
@ -0,0 +1,25 @@
|
|||
// Test asyncio.sleep(), both at the top level and within a task.
|
||||
|
||||
const mp = await (await import(process.argv[2])).loadMicroPython();
|
||||
|
||||
await mp.runPythonAsync(`
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
print("main start")
|
||||
t0 = time.time()
|
||||
await asyncio.sleep(0.25)
|
||||
dt = time.time() - t0
|
||||
print(0.2 <= dt <= 0.3)
|
||||
|
||||
async def task():
|
||||
print("task start")
|
||||
t0 = time.time()
|
||||
await asyncio.sleep(0.25)
|
||||
dt = time.time() - t0
|
||||
print(0.2 <= dt <= 0.3)
|
||||
print("task end")
|
||||
|
||||
asyncio.create_task(task())
|
||||
print("main end")
|
||||
`);
|
|
@ -0,0 +1,6 @@
|
|||
main start
|
||||
True
|
||||
main end
|
||||
task start
|
||||
True
|
||||
task end
|
|
@ -681,6 +681,17 @@ def run_tests(pyb, tests, args, result_dir, num_threads=1):
|
|||
elif args.target == "webassembly":
|
||||
skip_tests.add("basics/string_format_modulo.py") # can't print nulls to stdout
|
||||
skip_tests.add("basics/string_strip.py") # can't print nulls to stdout
|
||||
skip_tests.add("extmod/asyncio_basic2.py")
|
||||
skip_tests.add("extmod/asyncio_cancel_self.py")
|
||||
skip_tests.add("extmod/asyncio_current_task.py")
|
||||
skip_tests.add("extmod/asyncio_exception.py")
|
||||
skip_tests.add("extmod/asyncio_gather_finished_early.py")
|
||||
skip_tests.add("extmod/asyncio_get_event_loop.py")
|
||||
skip_tests.add("extmod/asyncio_heaplock.py")
|
||||
skip_tests.add("extmod/asyncio_loop_stop.py")
|
||||
skip_tests.add("extmod/asyncio_new_event_loop.py")
|
||||
skip_tests.add("extmod/asyncio_threadsafeflag.py")
|
||||
skip_tests.add("extmod/asyncio_wait_for_fwd.py")
|
||||
skip_tests.add("extmod/binascii_a2b_base64.py")
|
||||
skip_tests.add("extmod/re_stack_overflow.py")
|
||||
skip_tests.add("extmod/time_res.py")
|
||||
|
|
Ładowanie…
Reference in New Issue