From c1fb5b5f9e13ec4aa929b273d6e824184775f9f1 Mon Sep 17 00:00:00 2001 From: Markus Lassila Date: Fri, 14 Nov 2025 13:48:26 +0200 Subject: [PATCH] app: Add automatic data reception for sockets A new AT-command #XRECVCFG can be used to set the automatic data reception for the sockets. When the automatic data reception is activated, the data received for the socket is immediately sent to host as URC. Signed-off-by: Markus Lassila --- app/src/sm_at_host.c | 7 + app/src/sm_at_host.h | 7 + app/src/sm_at_socket.c | 486 ++++++++++++++++++++++++--------- app/src/sm_at_socket.h | 5 + app/src/sm_cmux.c | 4 +- app/src/sm_uart_handler.c | 4 +- doc/app/SOCKET_AT_commands.rst | 291 ++++++++++++++++++-- doc/app/sm_data_mode.rst | 3 +- 8 files changed, 660 insertions(+), 147 deletions(-) diff --git a/app/src/sm_at_host.c b/app/src/sm_at_host.c index 73d28125..311fc38e 100644 --- a/app/src/sm_at_host.c +++ b/app/src/sm_at_host.c @@ -132,6 +132,8 @@ static bool exit_datamode(void) rsp_send("\r\n#XDATAMODE: %d\r\n", datamode_handler_result); datamode_handler_result = 0; + sm_at_socket_notify_datamode_exit(); + LOG_INF("Exit datamode"); ret = true; } @@ -873,6 +875,11 @@ bool in_datamode(void) return (get_sm_mode() == SM_DATA_MODE); } +bool in_at_mode(void) +{ + return (get_sm_mode() == SM_AT_COMMAND_MODE); +} + bool exit_datamode_handler(int result) { bool ret = false; diff --git a/app/src/sm_at_host.h b/app/src/sm_at_host.h index 317bfa37..b48b67f5 100644 --- a/app/src/sm_at_host.h +++ b/app/src/sm_at_host.h @@ -164,6 +164,13 @@ int enter_datamode(sm_datamode_handler_t handler, size_t data_len); */ bool in_datamode(void); +/** + * @brief Check whether Serial Modem AT host is in AT command mode + * + * @retval true if yes, false if no. + */ +bool in_at_mode(void); + /** * @brief Exit the data mode handler * diff --git a/app/src/sm_at_socket.c b/app/src/sm_at_socket.c index eeea21ac..a56c970b 100644 --- a/app/src/sm_at_socket.c +++ b/app/src/sm_at_socket.c @@ -5,6 +5,7 @@ */ #include #include +#include #include #include #include @@ -41,11 +42,18 @@ enum sm_socket_role { AT_SOCKET_ROLE_SERVER }; -/**@brief Socket send modes. */ -enum sm_socket_send_mode { - AT_SOCKET_SEND_MODE_STRING = 0, /* String data in command */ - AT_SOCKET_SEND_MODE_HEXSTRING = 1, /* Hexadecimal string data */ - AT_SOCKET_SEND_MODE_DATA = 2 /* Enter data mode */ +/**@brief Socket modes for send and receive. */ +enum sm_socket_mode { + AT_SOCKET_MODE_UNFORMATTED = 0, /* Text/binary string data */ + AT_SOCKET_MODE_HEX = 1, /* Hexadecimal string data */ + AT_SOCKET_MODE_DATA = 2, /* Enter data mode */ +}; + +/**@brief Socket automatic reception flags. */ +enum sm_socket_adr_flags { + SM_ADR_DISABLE = 0, /* Disable automatic data reception */ + SM_ADR_AT_MODE = 1, /* Enable automatic data reception in AT mode */ + SM_ADR_DATA_MODE = 2 /* Enable automatic data reception in data mode */ }; /**@brief Socket send result modes. */ @@ -54,21 +62,18 @@ enum sm_socket_send_result_mode { AT_SOCKET_SEND_RESULT_NW_ACK_URC = 1 /* URC from network acknowledgment will follow. */ }; -/**@brief Socket receive modes. */ -enum sm_socket_recv_mode { - AT_SOCKET_RECV_MODE_BINARY = 0, /* Receive binary data */ - AT_SOCKET_RECV_MODE_HEXSTRING = 1 /* Receive hexadecimal string data */ -}; - static char udp_url[SM_MAX_URL]; static uint16_t udp_port; struct sm_async_poll { - int events; /* Events to poll for this socket. */ - atomic_t revents; /* Events received for this socket. */ - int update_events; /* Events to update for this socket. */ - bool specific: 1; /* Specific socket to poll. */ - bool disable: 1; /* Poll needs to stay disabled for this socket. */ + uint16_t events; /* Events to poll for this socket. */ + atomic_t revents; /* Events received for this socket. */ + uint16_t delayed_revents; /* Events received for this socket during datamode. */ + uint16_t xapoll_events; /* Events to update for xapoll. */ + uint8_t adr_flags; /* Flags for automatic data reception. */ + bool disable: 1; /* Poll needs to stay disabled for this socket. */ + bool xapoll_specific: 1; /* xapoll specific socket to poll. */ + bool adr_hex: 1; /* Automatic data reception in hex mode. */ }; #define SM_MSG_SEND_ACK 0x2000 @@ -79,16 +84,17 @@ struct sm_send_ntf { }; static struct sm_socket { - int type; /* SOCK_STREAM or SOCK_DGRAM */ - uint16_t role; /* Client or Server */ - sec_tag_t sec_tag; /* Security tag of the credential */ - int family; /* Socket address family */ - int fd; /* Socket descriptor. */ - uint16_t cid; /* PDP Context ID, 0: primary; 1~10: secondary */ - int send_flags; /* Send flags */ - bool send_cb_set; /* Send callback set */ + int type; /* SOCK_STREAM or SOCK_DGRAM */ + uint16_t role; /* Client or Server */ + sec_tag_t sec_tag; /* Security tag of the credential */ + int family; /* Socket address family */ + int fd; /* Socket descriptor. */ + uint16_t cid; /* PDP Context ID, 0: primary; 1~10: secondary */ + int send_flags; /* Send flags */ + bool send_cb_set: 1; /* Send callback set */ + bool connected: 1; /* Connected flag. */ struct sm_async_poll async_poll; /* Async poll info. */ - struct sm_send_ntf send_ntf; /* Send notification info. */ + struct sm_send_ntf send_ntf; /* Send notification info. */ } socks[SM_MAX_SOCKET_COUNT]; static struct sm_socket *datamode_sock; /* Socket for data mode */ @@ -96,27 +102,36 @@ static char hex_data[1400 + 1]; /* Buffer for hex data conversion */ static struct async_poll_ctx { struct k_work poll_work; /* Work to send poll URCs. */ - int poll_events; /* Events to poll for async poll. */ - bool poll_all: 1; /* Poll all the sockets. */ - bool poll_running: 1; /* Async poll is running. */ + uint16_t xapoll_events; /* Events to poll for async poll. */ + uint8_t adr_flags; /* Auto reception flags for all sockets. */ + bool xapoll_all: 1; /* Poll all the sockets. */ + bool xapoll_running: 1; /* Async poll is running. */ + bool adr_hex: 1; /* Auto reception hex mode for all sockets. */ } poll_ctx; /* forward declarations */ #define SOCKET_SEND_TMO_SEC 30 +static int do_recv(struct sm_socket *sock, int timeout, int flags, + enum sm_socket_mode mode, size_t data_len); + +static int do_recvfrom(struct sm_socket *sock, int timeout, int flags, + enum sm_socket_mode mode, size_t data_len); + static void init_socket(struct sm_socket *socket) { if (socket == NULL) { return; } - - socket->family = AF_UNSPEC; - socket->sec_tag = SEC_TAG_TLS_INVALID; + socket->type = 0; socket->role = AT_SOCKET_ROLE_CLIENT; + socket->sec_tag = SEC_TAG_TLS_INVALID; + socket->family = AF_UNSPEC; socket->fd = INVALID_SOCKET; socket->cid = 0; socket->send_flags = 0; socket->send_cb_set = false; + socket->connected = false; socket->send_ntf = (struct sm_send_ntf){0}; socket->async_poll = (struct sm_async_poll){0}; } @@ -182,7 +197,7 @@ static void poll_cb(struct nrf_pollfd *pollfd) } -static int set_so_poll_cb(struct sm_socket *socket, short events) +static int set_so_poll_cb(struct sm_socket *socket, uint16_t events) { int err; @@ -207,65 +222,168 @@ static int set_so_poll_cb(struct sm_socket *socket, short events) return 0; } -static int clear_so_poll_cb(struct sm_socket *socket) +static void auto_reception(struct sm_socket *sock) { - int err; + int err = 0; - if (socket == NULL) { + if (sock == NULL) { + return; + } + if (sock->connected || sock->type == NRF_SOCK_RAW) { + err = do_recv(sock, 0, NRF_MSG_DONTWAIT, + sock->async_poll.adr_hex ? AT_SOCKET_MODE_HEX + : AT_SOCKET_MODE_UNFORMATTED, + sizeof(sm_data_buf)); + } else { + err = do_recvfrom(sock, 0, NRF_MSG_DONTWAIT, + sock->async_poll.adr_hex ? AT_SOCKET_MODE_HEX + : AT_SOCKET_MODE_UNFORMATTED, + sizeof(sm_data_buf)); + } + if (err) { + LOG_ERR("auto_reception() error: %d", err); + return; + } + if (!in_datamode()) { + /* after the data. */ + sm_at_send_str("\r\n"); + } +} + +static int update_poll_events(struct sm_socket *sock, uint16_t events, bool update_xapoll) +{ + int ret; + + if (sock == NULL) { return -EINVAL; } + if (sock->async_poll.disable) { + return 0; + } - err = nrf_setsockopt(socket->fd, NRF_SOL_SOCKET, NRF_SO_POLLCB, NULL, 0); - if (err < 0) { - LOG_ERR("nrf_setsockopt(%d,%d,%d) error: %d", socket->fd, NRF_SOL_SOCKET, - NRF_SO_POLLCB, -errno); - return -errno; + if (update_xapoll && poll_ctx.xapoll_running && + (poll_ctx.xapoll_all || sock->async_poll.xapoll_specific)) { + /* Update expected xapoll events. */ + sock->async_poll.xapoll_events |= (poll_ctx.xapoll_events & events); } + + sock->async_poll.events |= events; + ret = set_so_poll_cb(sock, sock->async_poll.events); + if (ret) { + LOG_ERR("Failed to update poll events %d for socket %d: %d", + sock->async_poll.events, sock->fd, ret); + return ret; + } + + LOG_DBG("Updated poll events %d for socket %d", sock->async_poll.events, sock->fd); return 0; } static void poll_work_fn(struct k_work *work) { - if (poll_ctx.poll_running == false) { - return; - } + bool at_mode = in_at_mode(); + bool data_mode = in_datamode(); for (int i = 0; i < SM_MAX_SOCKET_COUNT; i++) { - if (socks[i].fd == INVALID_SOCKET) { + struct sm_socket *sock = &socks[i]; + + if (sock->fd == INVALID_SOCKET) { continue; } - short revents = atomic_clear(&socks[i].async_poll.revents); + uint16_t revents = atomic_clear(&sock->async_poll.revents); - if (revents) { - urc_send("\r\n#XAPOLL: %d,%d\r\n", socks[i].fd, revents); - if (revents & (NRF_POLLERR | NRF_POLLNVAL | NRF_POLLHUP)) { - /* Remove socket from poll until closed. */ - socks[i].async_poll.disable = true; - } else { - /* Remove POLLIN/POLLOUT from poll, until AT-operation. */ - socks[i].async_poll.events &= ~revents; + LOG_DBG("Socket %d poll revents 0x%x", sock->fd, revents); + + /* Store events for later processing when not in AT mode. */ + if (!at_mode) { + sock->async_poll.delayed_revents |= revents; + LOG_DBG("Socket %d delayed revents 0x%x", sock->fd, + sock->async_poll.delayed_revents); + } + + /* Do not process any socket events if not in correct mode. */ + if (!at_mode && !data_mode) { + continue; + } + /* In data mode, skip non-datamode sockets. */ + if (data_mode && sock != datamode_sock) { + continue; + } + /* Transitioning back to AT mode: re-enable delayed events */ + if (at_mode && sock->async_poll.delayed_revents) { + /* We have received events when not in AT-command mode. + * Re-enable all the events to see which ones are still valid. + */ + sock->async_poll.disable = false; + update_poll_events(sock, sock->async_poll.delayed_revents | revents, true); + sock->async_poll.delayed_revents = 0; + continue; + } + + assert(at_mode || (data_mode && sock == datamode_sock)); + + /* Send #XAPOLL URC for poll events. */ + if (!data_mode && poll_ctx.xapoll_running) { + uint16_t xapoll_events = revents & (sock->async_poll.xapoll_events); + + /* Do not send URC for the same events twice, unless send/recv is done. */ + sock->async_poll.xapoll_events &= ~xapoll_events; + if (xapoll_events) { + rsp_send("\r\n#XAPOLL: %d,%d\r\n", sock->fd, xapoll_events); + } + } + + /* Remove POLLOUT from poll, until send is done. */ + if (revents & NRF_POLLOUT) { + sock->async_poll.events &= ~NRF_POLLOUT; + } + + /* Prevent further poll activations for socket. */ + if (revents & (NRF_POLLERR | NRF_POLLNVAL | NRF_POLLHUP)) { + sock->async_poll.disable = true; + } + + /* Remove POLLIN from poll, until recv is done. */ + if (revents & NRF_POLLIN) { + sock->async_poll.events &= ~NRF_POLLIN; + + /* Automatic data reception may reactivate POLLIN. */ + if (((at_mode && (sock->async_poll.adr_flags & SM_ADR_AT_MODE)) || + (data_mode && (sock->async_poll.adr_flags & SM_ADR_DATA_MODE)))) { + auto_reception(sock); } } - if (socks[i].async_poll.disable == false && - (revents != 0 || socks[i].async_poll.update_events != 0)) { - /* Re-register for remaining events */ - socks[i].async_poll.events |= socks[i].async_poll.update_events; - socks[i].async_poll.update_events = 0; - set_so_poll_cb(&socks[i], socks[i].async_poll.events); + LOG_DBG("Socket %d, revents %d, disable %d, events %d, xapoll_events %d", + sock->fd, revents, sock->async_poll.disable, + sock->async_poll.events, sock->async_poll.xapoll_events); + + /* Re-register for remaining events */ + update_poll_events(sock, 0, false); + + /* Exit data mode handler on socket error */ + if (data_mode) { + int err = 0; + + if (revents & NRF_POLLERR) { + err = -EIO; + } else if (revents & NRF_POLLNVAL) { + err = -ENETDOWN; + } else if (revents & NRF_POLLHUP) { + err = -ECONNRESET; + } + if (err) { + exit_datamode_handler(err); + } } } } -static void delegate_poll_event(struct sm_socket *s, int events) +void sm_at_socket_notify_datamode_exit(void) { - if (poll_ctx.poll_running && (poll_ctx.poll_all || s->async_poll.specific)) { - LOG_DBG("Delegate poll events %d for socket %d", events, s->fd); - s->async_poll.update_events |= (poll_ctx.poll_events & events); - - k_work_submit_to_queue(&sm_work_q, &poll_ctx.poll_work); - } + /* Update events that were received during data mode. */ + k_work_submit_to_queue(&sm_work_q, &poll_ctx.poll_work); } static void send_cb_fn(struct k_work *work) @@ -285,7 +403,7 @@ static void send_cb_fn(struct k_work *work) status = -1; } urc_send("\r\n#XSENDNTF: %d,%d,%d\r\n", socks[i].fd, status, bytes_sent); - delegate_poll_event(&socks[i], NRF_POLLOUT); + update_poll_events(&socks[i], NRF_POLLOUT, true); } } } @@ -412,7 +530,11 @@ static int do_socket_open(struct sm_socket *sock) rsp_send("\r\n#XSOCKET: %d,%d,%d\r\n", sock->fd, sock->type, proto); - delegate_poll_event(sock, NRF_POLLIN | NRF_POLLOUT); + /* Update poll events for xapoll and automatic data reception */ + sock->async_poll.adr_flags = poll_ctx.adr_flags; + sock->async_poll.adr_hex = poll_ctx.adr_hex; + update_poll_events( + sock, NRF_POLLIN | NRF_POLLOUT | NRF_POLLERR | NRF_POLLHUP | NRF_POLLNVAL, true); return 0; @@ -486,7 +608,11 @@ static int do_secure_socket_open(struct sm_socket *sock, int peer_verify) rsp_send("\r\n#XSSOCKET: %d,%d,%d\r\n", sock->fd, sock->type, proto); - delegate_poll_event(sock, NRF_POLLIN | NRF_POLLOUT); + /* Update poll events for xapoll and automatic data reception */ + sock->async_poll.adr_flags = poll_ctx.adr_flags; + sock->async_poll.adr_hex = poll_ctx.adr_hex; + update_poll_events( + sock, NRF_POLLIN | NRF_POLLOUT | NRF_POLLERR | NRF_POLLHUP | NRF_POLLNVAL, true); return 0; @@ -814,6 +940,7 @@ static int do_connect(struct sm_socket *sock, const char *url, uint16_t port) return -errno; } + sock->connected = true; rsp_send("\r\n#XCONNECT: %d,1\r\n", sock->fd); return ret; @@ -859,9 +986,9 @@ static int do_send(struct sm_socket *sock, const uint8_t *data, int len, int fla send_ntf ? AT_SOCKET_SEND_RESULT_NW_ACK_URC : AT_SOCKET_SEND_RESULT_DEFAULT, sent); - if (!send_ntf) { - delegate_poll_event(sock, NRF_POLLOUT); - } + } + if (!send_ntf) { + update_poll_events(sock, NRF_POLLOUT, true); } return sent > 0 ? sent : ret; @@ -890,8 +1017,8 @@ static int data_send_hex(struct sm_socket *sock, const uint8_t *buf, int recv_le return 0; } -static int do_recv(struct sm_socket *sock, int timeout, int flags, enum sm_socket_recv_mode mode, - size_t data_len) +static int do_recv(struct sm_socket *sock, int timeout, int flags, + enum sm_socket_mode mode, size_t data_len) { int ret; int sockfd = sock->fd; @@ -917,9 +1044,11 @@ static int do_recv(struct sm_socket *sock, int timeout, int flags, enum sm_socke if (ret == 0) { LOG_WRN("nrf_recv() return 0"); } else { - rsp_send("\r\n#XRECV: %d,%d,%d\r\n", sock->fd, mode, ret); + if (!in_datamode()) { + rsp_send("\r\n#XRECV: %d,%d,%d\r\n", sock->fd, mode, ret); + } - if (mode == AT_SOCKET_RECV_MODE_HEXSTRING) { + if (mode == AT_SOCKET_MODE_HEX) { ret = data_send_hex(sock, sm_data_buf, ret); if (ret) { return ret; @@ -929,7 +1058,7 @@ static int do_recv(struct sm_socket *sock, int timeout, int flags, enum sm_socke } ret = 0; - delegate_poll_event(sock, NRF_POLLIN); + update_poll_events(sock, NRF_POLLIN, true); } return ret; @@ -992,16 +1121,16 @@ static int do_sendto(struct sm_socket *sock, const char *url, uint16_t port, con send_ntf ? AT_SOCKET_SEND_RESULT_NW_ACK_URC : AT_SOCKET_SEND_RESULT_DEFAULT, sent); - if (!send_ntf) { - delegate_poll_event(sock, NRF_POLLOUT); - } + } + if (!send_ntf) { + update_poll_events(sock, NRF_POLLOUT, true); } return sent > 0 ? sent : ret; } static int do_recvfrom(struct sm_socket *sock, int timeout, int flags, - enum sm_socket_recv_mode mode, size_t data_len) + enum sm_socket_mode mode, size_t data_len) { int ret; struct sockaddr remote; @@ -1027,14 +1156,16 @@ static int do_recvfrom(struct sm_socket *sock, int timeout, int flags, if (ret == 0) { LOG_WRN("nrf_recvfrom() return 0"); } else { - char peer_addr[NRF_INET6_ADDRSTRLEN] = {0}; - uint16_t peer_port = 0; + if (!in_datamode()) { + char peer_addr[NRF_INET6_ADDRSTRLEN] = {0}; + uint16_t peer_port = 0; - util_get_peer_addr(&remote, peer_addr, &peer_port); - rsp_send("\r\n#XRECVFROM: %d,%d,%d,\"%s\",%d\r\n", sock->fd, mode, ret, peer_addr, - peer_port); + util_get_peer_addr(&remote, peer_addr, &peer_port); + rsp_send("\r\n#XRECVFROM: %d,%d,%d,\"%s\",%d\r\n", sock->fd, mode, + ret, peer_addr, peer_port); + } - if (mode == AT_SOCKET_RECV_MODE_HEXSTRING) { + if (mode == AT_SOCKET_MODE_HEX) { ret = data_send_hex(sock, sm_data_buf, ret); if (ret) { return ret; @@ -1043,7 +1174,7 @@ static int do_recvfrom(struct sm_socket *sock, int timeout, int flags, data_send(sm_data_buf, ret); } - delegate_poll_event(sock, ZSOCK_POLLIN); + update_poll_events(sock, NRF_POLLIN, true); } return 0; @@ -1066,13 +1197,21 @@ static int socket_datamode_callback(uint8_t op, const uint8_t *data, int len, ui } else { ret = do_send(datamode_sock, data, len, datamode_sock->send_flags); } - LOG_INF("datamode send: %d", ret); + if (ret == -EAGAIN || ret == -ETIMEDOUT) { + LOG_WRN("Send failed: %d", ret); + return ret; + } else if (ret < 0) { + LOG_ERR("Send failed: %d", ret); + exit_datamode_handler(ret); + return ret; + } } } else if (op == DATAMODE_EXIT) { LOG_DBG("datamode exit"); memset(udp_url, 0, sizeof(udp_url)); - if ((datamode_sock->send_flags & SM_MSG_SEND_ACK) == 0) { - delegate_poll_event(datamode_sock, NRF_POLLOUT); + if ((flags & SM_DATAMODE_FLAGS_EXIT_HANDLER) != 0) { + /* Datamode exited unexpectedly. */ + rsp_send(CONFIG_SM_DATAMODE_TERMINATOR); } datamode_sock = NULL; } @@ -1549,7 +1688,7 @@ static int handle_at_send(enum at_parser_cmd_type cmd_type, struct at_parser *pa if (err) { return err; } - if (mode == AT_SOCKET_SEND_MODE_STRING || mode == AT_SOCKET_SEND_MODE_HEXSTRING) { + if (mode == AT_SOCKET_MODE_UNFORMATTED || mode == AT_SOCKET_MODE_HEX) { if (param_count > 4) { err = at_parser_string_ptr_get(parser, 4, &str_ptr, &size); if (err) { @@ -1560,7 +1699,7 @@ static int handle_at_send(enum at_parser_cmd_type cmd_type, struct at_parser *pa } /* Convert hex string to binary data */ - if (mode == AT_SOCKET_SEND_MODE_HEXSTRING) { + if (mode == AT_SOCKET_MODE_HEX) { err = sm_util_atoh(str_ptr, size, hex_data, sizeof(hex_data)); if (err < 0) { LOG_ERR("Failed to convert hex string to binary data"); @@ -1576,7 +1715,7 @@ static int handle_at_send(enum at_parser_cmd_type cmd_type, struct at_parser *pa } else { err = err < 0 ? err : -EAGAIN; } - } else if (mode == AT_SOCKET_SEND_MODE_DATA) { + } else if (mode == AT_SOCKET_MODE_DATA) { if (param_count > 4) { err = at_parser_num_get(parser, 4, &data_len); if (err) { @@ -1585,6 +1724,9 @@ static int handle_at_send(enum at_parser_cmd_type cmd_type, struct at_parser *pa } datamode_sock = sock; err = enter_datamode(socket_datamode_callback, data_len); + if (!err && sock->async_poll.adr_flags & SM_ADR_DATA_MODE) { + update_poll_events(sock, NRF_POLLIN, false); + } } else { return -EINVAL; } @@ -1623,7 +1765,7 @@ static int handle_at_recv(enum at_parser_cmd_type cmd_type, struct at_parser *pa if (err) { return err; } - if (mode != AT_SOCKET_RECV_MODE_BINARY && mode != AT_SOCKET_RECV_MODE_HEXSTRING) { + if (mode != AT_SOCKET_MODE_UNFORMATTED && mode != AT_SOCKET_MODE_HEX) { return -EINVAL; } err = at_parser_num_get(parser, 3, &flags); @@ -1694,7 +1836,7 @@ static int handle_at_sendto(enum at_parser_cmd_type cmd_type, struct at_parser * if (err) { return err; } - if (mode == AT_SOCKET_SEND_MODE_STRING || mode == AT_SOCKET_SEND_MODE_HEXSTRING) { + if (mode == AT_SOCKET_MODE_UNFORMATTED || mode == AT_SOCKET_MODE_HEX) { if (param_count > 6) { err = at_parser_string_ptr_get(parser, 6, &str_ptr, &size); if (err) { @@ -1705,7 +1847,7 @@ static int handle_at_sendto(enum at_parser_cmd_type cmd_type, struct at_parser * } /* Convert hex string to binary data */ - if (mode == AT_SOCKET_SEND_MODE_HEXSTRING) { + if (mode == AT_SOCKET_MODE_HEX) { err = sm_util_atoh(str_ptr, size, hex_data, sizeof(hex_data)); if (err < 0) { LOG_ERR("Failed to convert hex string to binary data"); @@ -1722,7 +1864,7 @@ static int handle_at_sendto(enum at_parser_cmd_type cmd_type, struct at_parser * err = err < 0 ? err : -EAGAIN; } memset(udp_url, 0, sizeof(udp_url)); - } else if (mode == AT_SOCKET_SEND_MODE_DATA) { + } else if (mode == AT_SOCKET_MODE_DATA) { if (param_count > 6) { err = at_parser_num_get(parser, 6, &data_len); if (err) { @@ -1731,6 +1873,9 @@ static int handle_at_sendto(enum at_parser_cmd_type cmd_type, struct at_parser * } datamode_sock = sock; err = enter_datamode(socket_datamode_callback, data_len); + if (!err && sock->async_poll.adr_flags & SM_ADR_DATA_MODE) { + update_poll_events(sock, NRF_POLLIN, false); + } } else { return -EINVAL; } @@ -1769,7 +1914,7 @@ static int handle_at_recvfrom(enum at_parser_cmd_type cmd_type, struct at_parser if (err) { return err; } - if (mode != AT_SOCKET_RECV_MODE_BINARY && mode != AT_SOCKET_RECV_MODE_HEXSTRING) { + if (mode != AT_SOCKET_MODE_UNFORMATTED && mode != AT_SOCKET_MODE_HEX) { return -EINVAL; } err = at_parser_num_get(parser, 3, &flags); @@ -1882,15 +2027,15 @@ static int handle_at_getaddrinfo(enum at_parser_cmd_type cmd_type, struct at_par static int apoll_stop(void) { - poll_ctx.poll_all = false; - poll_ctx.poll_events = 0; - poll_ctx.poll_running = false; + poll_ctx.xapoll_running = false; + poll_ctx.xapoll_all = false; + poll_ctx.xapoll_events = 0; for (int i = 0; i < SM_MAX_SOCKET_COUNT; i++) { if (socks[i].fd != INVALID_SOCKET) { - clear_so_poll_cb(&socks[i]); + socks[i].async_poll.xapoll_events = 0; + socks[i].async_poll.xapoll_specific = false; } - socks[i].async_poll = (struct sm_async_poll){0}; } return 0; @@ -1900,12 +2045,12 @@ static void format_apoll_read_response(char *response, size_t size) { int offset = 0; - offset = snprintf(response, size, "\r\n#XAPOLL: %d,%d", poll_ctx.poll_running, - poll_ctx.poll_events); + offset = snprintf(response, size, "\r\n#XAPOLL: %d,%d", poll_ctx.xapoll_running, + (poll_ctx.xapoll_events & ~(NRF_POLLERR | NRF_POLLHUP | NRF_POLLNVAL))); for (int i = 0; i < SM_MAX_SOCKET_COUNT && offset < size; i++) { if (socks[i].fd != INVALID_SOCKET && - (poll_ctx.poll_all || socks[i].async_poll.specific)) { + (poll_ctx.xapoll_all || socks[i].async_poll.xapoll_specific)) { offset += snprintf(&response[offset], size - offset, ",%d", socks[i].fd); } } @@ -1916,15 +2061,21 @@ static void format_apoll_read_response(char *response, size_t size) snprintf(&response[offset], size - offset, "\r\n"); } -static void set_apoll_all_sockets(void) +static int set_apoll_all_sockets(void) { - poll_ctx.poll_running = true; - poll_ctx.poll_all = true; + int ret; + + poll_ctx.xapoll_running = true; + poll_ctx.xapoll_all = true; for (int i = 0; i < SM_MAX_SOCKET_COUNT; i++) { if (socks[i].fd != INVALID_SOCKET) { - socks[i].async_poll.update_events = poll_ctx.poll_events; + ret = update_poll_events(&socks[i], poll_ctx.xapoll_events, true); + if (ret) { + return ret; + } } } + return 0; } static int set_apoll_specific_sockets(struct at_parser *parser, uint32_t param_count) @@ -1933,15 +2084,15 @@ static int set_apoll_specific_sockets(struct at_parser *parser, uint32_t param_c int err; bool socket_found; - poll_ctx.poll_running = true; - poll_ctx.poll_all = false; + poll_ctx.xapoll_running = true; + poll_ctx.xapoll_all = false; /* Clear previously set values. */ for (int i = 0; i < SM_MAX_SOCKET_COUNT; i++) { if (socks[i].fd != INVALID_SOCKET) { - clear_so_poll_cb(&socks[i]); + socks[i].async_poll.xapoll_specific = false; + socks[i].async_poll.xapoll_events = 0; } - socks[i].async_poll = (struct sm_async_poll){0}; } /* Go through all the given handles. */ @@ -1955,8 +2106,11 @@ static int set_apoll_specific_sockets(struct at_parser *parser, uint32_t param_c /* Match given handles to socket handles. */ for (int j = 0; j < SM_MAX_SOCKET_COUNT; j++) { if (socks[j].fd == handle) { - socks[j].async_poll.specific = true; - socks[j].async_poll.update_events = poll_ctx.poll_events; + socks[j].async_poll.xapoll_specific = true; + err = update_poll_events(&socks[j], poll_ctx.xapoll_events, true); + if (err) { + return err; + } socket_found = true; break; } @@ -1992,24 +2146,28 @@ static int handle_at_apoll(enum at_parser_cmd_type cmd_type, struct at_parser *p return apoll_stop(); } - /* APOLL START */ - err = at_parser_num_get(parser, 2, &poll_ctx.poll_events); + /* op == AT_ASYNCPOLL_START */ + err = at_parser_num_get(parser, 2, &poll_ctx.xapoll_events); if (err) { return err; } - if (poll_ctx.poll_events & ~(NRF_POLLIN | NRF_POLLOUT)) { - LOG_ERR("Invalid poll events: 0x%02x", poll_ctx.poll_events); + if (poll_ctx.xapoll_events & ~(NRF_POLLIN | NRF_POLLOUT)) { + LOG_ERR("Invalid poll events: 0x%02x", poll_ctx.xapoll_events); return -EINVAL; } + /* Libmodem always returns these. */ + poll_ctx.xapoll_events |= NRF_POLLERR | NRF_POLLHUP | NRF_POLLNVAL; if (param_count == 3) { - set_apoll_all_sockets(); + err = set_apoll_all_sockets(); + if (err) { + return err; + } } else { err = set_apoll_specific_sockets(parser, param_count); if (err) { return err; } } - poll_work_fn(NULL); break; case AT_PARSER_CMD_TYPE_READ: @@ -2033,6 +2191,88 @@ static int handle_at_apoll(enum at_parser_cmd_type cmd_type, struct at_parser *p return err; } +SM_AT_CMD_CUSTOM(xrecvcfg, "AT#XRECVCFG", handle_at_recvcfg); +static int handle_at_recvcfg(enum at_parser_cmd_type cmd_type, struct at_parser *parser, + uint32_t param_count) +{ + int err = -EINVAL; + int fd = -1; + uint16_t flags; + uint16_t hex_mode = 0; + struct sm_socket *sock = NULL; + + switch (cmd_type) { + case AT_PARSER_CMD_TYPE_SET: + err = at_parser_num_get(parser, 1, &fd); + if (err && err != -ENODATA) { + return err; + } + if (fd != -1) { + sock = find_socket(fd); + if (sock == NULL) { + return -EINVAL; + } + } + err = at_parser_num_get(parser, 2, &flags); + if (err) { + return err; + } + if (param_count > 3) { + err = at_parser_num_get(parser, 3, &hex_mode); + if (err) { + return err; + } + } + if ((flags & SM_ADR_DATA_MODE) && hex_mode) { + LOG_ERR("Hex mode with data mode is not supported."); + return -EINVAL; + } + if (sock) { + sock->async_poll.adr_flags = flags; + sock->async_poll.adr_hex = hex_mode != 0; + err = update_poll_events(sock, NRF_POLLIN, false); + } else { + /* Apply to all sockets */ + poll_ctx.adr_flags = flags; + poll_ctx.adr_hex = hex_mode != 0; + for (int i = 0; i < SM_MAX_SOCKET_COUNT; i++) { + if (socks[i].fd != INVALID_SOCKET) { + socks[i].async_poll.adr_flags = poll_ctx.adr_flags; + socks[i].async_poll.adr_hex = poll_ctx.adr_hex; + err = update_poll_events(&socks[i], NRF_POLLIN, false); + if (err) { + return err; + } + } + } + } + break; + + case AT_PARSER_CMD_TYPE_READ: + for (int i = 0; i < SM_MAX_SOCKET_COUNT; i++) { + if (socks[i].fd != INVALID_SOCKET && socks[i].async_poll.adr_flags) { + rsp_send("\r\n#XRECVCFG: %d,%d,%d\r\n", socks[i].fd, + socks[i].async_poll.adr_flags, + socks[i].async_poll.adr_hex); + } + } + err = 0; + break; + + case AT_PARSER_CMD_TYPE_TEST: + rsp_send("\r\n#XRECVCFG: ,(%d,%d,%d,%d),(%d,%d)\r\n", SM_ADR_DISABLE, + SM_ADR_AT_MODE, SM_ADR_DATA_MODE, SM_ADR_AT_MODE | SM_ADR_DATA_MODE, + AT_SOCKET_MODE_UNFORMATTED, AT_SOCKET_MODE_HEX); + err = 0; + break; + + default: + break; + } + + return err; +} + /**@brief API to initialize Socket AT commands handler */ int sm_at_socket_init(void) diff --git a/app/src/sm_at_socket.h b/app/src/sm_at_socket.h index 9382cfab..f73e2d1d 100644 --- a/app/src/sm_at_socket.h +++ b/app/src/sm_at_socket.h @@ -13,6 +13,11 @@ * @{ */ +/** + * @brief Notify socket AT command parser that the data mode has been exited. + */ +void sm_at_socket_notify_datamode_exit(void); + /** * @brief Initialize socket AT command parser. * diff --git a/app/src/sm_cmux.c b/app/src/sm_cmux.c index 64ab6da6..ab128a01 100644 --- a/app/src/sm_cmux.c +++ b/app/src/sm_cmux.c @@ -213,8 +213,8 @@ static void nonblock_tx_work_fn(struct k_work *work) LOG_ERR("No URC context"); return; } - if (in_datamode()) { - /* Do not send URCs in datamode. */ + if (!in_at_mode()) { + /* Send URCs only in AT mode. */ return; } diff --git a/app/src/sm_uart_handler.c b/app/src/sm_uart_handler.c index e1465579..3988bcfd 100644 --- a/app/src/sm_uart_handler.c +++ b/app/src/sm_uart_handler.c @@ -447,8 +447,8 @@ static void tx_write_nonblock_fn(struct k_work *) return; } - if (in_datamode()) { - /* Do not send URCs in datamode. */ + if (!in_at_mode()) { + /* Send URCs only in AT mode. */ return; } diff --git a/doc/app/SOCKET_AT_commands.rst b/doc/app/SOCKET_AT_commands.rst index 7730468b..280a0e08 100644 --- a/doc/app/SOCKET_AT_commands.rst +++ b/doc/app/SOCKET_AT_commands.rst @@ -138,7 +138,7 @@ Response syntax It represents ``cid`` in the ``+CGDCONT`` command. Example -~~~~~~~~ +~~~~~~~ :: @@ -166,7 +166,7 @@ Response syntax #XSOCKET: ,,,, Example -~~~~~~~~ +~~~~~~~ :: @@ -302,7 +302,7 @@ Response syntax It represents ``cid`` in the ``+CGDCONT`` command. Example -~~~~~~~~ +~~~~~~~ :: @@ -330,7 +330,7 @@ Response syntax #XSSOCKET: ,,,,,, Example -~~~~~~~~ +~~~~~~~ :: @@ -534,7 +534,7 @@ Response syntax #XSOCKETOPT: ,,, Example -~~~~~~~~ +~~~~~~~ :: @@ -621,7 +621,7 @@ See `nRF socket options `_ for explanation of the supporte Example -~~~~~~~~ +~~~~~~~ :: @@ -653,7 +653,7 @@ Response syntax #XSSOCKETOPT: ,,, Example -~~~~~~~~ +~~~~~~~ :: @@ -687,7 +687,7 @@ Syntax It represents the specific port to use for binding the socket. Example -~~~~~~~~ +~~~~~~~ :: @@ -878,7 +878,7 @@ For network acknowledged sends (when the ``8192`` flag is used), an unsolicited Example -~~~~~~~~ +~~~~~~~ :: @@ -952,6 +952,8 @@ Syntax Response syntax ~~~~~~~~~~~~~~~ +.. sm_recv_response_start + :: #XRECV: ,, @@ -961,13 +963,15 @@ Response syntax * The ```` value is an integer indicating the receive mode used. -* The ```` value is a string that contains the data being received. - * The ```` value is an integer that represents the actual number of bytes received. In case of hex string mode, it represents the number of bytes before conversion to hexadecimal format. +* The ```` value is a string that contains the data being received. + +.. sm_recv_response_end + Example -~~~~~~~~ +~~~~~~~ :: @@ -1096,7 +1100,7 @@ For network acknowledged sends (when the ``8192`` flag is used), an unsolicited * The ```` value is an integer indicating the size of the data sent. Example -~~~~~~~~ +~~~~~~~ :: @@ -1164,6 +1168,8 @@ Syntax Response syntax ~~~~~~~~~~~~~~~ +.. sm_recvfrom_response_start + :: #XRECVFROM: ,,,"", @@ -1173,8 +1179,6 @@ Response syntax * The ```` value is an integer indicating the receive mode used. -* The ```` value is a string that contains the data being received. - * The ```` value is an integer that represents the actual number of bytes received. In case of hex string mode, it represents the number of bytes before conversion to hexadecimal format. @@ -1182,8 +1186,12 @@ Response syntax * The ```` value is an integer that represents the UDP port of the remote peer. +* The ```` value is a string that contains the data being received. + +.. sm_recvfrom_response_end + Example -~~~~~~~~ +~~~~~~~ :: @@ -1401,7 +1409,7 @@ Response syntax * The ```` values return the socket handles that are being polled. Example -~~~~~~~~ +~~~~~~~ :: @@ -1442,7 +1450,7 @@ Response syntax #XAPOLL: ,,,,... Example -~~~~~~~~ +~~~~~~~ :: @@ -1453,6 +1461,251 @@ Example OK +Configure socket receive #XRECVCFG +================================== + +The socket receive configuration command allows you to configure the following aspects of a socket: + +* Automatic data reception +* Automatic data reception in hex format + +Set command +----------- + +The set command allows you to configure the socket receive configuration of all sockets or a specific socket. + +Syntax +~~~~~~ + +:: + + AT#XRECVCFG=[],[,] + +* The ```` parameter is an integer that identifies the socket handle. + If omitted, the command applies to all opened sockets, whether already open or opened in the future. +* The ```` parameter is an integer that specifies the automatic reception flags. + It can be a combination of the following values summed up: + + * ``0`` - No automatic data reception. + * ``1`` - Automatic data reception in AT-command mode. + * ``2`` - Automatic data reception in data mode. +* The ```` parameter is an integer that specifies the hex format for automatically received data. + It applies only when automatic data reception is enabled. + It can be one of the following values: + + * ``0`` - Data is received in binary format (default). + * ``1`` - Data is received in hex string format (supported only in AT-command mode). + +Response syntax +~~~~~~~~~~~~~~~ + +When in the AT-command mode and the automatic data reception is enabled, |SM| sends ``#XRECV`` and ``#XRECVFROM`` responses as URC notifications when data is received. +``#XRECV`` is used for TCP, raw, and connected UDP sockets, while ``#XRECVFROM`` is used for unconnected UDP sockets. + +.. include:: SOCKET_AT_commands.rst + :start-after: sm_recv_response_start + :end-before: sm_recv_response_end + +.. include:: SOCKET_AT_commands.rst + :start-after: sm_recvfrom_response_start + :end-before: sm_recvfrom_response_end + +.. note:: + ```` is sent after ````. + This differs from the standard ``#XRECV`` and ``#XRECVFROM`` responses where ```` is sent before ``OK``. + +When in data mode and the automatic data reception is enabled, |SM| sends the received data as is, without any additional headers or formatting. + +Example +~~~~~~~ + +:: + + // Enable automatic data reception in AT-command mode in binary format for all sockets. + AT#XRECVCFG=,1,0 + + OK + AT#XSOCKET=1,1,0 + + #XSOCKET: 0,1,6 + + OK + AT#XCONNECT=0,"test.server.com",1234 + + #XCONNECT: 0,1 + + OK + // Send data to the test server, which will echo it back. + AT#XSEND=0,0,0,"Test" + + #XSEND: 0,0,4 + + OK + // Data is automatically received. + #XRECV: 0,0,4 + Test + // Enable automatic data reception in AT-command mode in hex string format for socket 0. + AT#XRECVCFG=0,1,1 + + OK + // Send data to the test server, which will echo it back. + AT#XSEND=0,0,0,"Test hex" + + #XSEND: 0,0,8 + + OK + // Data is automatically received in hex string format. + #XRECV: 0,1,8 + 5465737420686578 + // Enable automatic reception of data in data mode. + AT#XRECVCFG=0,2,0 + + OK + // Enter data mode and send data to the test server, which will echo it back. + AT#XSEND=0,2,0 + + OK + DATA TEST + DATA TEST + +++ + #XDATAMODE: 0 + + // Create a new UDP socket. Data reception in AT-command mode in binary format is enabled for it. + AT#XSOCKET=1,2,0 + + #XSOCKET: 1,2,17 + + OK + // Send data with unconnected UDP socket to the test server, which will echo it back with a delay. + AT#XSENDTO=1,0,0,"test.server.com",1235,"Delayed UDP data" + + #XSENDTO: 1,0,16 + + OK + // Enter data mode with socket 0. + AT#XSEND=0,2,0 + + OK + + // Long operations during data mode. + + // Exiting the data mode allows the delayed UDP data to be received. + +++ + #XDATAMODE: 0 + // Unconnected UDP socket automatically receives the delayed data with RECVFROM. + #XRECVFROM: 1,0,16,"111.112.113.114",1235 + Delayed UDP data + // Disable automatic data reception for all sockets. + AT#XRECVCFG=,0 + + OK + AT#XCLOSE + + #XCLOSE: 0,0 + + #XCLOSE: 1,0 + + OK + +Read command +------------ + +The read command allows you to check the receive configuration settings of sockets. + +Syntax +~~~~~~ + +:: + + #XRECVCFG? + +Response syntax +~~~~~~~~~~~~~~~ + +:: + + #XRECVCFG: ,, + +* The ```` parameter is an integer that identifies the socket handle. +* The ```` parameter is an integer that specifies the automatic reception flags. + It is a combination of the following values summed up: + + * ``0`` - No automatic data reception. + * ``1`` - Automatic data reception in AT-command mode. + * ``2`` - Automatic data reception in data mode. +* The ```` parameter is an integer that specifies the hex format for automatically received data. + It can be one of the following values: + + * ``0`` - Data is received in binary format. + * ``1`` - Data is received in hex string format (supported only in AT-command mode). + +Example +~~~~~~~ + +:: + + AT#XRECVCFG=,1,0 + + OK + AT#XSOCKET=1,1,0 + + #XSOCKET: 0,1,6 + + OK + AT#XSOCKET=1,1,0 + + #XSOCKET: 1,1,6 + + OK + // Enable automatic data reception in data mode for socket 1. + AT#XRECVCFG=1,2 + + OK + AT#XRECVCFG? + + #XRECVCFG: 0,1,0 + + #XRECVCFG: 1,2,0 + + OK + // Disable automatic reception for all sockets. + AT#XRECVCFG=,0 + + OK + AT#XRECVCFG? + + OK + +Test command +------------ + +The test command provides information about the command and its parameters. + +Syntax +~~~~~~ + +:: + + #XRECVCFG=? + +Response syntax +~~~~~~~~~~~~~~~ + +:: + + #XRECVCFG: ,(0,1,2,3),(0,1) + +Example +~~~~~~~ + +:: + + AT#XRECVCFG=? + + #XRECVCFG: ,(0,1,2,3),(0,1) + + OK + Resolve hostname #XGETADDRINFO ============================== @@ -1490,7 +1743,7 @@ Response syntax It indicates the IPv4 or IPv6 address of the resolved hostname. Example -~~~~~~~~ +~~~~~~~ :: diff --git a/doc/app/sm_data_mode.rst b/doc/app/sm_data_mode.rst index 1e48dfcf..303cf7f9 100644 --- a/doc/app/sm_data_mode.rst +++ b/doc/app/sm_data_mode.rst @@ -13,7 +13,7 @@ When running in data mode, the application does the following: * It considers all the data received from the MCU over the UART bus as arbitrary data to be streamed through the LTE network by various service modules. * It buffers the URCs received from modem and threads and sends them to the MCU only after exiting data mode. -* For TLS and UDP client (and servers), it considers all the data streamed from a remote service as arbitrary data to be sent to the MCU over the UART bus. +* For the socket that is in data mode with automatic data reception, and TCP and UDP clients, the data streamed from a remote service is considered binary data to be sent to the MCU over the UART. Overview ******** @@ -84,6 +84,7 @@ After exiting the data mode, the |SM| application returns to the AT command mode * The remote server disconnects the TCP client. * The TCP client disconnects from the remote server due to an error. * The UDP client disconnects from the remote server due to an error. + * The socket in data mode encounters an unrecoverable error. For |SM| to stop dropping the data received from UART and move to AT-command mode, the MCU needs to send the termination command :ref:`CONFIG_SM_DATAMODE_TERMINATOR ` back to the |SM| application.