diff --git a/components/net/Kconfig b/components/net/Kconfig index ac7a063edd..f79ac27843 100644 --- a/components/net/Kconfig +++ b/components/net/Kconfig @@ -19,9 +19,9 @@ config RT_USING_SAL config SAL_USING_AT bool "Support AT Commands stack" - default y if RT_USING_AT_SOCKET + default y if AT_USING_SOCKET default n - depends on RT_USING_AT_SOCKET + depends on AT_USING_SOCKET endmenu config SAL_USING_POSIX @@ -369,6 +369,8 @@ endmenu source "$RTT_DIR/components/net/freemodbus/Kconfig" +source "$RTT_DIR/components/net/at/Kconfig" + if RT_USING_LWIP config LWIP_USING_DHCPD diff --git a/components/net/at/Kconfig b/components/net/at/Kconfig new file mode 100644 index 0000000000..df34b70091 --- /dev/null +++ b/components/net/at/Kconfig @@ -0,0 +1,78 @@ +menu "AT commands" + +config RT_USING_AT + bool "Enable AT commands" + default n + +if RT_USING_AT + + config AT_DEBUG + bool "Enable debug log output" + default n + + config AT_USING_SERVER + bool "Enable AT commands server" + default n + + if AT_USING_SERVER + + config AT_SERVER_DEVICE + string "Server device name" + default "uart3" + + config AT_SERVER_RECV_BUFF_LEN + int "The maximum length of server data accepted" + default 256 + + choice + prompt "The commands new line sign" + help + This end mark can used for AT server determine the end of commands , + it can choose "\r", "\n" or "\r\n" + + default AT_CMD_END_MARK_CRLF + + config AT_CMD_END_MARK_CRLF + bool "\\r\\n" + + config AT_CMD_END_MARK_CR + bool "\\r" + + config AT_CMD_END_MARK_LF + bool "\\n" + + endchoice + + endif + + config AT_USING_CLIENT + bool "Enable AT commands client" + default n + + if AT_USING_CLIENT + + config AT_CLIENT_DEVICE + string "Client device name" + default "uart2" + + config AT_CLIENT_RECV_BUFF_LEN + int "The maximum length of client data accepted" + default 512 + + config AT_USING_SOCKET + bool "Provide similar BSD Socket API by AT" + default n + endif + + config AT_USING_CLI + bool "Enable command-line interface for AT commands" + default y + depends on FINSH_USING_MSH + + config AT_PRINT_RAW_CMD + bool "Enable print RAW format AT command communication data" + default n + +endif + +endmenu diff --git a/components/net/at/SConscript b/components/net/at/SConscript new file mode 100644 index 0000000000..2a25aaeb42 --- /dev/null +++ b/components/net/at/SConscript @@ -0,0 +1,26 @@ +from building import * + +cwd = GetCurrentDir() +path = [cwd + '/include'] + +src = Split(''' +src/at_cli.c +src/at_utils.c +''') + +if GetDepend(['AT_USING_SERVER']): + src += Split(''' + src/at_server.c + src/at_base_cmd.c + ''') + +if GetDepend(['AT_USING_CLIENT']): + src += Glob('src/at_client.c') + +if GetDepend(['AT_USING_SOCKET']): + src += Glob('at_socket/*.c') + path += [cwd + '/at_socket'] + +group = DefineGroup('AT', src, depend = ['RT_USING_AT'], CPPPATH = path) + +Return('group') diff --git a/components/net/at/at_socket/at_socket.c b/components/net/at/at_socket/at_socket.c new file mode 100644 index 0000000000..0beaa9645c --- /dev/null +++ b/components/net/at/at_socket/at_socket.c @@ -0,0 +1,1138 @@ +/* + * File : at_socket.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-06-06 chenyong first version + */ + +#include +#include +#include +#include + +#include + +#ifdef SAL_USING_POSIX +#include +#endif + +#ifdef DBG_SECTION_NAME +#undef DBG_SECTION_NAME +#define DBG_SECTION_NAME "[AT_SOC] " +#endif + + +#define HTONS_PORT(x) ((((x) & 0x00ffUL) << 8) | (((x) & 0xff00UL) >> 8)) +#define NIPQUAD(addr) \ + ((unsigned char *)&addr)[0], \ + ((unsigned char *)&addr)[1], \ + ((unsigned char *)&addr)[2], \ + ((unsigned char *)&addr)[3] + +#ifdef AT_DEVICE_NOT_SELECTED +#error The AT socket device is not selected, please select it through the env menuconfig. +#endif + +/* The maximum number of sockets structure */ +#ifndef AT_SOCKETS_NUM +#define AT_SOCKETS_NUM AT_DEVICE_SOCKETS_NUM +#endif + +typedef enum { + AT_EVENT_SEND, + AT_EVENT_RECV, + AT_EVENT_ERROR, +} at_event_t; + +/* the global array of available sockets */ +static struct at_socket sockets[AT_SOCKETS_NUM] = { 0 }; +/* the global AT socket lock */ +static rt_mutex_t at_socket_lock = RT_NULL; +/* AT device socket options */ +static struct at_device_ops *at_dev_ops = RT_NULL; + +struct at_socket *at_get_socket(int socket) +{ + if (socket < 0 || socket >= AT_SOCKETS_NUM) + { + return RT_NULL; + } + + /* check socket structure valid or not */ + if (sockets[socket].magic != AT_SOCKET_MAGIC) + { + return RT_NULL; + } + + return &sockets[socket]; +} + +/* get a block to the AT socket receive list*/ +static size_t at_recvpkt_put(rt_slist_t *rlist, const char *ptr, size_t length) +{ + at_recv_pkt_t pkt; + + pkt = (at_recv_pkt_t) rt_calloc(1, sizeof(struct at_recv_pkt)); + if (!pkt) + { + LOG_E("No memory for receive packet table!"); + return 0; + } + + pkt->bfsz_totle = length; + pkt->bfsz_index = 0; + pkt->buff = (char *) ptr; + + rt_slist_append(rlist, &pkt->list); + + return length; +} + +/* delete and free all receive buffer list */ +static int at_recvpkt_all_delete(rt_slist_t *rlist) +{ + at_recv_pkt_t pkt; + rt_slist_t *node; + + if(rt_slist_isempty(rlist)) + return 0; + + for(node = rt_slist_first(rlist); node; node = rt_slist_next(node)) + { + pkt = rt_slist_entry(node, struct at_recv_pkt, list); + if (pkt->buff) + { + rt_free(pkt->buff); + } + if(pkt) + { + rt_free(pkt); + pkt = RT_NULL; + } + } + + return 0; +} + +/* delete and free specified list block */ +static int at_recvpkt_node_delete(rt_slist_t *rlist, rt_slist_t *node) +{ + at_recv_pkt_t pkt; + + if(rt_slist_isempty(rlist)) + return 0; + + rt_slist_remove(rlist, node); + + pkt= rt_slist_entry(node, struct at_recv_pkt, list); + if (pkt->buff) + { + rt_free(pkt->buff); + } + if (pkt) + { + rt_free(pkt); + pkt = RT_NULL; + } + + return 0; +} + +/* get a block from AT socket receive list */ +static size_t at_recvpkt_get(rt_slist_t *rlist, char *mem, size_t len) +{ + rt_slist_t *node; + at_recv_pkt_t pkt; + size_t content_pos = 0, page_pos = 0; + + if(rt_slist_isempty(rlist)) + return 0; + + for (node = rt_slist_first(rlist); node; node = rt_slist_next(node)) + { + pkt = rt_slist_entry(node, struct at_recv_pkt, list); + + page_pos = pkt->bfsz_totle - pkt->bfsz_index; + + if (page_pos >= len - content_pos) + { + memcpy((char *) mem + content_pos, pkt->buff + pkt->bfsz_index, len - content_pos); + pkt->bfsz_index += len - content_pos; + if (pkt->bfsz_index == pkt->bfsz_totle) + { + at_recvpkt_node_delete(rlist, node); + } + content_pos = len; + break; + } + else + { + memcpy((char *) mem + content_pos, pkt->buff + pkt->bfsz_index, page_pos); + content_pos += page_pos; + pkt->bfsz_index += page_pos; + at_recvpkt_node_delete(rlist, node); + } + } + + return content_pos; +} + +static void at_do_event_changes(struct at_socket *sock, at_event_t event, rt_bool_t is_plus) +{ + switch (event) + { + case AT_EVENT_SEND: + { + if (is_plus) + { + sock->sendevent++; + +#ifdef SAL_USING_POSIX + rt_wqueue_wakeup(&sock->wait_head, (void*) POLLOUT); +#endif + + } + else if (sock->sendevent) + { + sock->sendevent --; + } + break; + } + case AT_EVENT_RECV: + { + if (is_plus) + { + sock->rcvevent++; + +#ifdef SAL_USING_POSIX + rt_wqueue_wakeup(&sock->wait_head, (void*) POLLIN); +#endif + + } + else if (sock->rcvevent) + { + sock->rcvevent --; + } + break; + } + case AT_EVENT_ERROR: + { + if (is_plus) + { + sock->errevent++; + +#ifdef SAL_USING_POSIX + rt_wqueue_wakeup(&sock->wait_head, (void*) POLLERR); +#endif + + } + else if (sock->errevent) + { + sock->errevent --; + } + break; + } + default: + LOG_E("Not supported event (%d)", event) + } +} + +static struct at_socket *alloc_socket(void) +{ + char sem_name[RT_NAME_MAX]; + char lock_name[RT_NAME_MAX]; + struct at_socket *sock; + int idx; + + rt_mutex_take(at_socket_lock, RT_WAITING_FOREVER); + + /* find an empty at socket entry */ + for (idx = 0; idx < AT_SOCKETS_NUM && sockets[idx].magic; idx++); + + /* can't find an empty protocol family entry */ + if (idx == AT_SOCKETS_NUM) + { + goto __err; + } + + sock = &(sockets[idx]); + sock->magic = AT_SOCKET_MAGIC; + sock->socket = idx; + sock->state = AT_SOCKET_NONE; + sock->rcvevent = RT_NULL; + sock->sendevent = RT_NULL; + sock->errevent = RT_NULL; + rt_slist_init(&sock->recvpkt_list); + + rt_snprintf(sem_name, RT_NAME_MAX, "%s%d", "at_recv_notice_", idx); + /* create AT socket receive mailbox */ + if ((sock->recv_notice = rt_sem_create(sem_name, 0, RT_IPC_FLAG_FIFO)) == RT_NULL) + { + goto __err; + } + + rt_snprintf(lock_name, RT_NAME_MAX, "%s%d", "at_recv_lock_", idx); + /* create AT socket receive ring buffer lock */ + if((sock->recv_lock = rt_mutex_create(lock_name, RT_IPC_FLAG_FIFO)) == RT_NULL) + { + goto __err; + } + + rt_mutex_release(at_socket_lock); + return sock; + +__err: + rt_mutex_release(at_socket_lock); + return RT_NULL; +} + +int at_socket(int domain, int type, int protocol) +{ + struct at_socket *sock; + enum at_socket_type socket_type; + + /* check socket family protocol */ + RT_ASSERT(domain == AF_AT||domain == AF_INET); + + //TODO check protocol + + switch(type) + { + case SOCK_STREAM: + socket_type = AT_SOCKET_TCP; + break; + + case SOCK_DGRAM: + socket_type = AT_SOCKET_UDP; + break; + + default : + LOG_E("Don't support socket type (%d)!", type); + return -1; + } + + /* allocate and initialize a new AT socket */ + sock = alloc_socket(); + if(!sock) + { + LOG_E("Allocate a new AT socket failed!"); + return RT_NULL; + } + sock->type = socket_type; + +#ifdef SAL_USING_POSIX + rt_wqueue_init(&sock->wait_head); +#endif + + return sock->socket; +} + +static int free_socket(struct at_socket *sock) +{ + if (sock->recv_notice) + { + rt_sem_delete(sock->recv_notice); + } + + if (sock->recv_lock) + { + rt_mutex_delete(sock->recv_lock); + } + + if (!rt_slist_isempty(&sock->recvpkt_list)) + { + at_recvpkt_all_delete(&sock->recvpkt_list); + } + + memset(sock, 0x00, sizeof(struct at_socket)); + + return 0; +} + +int at_closesocket(int socket) +{ + struct at_socket *sock; + enum at_socket_state last_state; + + if (!at_dev_ops) + { + LOG_E("Please register AT device socket options first!"); + return -1; + } + + if ((sock = at_get_socket(socket)) == RT_NULL) + return -1; + + last_state = sock->state; + + /* the rt_at_socket_close is need some time, so change state in advance */ + sock->state = AT_SOCKET_CLOSED; + + if (last_state == AT_SOCKET_CONNECT) + { + if (at_dev_ops->close(socket) != 0) + { + LOG_E("AT socket (%d) closesocket failed!", socket); + } + } + + return free_socket(sock); +} + +int at_shutdown(int socket, int how) +{ + struct at_socket *sock; + + if (!at_dev_ops) + { + LOG_E("Please register AT device socket options first!"); + return -1; + } + + if ((sock = at_get_socket(socket)) == RT_NULL) + return -1; + + if (sock->state == AT_SOCKET_CONNECT) + { + if (at_dev_ops->close(socket) != 0) + { + LOG_E("AT socket (%d) shutdown failed!", socket); + } + } + + return free_socket(sock); +} + +int at_bind(int socket, const struct sockaddr *name, socklen_t namelen) +{ + + if (at_get_socket(socket) == RT_NULL) + return -1; + + return 0; +} + +/* get IP address and port by socketaddr structure information */ +static int socketaddr_to_ipaddr_port(const struct sockaddr *sockaddr, ip_addr_t *addr, uint16_t *port) +{ + const struct sockaddr_in* sin = (const struct sockaddr_in*) (const void *) sockaddr; + + (*addr).u_addr.ip4.addr = sin->sin_addr.s_addr; + + *port = (uint16_t) HTONS_PORT(sin->sin_port); + + return 0; +} + +/* ipaddr structure change to IP address */ +static int ipaddr_to_ipstr(const struct sockaddr *sockaddr, char *ipstr) +{ + struct sockaddr_in *sin = (struct sockaddr_in *) sockaddr; + + /* change network ip_addr to ip string */ + rt_snprintf(ipstr, 16, "%u.%u.%u.%u", NIPQUAD(sin->sin_addr.s_addr)); + + return 0; +} + +static void at_recv_notice_cb(int socket, at_socket_evt_t event, const char *buff, size_t bfsz) +{ + struct at_socket *sock; + + RT_ASSERT(buff); + RT_ASSERT(bfsz); + RT_ASSERT(event == AT_SOCKET_EVT_RECV); + + if ((sock = at_get_socket(socket)) == RT_NULL) + return ; + + /* put receive buffer to receiver packet list */ + rt_mutex_take(sock->recv_lock, RT_WAITING_FOREVER); + at_recvpkt_put(&(sock->recvpkt_list), buff, bfsz); + rt_mutex_release(sock->recv_lock); + + rt_sem_release(sock->recv_notice); + + at_do_event_changes(sock, AT_EVENT_RECV, RT_TRUE); +} + +static void at_closed_notice_cb(int socket, at_socket_evt_t event, const char *buff, size_t bfsz) +{ + struct at_socket *sock; + + RT_ASSERT(event == AT_SOCKET_EVT_CLOSED); + + if ((sock = at_get_socket(socket)) == RT_NULL) + return ; + + at_do_event_changes(sock, AT_EVENT_RECV, RT_TRUE); + at_do_event_changes(sock, AT_EVENT_ERROR, RT_TRUE); + +// LOG_D("socket (%d) closed by remote"); + sock->state = AT_SOCKET_CLOSED; + rt_sem_release(sock->recv_notice); +} +int at_connect(int socket, const struct sockaddr *name, socklen_t namelen) +{ + struct at_socket *sock; + ip_addr_t remote_addr; + uint16_t remote_port; + char ipstr[16] = { 0 }; + int result = 0; + + if (!at_dev_ops) + { + LOG_E("Please register AT device socket options first!"); + return -1; + } + + sock = at_get_socket(socket); + if (!sock) + { + result = -1; + goto __exit; + } + + if (sock->state != AT_SOCKET_NONE) + { + LOG_E("Socket %d connect state is %d.", sock->socket, sock->state); + result = -1; + goto __exit; + } + + /* get IP address and port by socketaddr structure */ + socketaddr_to_ipaddr_port(name, &remote_addr, &remote_port); + ipaddr_to_ipstr(name, ipstr); + + if (at_dev_ops->connect(socket, ipstr, remote_port, sock->type, RT_TRUE) < 0) + { + LOG_E("AT socket(%d) connect failed!", socket); + result = -1; + goto __exit; + } + + sock->state = AT_SOCKET_CONNECT; + + /* set AT socket receive data callback function */ + at_dev_ops->set_event_cb(AT_SOCKET_EVT_RECV, at_recv_notice_cb); + at_dev_ops->set_event_cb(AT_SOCKET_EVT_CLOSED, at_closed_notice_cb); + +__exit: + + if (result < 0) + { + at_do_event_changes(sock, AT_EVENT_ERROR, RT_TRUE); + } + + return result; +} + +int at_recvfrom(int socket, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen) +{ + struct at_socket *sock; + int timeout; + int result = 0; + size_t recv_len = 0; + + if (!mem || len == 0) + { + LOG_E("AT recvfrom input data or length error!"); + result = -1; + goto __exit; + } + + if (!at_dev_ops) + { + LOG_E("Please register AT device socket options first!"); + return -1; + } + + sock = at_get_socket(socket); + if (!sock) + { + result = -1; + goto __exit; + } + + /* if the socket type is UDP, nead to connect socket first */ + if (from && sock->type == AT_SOCKET_UDP && sock->state == AT_SOCKET_NONE) + { + ip_addr_t remote_addr; + uint16_t remote_port; + char ipstr[16] = { 0 }; + + socketaddr_to_ipaddr_port(from, &remote_addr, &remote_port); + ipaddr_to_ipstr(from, ipstr); + + if (at_dev_ops->connect(socket, ipstr, remote_port, sock->type, RT_TRUE) < 0) + { + LOG_E("AT socket UDP connect failed!"); + result = -1; + goto __exit; + } + sock->state = AT_SOCKET_CONNECT; + } + + if (sock->state != AT_SOCKET_CONNECT) + { + LOG_E("received data error, current socket (%d) state (%d) is error.", socket, sock->state); + result = -1; + goto __exit; + } + + /* receive packet list last transmission of remaining data */ + rt_mutex_take(sock->recv_lock, RT_WAITING_FOREVER); + if((recv_len = at_recvpkt_get(&(sock->recvpkt_list), (char *)mem, len)) > 0) + { + rt_mutex_release(sock->recv_lock); + goto __exit; + } + rt_mutex_release(sock->recv_lock); + + /* non-blocking sockets receive data */ + if (flags & MSG_DONTWAIT) + { + goto __exit; + } + + /* set AT socket receive timeout */ + if((timeout = sock->recv_timeout) == 0) + { + timeout = RT_WAITING_FOREVER; + } + + while (1) + { + /* wait the receive semaphore */ + if (rt_sem_take(sock->recv_notice, timeout) < 0) + { + LOG_E("AT socket (%d) receive timeout (%d)!", socket, timeout); + result = -1; + goto __exit; + } + else + { + if (sock->state == AT_SOCKET_CONNECT) + { + /* get receive buffer to receiver ring buffer */ + rt_mutex_take(sock->recv_lock, RT_WAITING_FOREVER); + recv_len = at_recvpkt_get(&(sock->recvpkt_list), (char *) mem, len); + rt_mutex_release(sock->recv_lock); + if (recv_len > 0) + { + break; + } + } + else + { + LOG_D("received data exit, current socket (%d) is closed by remote.", socket); + result = -1; + goto __exit; + } + } + } + +__exit: + + if (result < 0) + { + at_do_event_changes(sock, AT_EVENT_ERROR, RT_TRUE); + } + else + { + result = recv_len; + if (recv_len) + { + at_do_event_changes(sock, AT_EVENT_RECV, RT_FALSE); + } + } + + return result; +} + +int at_recv(int s, void *mem, size_t len, int flags) +{ + return at_recvfrom(s, mem, len, flags, RT_NULL, RT_NULL); +} + +int at_sendto(int socket, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen) +{ + struct at_socket *sock; + int len, result = 0; + + if (!at_dev_ops) + { + LOG_E("Please register AT device socket options first!"); + result = -1; + goto __exit; + } + + if (!data || size == 0) + { + LOG_E("AT sendto input data or size error!"); + result = -1; + goto __exit; + } + + sock = at_get_socket(socket); + if (!sock) + { + result = -1; + goto __exit; + } + + switch (sock->type) + { + case AT_SOCKET_TCP: + if (sock->state != AT_SOCKET_CONNECT) + { + LOG_E("send data error, current socket (%d) state (%d) is error.", socket, sock->state); + result = -1; + goto __exit; + } + + if ((len = at_dev_ops->send(sock->socket, (const char *) data, size, sock->type)) < 0) + { + result = -1; + goto __exit; + } + break; + + case AT_SOCKET_UDP: + if (to && sock->state == AT_SOCKET_NONE) + { + ip_addr_t remote_addr; + uint16_t remote_port; + char ipstr[16] = { 0 }; + + socketaddr_to_ipaddr_port(to, &remote_addr, &remote_port); + ipaddr_to_ipstr(to, ipstr); + + if (at_dev_ops->connect(socket, ipstr, remote_port, sock->type, RT_TRUE) < 0) + { + LOG_E("AT socket (%d) UDP connect failed!", socket); + result = -1; + goto __exit; + } + sock->state = AT_SOCKET_CONNECT; + } + + if ((len = at_dev_ops->send(sock->socket, (char *) data, size, sock->type)) < 0) + { + result = -1; + goto __exit; + } + break; + + default: + LOG_E("Socket (%d) type %d is not support.", socket, sock->type); + result = -1; + goto __exit; + } + +__exit: + + if (result < 0) + { + at_do_event_changes(sock, AT_EVENT_ERROR, RT_TRUE); + } + else + { + result = len; + } + + return result; +} + +int at_send(int socket, const void *data, size_t size, int flags) +{ + return at_sendto(socket, data, size, flags, RT_NULL, 0); +} + + +int at_getsockopt(int socket, int level, int optname, void *optval, socklen_t *optlen) +{ + struct at_socket *sock; + int32_t timeout; + + if (!optval || !optlen) + { + LOG_E("AT getsocketopt input option value or option length error!"); + return -1; + } + + sock = at_get_socket(socket); + if (!sock) + { + return -1; + } + + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_RCVTIMEO: + timeout = sock->recv_timeout; + ((struct timeval *)(optval))->tv_sec = (timeout) / 1000U; + ((struct timeval *)(optval))->tv_usec = (timeout % 1000U) * 1000U; + break; + + case SO_SNDTIMEO: + timeout = sock->send_timeout; + ((struct timeval *) optval)->tv_sec = timeout / 1000U; + ((struct timeval *) optval)->tv_usec = (timeout % 1000U) * 1000U; + break; + + default: + LOG_E("AT socket (%d) not support option name : %d.", socket, optname); + return -1; + } + break; + + default: + LOG_E("AT socket (%d) not support option level : %d.", socket, level); + return -1; + } + + return 0; +} + +int at_setsockopt(int socket, int level, int optname, const void *optval, socklen_t optlen) +{ + struct at_socket *sock; + + if (!optval) + { + LOG_E("AT setsockopt input option value error!"); + return -1; + } + + sock = at_get_socket(socket); + if (!sock) + { + return -1; + } + + switch (level) + { + case SOL_SOCKET: + switch (optname) + { + case SO_RCVTIMEO: + sock->recv_timeout = ((const struct timeval *) optval)->tv_sec * 1000 + + ((const struct timeval *) optval)->tv_usec / 1000; + break; + + case SO_SNDTIMEO: + sock->send_timeout = ((const struct timeval *) optval)->tv_sec * 1000 + + ((const struct timeval *) optval)->tv_usec / 1000; + break; + + default: + LOG_E("AT socket (%d) not support option name : %d.", socket, optname); + return -1; + } + break; + case IPPROTO_TCP: + switch (optname) + { + case TCP_NODELAY: + break; + } + break; + default: + LOG_E("AT socket (%d) not support option level : %d.", socket, level); + return -1; + } + + return 0; +} + +static uint32_t ipstr_atol(const char* nptr) +{ + uint32_t total = 0; + char sign = '+'; + /* jump space */ + while (isspace(*nptr)) + { + ++nptr; + } + if (*nptr == '-' || *nptr == '+') + { + sign = *nptr++; + } + while (isdigit(*nptr)) + { + total = 10 * total + ((*nptr++) - '0'); + } + return (sign == '-') ? -total : total; +} + +/* IP address to unsigned int type */ +static uint32_t ipstr_to_u32(char *ipstr) +{ + char ipBytes[4] = { 0 }; + uint32_t i; + + for (i = 0; i < 4; i++, ipstr++) + { + ipBytes[i] = (char) ipstr_atol(ipstr); + if ((ipstr = strchr(ipstr, '.')) == RT_NULL) + { + break; + } + } + return *(uint32_t *) ipBytes; +} + +struct hostent *at_gethostbyname(const char *name) +{ + ip_addr_t addr; + char ipstr[16] = { 0 }; + /* buffer variables for at_gethostbyname() */ + static struct hostent s_hostent; + static char *s_aliases; + static ip_addr_t s_hostent_addr; + static ip_addr_t *s_phostent_addr[2]; + static char s_hostname[DNS_MAX_NAME_LENGTH + 1]; + size_t idx = 0; + + if (!name) + { + LOG_E("AT gethostbyname input name error!"); + return RT_NULL; + } + + if (!at_dev_ops) + { + LOG_E("Please register AT device socket options first!"); + return RT_NULL; + } + + for (idx = 0; idx < strlen(name) && !isalpha(name[idx]); idx++); + + if (idx < strlen(name)) + { + if (at_dev_ops->domain_resolve(name, ipstr) < 0) + { + LOG_E("AT domain (%s) resolve error!", name); + return RT_NULL; + } + } + else + { + strncpy(ipstr, name, strlen(name)); + } + + addr.u_addr.ip4.addr = ipstr_to_u32(ipstr); + + /* fill hostent structure */ + s_hostent_addr = addr; + s_phostent_addr[0] = &s_hostent_addr; + s_phostent_addr[1] = RT_NULL; + strncpy(s_hostname, name, DNS_MAX_NAME_LENGTH); + s_hostname[DNS_MAX_NAME_LENGTH] = 0; + s_hostent.h_name = s_hostname; + s_aliases = RT_NULL; + s_hostent.h_aliases = &s_aliases; + s_hostent.h_addrtype = AF_AT; + s_hostent.h_length = sizeof(ip_addr_t); + s_hostent.h_addr_list = (char**) &s_phostent_addr; + + return &s_hostent; +} + +int at_getaddrinfo(const char *nodename, const char *servname, + const struct addrinfo *hints, struct addrinfo **res) +{ + int port_nr = 0; + ip_addr_t addr; + struct addrinfo *ai; + struct sockaddr_storage *sa; + size_t total_size = 0; + size_t namelen = 0; + int ai_family = 0; + + if (res == RT_NULL) + { + return EAI_FAIL; + } + if (!at_dev_ops) + { + LOG_E("Please register AT device socket options first!"); + return EAI_FAIL; + } + *res = RT_NULL; + if ((nodename == RT_NULL) && (servname == RT_NULL)) + { + return EAI_NONAME; + } + + if (hints != RT_NULL) + { + ai_family = hints->ai_family; + if (hints->ai_family != AF_AT && hints->ai_family != AF_INET && hints->ai_family != AF_UNSPEC) + { + return EAI_FAMILY; + } + } + + if (servname != RT_NULL) + { + /* service name specified: convert to port number */ + port_nr = atoi(servname); + if ((port_nr <= 0) || (port_nr > 0xffff)) + { + return EAI_SERVICE; + } + } + + if (nodename != RT_NULL) + { + /* service location specified, try to resolve */ + if ((hints != RT_NULL) && (hints->ai_flags & AI_NUMERICHOST)) + { + /* no DNS lookup, just parse for an address string */ + if (!inet_aton(nodename, (ip4_addr_t * )&addr)) + { + return EAI_NONAME; + } + + if (ai_family == AF_AT || ai_family == AF_INET) + { + return EAI_NONAME; + } + } + else + { + char ip_str[16] = { 0 }; + size_t idx = 0; + + for (idx = 0; idx < strlen(nodename) && !isalpha(nodename[idx]); idx++); + + if(idx < strlen(nodename)) + { + if (at_dev_ops->domain_resolve((char *) nodename, ip_str) != 0) + { + return EAI_FAIL; + } + } + else + { + strncpy(ip_str, nodename, strlen(nodename)); + } + + addr.type = IPADDR_TYPE_V4; + if ((addr.u_addr.ip4.addr = ipstr_to_u32(ip_str)) == 0) + { + return EAI_FAIL; + } + } + } + else + { + /* to do service location specified, use loopback address */ + } + + total_size = sizeof(struct addrinfo) + sizeof(struct sockaddr_storage); + if (nodename != RT_NULL) + { + namelen = strlen(nodename); + if (namelen > DNS_MAX_NAME_LENGTH) + { + /* invalid name length */ + return EAI_FAIL; + } + RT_ASSERT(total_size + namelen + 1 > total_size); + total_size += namelen + 1; + } + /* If this fails, please report to lwip-devel! :-) */ + RT_ASSERT(total_size <= sizeof(struct addrinfo) + sizeof(struct sockaddr_storage) + DNS_MAX_NAME_LENGTH + 1); + ai = (struct addrinfo *) rt_malloc(total_size); + if (ai == RT_NULL) + { + return EAI_MEMORY; + } + memset(ai, 0, total_size); + /* cast through void* to get rid of alignment warnings */ + sa = (struct sockaddr_storage *) (void *) ((uint8_t *) ai + sizeof(struct addrinfo)); + struct sockaddr_in *sa4 = (struct sockaddr_in *) sa; + /* set up sockaddr */ + sa4->sin_addr.s_addr = addr.u_addr.ip4.addr; + sa4->sin_family = AF_AT; + sa4->sin_len = sizeof(struct sockaddr_in); + sa4->sin_port = htons((u16_t )port_nr); + ai->ai_family = AF_AT; + + /* set up addrinfo */ + if (hints != RT_NULL) + { + /* copy socktype & protocol from hints if specified */ + ai->ai_socktype = hints->ai_socktype; + ai->ai_protocol = hints->ai_protocol; + } + if (nodename != RT_NULL) + { + /* copy nodename to canonname if specified */ + ai->ai_canonname = ((char *) ai + sizeof(struct addrinfo) + sizeof(struct sockaddr_storage)); + memcpy(ai->ai_canonname, nodename, namelen); + ai->ai_canonname[namelen] = 0; + } + ai->ai_addrlen = sizeof(struct sockaddr_storage); + ai->ai_addr = (struct sockaddr *) sa; + + *res = ai; + + return 0; +} + +void at_freeaddrinfo(struct addrinfo *ai) +{ + if (ai != RT_NULL) + { + rt_free(ai); + } +} + +void at_scoket_device_register(const struct at_device_ops *ops) +{ + RT_ASSERT(ops); + RT_ASSERT(ops->connect); + RT_ASSERT(ops->close); + RT_ASSERT(ops->send); + RT_ASSERT(ops->domain_resolve); + RT_ASSERT(ops->set_event_cb); + at_dev_ops = (struct at_device_ops *) ops; +} + +static int at_socket_init(void) +{ + /* create AT socket lock */ + at_socket_lock = rt_mutex_create("at_socket_lock", RT_IPC_FLAG_FIFO); + if (!at_socket_lock) + { + LOG_E("No memory for AT socket lock!"); + return -RT_ENOMEM; + } + + return RT_EOK; +} +INIT_COMPONENT_EXPORT(at_socket_init); diff --git a/components/net/at/at_socket/at_socket.h b/components/net/at/at_socket/at_socket.h new file mode 100644 index 0000000000..612078522d --- /dev/null +++ b/components/net/at/at_socket/at_socket.h @@ -0,0 +1,163 @@ +/* + * File : at_socket.h + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-06-06 chenYong first version + */ + +#ifndef __AT_SOCKET_H__ +#define __AT_SOCKET_H__ + +#include +#include +#include + +#include +#include + +#ifndef AT_SOCKET_RECV_BFSZ +#define AT_SOCKET_RECV_BFSZ 512 +#endif + +#define AT_DEFAULT_RECVMBOX_SIZE 10 +#define AT_DEFAULT_ACCEPTMBOX_SIZE 10 + +/* sal socket magic word */ +#define AT_SOCKET_MAGIC 0xA100 + +/* Current state of the AT socket. */ +enum at_socket_state +{ + AT_SOCKET_NONE, + AT_SOCKET_LISTEN, + AT_SOCKET_CONNECT, + AT_SOCKET_CLOSED +}; + +enum at_socket_type +{ + AT_SOCKET_INVALID = 0, + AT_SOCKET_TCP = 0x10, /* TCP IPv4 */ + AT_SOCKET_UDP = 0x20, /* UDP IPv4 */ +}; + +typedef enum +{ + AT_SOCKET_EVT_RECV, + AT_SOCKET_EVT_CLOSED, +} at_socket_evt_t; + +typedef void (*at_evt_cb_t)(int socket, at_socket_evt_t event, const char *buff, size_t bfsz); + +struct at_socket; +/* A callback prototype to inform about events for AT socket */ +typedef void (* at_socket_callback)(struct at_socket *conn, int event, uint16_t len); + +/* AT device socket options function */ +struct at_device_ops +{ + int (*connect)(int socket, char *ip, int32_t port, enum at_socket_type type, rt_bool_t is_client); + int (*close)(int socket); + int (*send)(int socket, const char *buff, size_t bfsz, enum at_socket_type type); + int (*domain_resolve)(const char *name, char ip[16]); + void (*set_event_cb)(at_socket_evt_t event, at_evt_cb_t cb); +}; + +/* AT receive package list structure */ +struct at_recv_pkt +{ + rt_slist_t list; + size_t bfsz_totle; + size_t bfsz_index; + char *buff; +}; +typedef struct at_recv_pkt *at_recv_pkt_t; + +struct at_socket +{ + /* AT socket magic word */ + uint32_t magic; + + int socket; + /* type of the AT socket (TCP, UDP or RAW) */ + enum at_socket_type type; + /* current state of the AT socket */ + enum at_socket_state state; + /* receive semaphore, received data release semaphore */ + rt_sem_t recv_notice; + rt_mutex_t recv_lock; + rt_slist_t recvpkt_list; + + /* timeout to wait for send or received data in milliseconds */ + int32_t recv_timeout; + int32_t send_timeout; + /* A callback function that is informed about events for this AT socket */ + at_socket_callback callback; + + /* number of times data was received, set by event_callback() */ + uint16_t rcvevent; + /* number of times data was ACKed (free send buffer), set by event_callback() */ + uint16_t sendevent; + /* error happened for this socket, set by event_callback() */ + uint16_t errevent; + +#ifdef SAL_USING_POSIX + rt_wqueue_t wait_head; +#endif +}; + +int at_socket(int domain, int type, int protocol); +int at_closesocket(int socket); +int at_shutdown(int socket, int how); +int at_bind(int socket, const struct sockaddr *name, socklen_t namelen); +int at_connect(int socket, const struct sockaddr *name, socklen_t namelen); +int at_sendto(int socket, const void *data, size_t size, int flags, const struct sockaddr *to, socklen_t tolen); +int at_send(int socket, const void *data, size_t size, int flags); +int at_recvfrom(int socket, void *mem, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); +int at_recv(int socket, void *mem, size_t len, int flags); +int at_getsockopt(int socket, int level, int optname, void *optval, socklen_t *optlen); +int at_setsockopt(int socket, int level, int optname, const void *optval, socklen_t optlen); +struct hostent *at_gethostbyname(const char *name); +int at_getaddrinfo(const char *nodename, const char *servname, const struct addrinfo *hints, struct addrinfo **res); +void at_freeaddrinfo(struct addrinfo *ai); + +struct at_socket *at_get_socket(int socket); +void at_scoket_device_register(const struct at_device_ops *ops); + +#ifndef RT_USING_SAL + +#define socket(domain, type, protocol) at_socket(domain, type, protocol) +#define closescoket(socket) at_closesocket(socket) +#define shutdown(socket, how) at_shutdown(socket, how) +#define bind(socket, name, namelen) at_bind(socket, name, namelen) +#define connect(socket, name, namelen) at_connect(socket, name, namelen) +#define sendto(socket, data, size, flags, to, tolen) at_sendto(socket, data, size, flags, to, tolen) +#define send(socket, data, size, flags) at_send(socket, data, size, flags) +#define recvfrom(socket, mem, len, flags, from, fromlen) at_recvfrom(socket, mem, len, flags, from, fromlen) +#define getsockopt(socket, level, optname, optval, optlen) at_getsockopt(socket, level, optname, optval, optlen) +#define setsockopt(socket, level, optname, optval, optlen) at_setsockopt(socket, level, optname, optval, optlen) + +#define gethostbyname(name) at_gethostbyname(name) +#define getaddrinfo(nodename, servname, hints, res) at_getaddrinfo(nodename, servname, hints, res) +#define freeaddrinfo(ai) at_freeaddrinfo(ai) + +#endif /* RT_USING_SAL */ + +#endif /* AT_SOCKET_H__ */ diff --git a/components/net/at/include/at.h b/components/net/at/include/at.h new file mode 100644 index 0000000000..2c866891d2 --- /dev/null +++ b/components/net/at/include/at.h @@ -0,0 +1,256 @@ +/* + * File : at.h + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-03-30 chenyong first version + */ + +#ifndef __AT_H__ +#define __AT_H__ + +#include + +#define AT_SW_VERSION "0.2.1" + +#define DBG_ENABLE +#define DBG_SECTION_NAME "AT" +#ifdef AT_DEBUG +#define DBG_LEVEL DBG_LOG +#else +#define DBG_LEVEL DBG_INFO +#endif /* AT_DEBUG */ +#define DBG_COLOR +#include + +#define AT_CMD_NAME_LEN 16 +#define AT_END_MARK_LEN 4 + +#ifndef AT_CMD_MAX_LEN +#define AT_CMD_MAX_LEN 128 +#endif + +/* client receive idle timeout, client will wait this timeout when send data, unit: ms */ +#ifndef AT_CLIENT_RECV_IDEL +#define AT_CLIENT_RECV_IDEL 50 +#endif + +/* the server AT commands new line sign */ +#if defined(AT_CMD_END_MARK_CRLF) +#define AT_CMD_END_MARK "\r\n" +#elif defined(AT_CMD_END_MARK_CR) +#define AT_CMD_END_MARK "\r" +#elif defined(AT_CMD_END_MARK_LF) +#define AT_CMD_END_MARK "\n" +#endif + +#ifndef AT_SERVER_RECV_BUFF_LEN +#define AT_SERVER_RECV_BUFF_LEN 256 +#endif + +#ifndef AT_CLIENT_RECV_BUFF_LEN +#define AT_CLIENT_RECV_BUFF_LEN 512 +#endif + +#ifndef AT_SERVER_DEVICE +#define AT_SERVER_DEVICE "uart2" +#endif + +#ifndef AT_CLIENT_DEVICE +#define AT_CLIENT_DEVICE "uart2" +#endif + +#define AT_CMD_EXPORT(_name_, _args_expr_, _test_, _query_, _setup_, _exec_) \ + RT_USED static const struct at_cmd __at_cmd_##_test_##_query_##_setup_##_exec_ SECTION("RtAtCmdTab") = \ + { \ + _name_, \ + _args_expr_, \ + _test_, \ + _query_, \ + _setup_, \ + _exec_, \ + }; + +enum at_status +{ + AT_STATUS_UNINITIALIZED = 0, + AT_STATUS_INITIALIZED, + AT_STATUS_BUSY, +}; +typedef enum at_status at_status_t; + +#ifdef AT_USING_SERVER +enum at_result +{ + AT_RESULT_OK = 0, /* AT result is no error */ + AT_RESULT_FAILE = -1, /* AT result have a generic error */ + AT_RESULT_NULL = -2, /* AT result not need return */ + AT_RESULT_CMD_ERR = -3, /* AT command format error or No way to execute */ + AT_RESULT_CHECK_FAILE = -4, /* AT command expression format is error */ + AT_RESULT_PARSE_FAILE = -5, /* AT command arguments parse is error */ +}; +typedef enum at_result at_result_t; + +struct at_cmd +{ + char name[AT_CMD_NAME_LEN]; + char *args_expr; + at_result_t (*test)(void); + at_result_t (*query)(void); + at_result_t (*setup)(const char *args); + at_result_t (*exec)(void); +}; +typedef struct at_cmd *at_cmd_t; + +struct at_server +{ + rt_device_t device; + + at_status_t status; + char (*get_char)(void); + rt_bool_t echo_mode; + + char recv_buffer[AT_SERVER_RECV_BUFF_LEN]; + rt_size_t cur_recv_len; + rt_sem_t rx_notice; + char end_mark[AT_END_MARK_LEN]; + + rt_thread_t parser; + void (*parser_entry)(struct at_server *server); +}; + +typedef struct at_server *at_server_t; +#endif /* AT_USING_SERVER */ + +#ifdef AT_USING_CLIENT +enum at_resp_status +{ + AT_RESP_OK = 0, /* AT response end is OK */ + AT_RESP_ERROR = -1, /* AT response end is ERROR */ + AT_RESP_TIMEOUT = -2, /* AT response is timeout */ + AT_RESP_BUFF_FULL= -3, /* AT response buffer is full */ +}; +typedef enum at_resp_status at_resp_status_t; + +struct at_response +{ + /* response buffer */ + char *buf; + /* the maximum response buffer size */ + rt_size_t buf_size; + /* the number of setting response lines + * == 0: the response data will auto return when received 'OK' or 'ERROR' + * != 0: the response data will return when received setting lines number data */ + rt_size_t line_num; + /* the count of received response lines */ + rt_size_t line_counts; + /* the maximum response time */ + rt_int32_t timeout; +}; + +typedef struct at_response *at_response_t; + +/* URC(Unsolicited Result Code) object, such as: 'RING', 'READY' request by AT server */ +struct at_urc +{ + const char *cmd_prefix; + const char *cmd_suffix; + void (*func)(const char *data, rt_size_t size); +}; +typedef struct at_urc *at_urc_t; + +struct at_client +{ + rt_device_t device; + + at_status_t status; + + char recv_buffer[AT_CLIENT_RECV_BUFF_LEN]; + rt_size_t cur_recv_len; + rt_sem_t rx_notice; + rt_mutex_t lock; + + at_response_t resp; + rt_sem_t resp_notice; + at_resp_status_t resp_status; + + const struct at_urc *urc_table; + rt_size_t urc_table_size; + + rt_thread_t parser; +}; + +typedef struct at_client *at_client_t; +#endif /* AT_USING_CLIENT */ + +#ifdef AT_USING_SERVER +/* AT server initialize and start */ +int at_server_init(void); + +/* AT server send command execute result to AT device */ +void at_server_printf(const char *format, ...); +void at_server_printfln(const char *format, ...); +void at_server_print_result(at_result_t result); + +/* AT server request arguments parse */ +int at_req_parse_args(const char *req_args, const char *req_expr, ...); +#endif /* AT_USING_SERVER */ + +#ifdef AT_USING_CLIENT +/* AT client initialize and start */ +int at_client_init(void); + +/* AT client send commands to AT server and waiter response */ +int at_exec_cmd(at_response_t resp, const char *cmd_expr, ...); + +/* AT Client send or receive data */ +rt_size_t at_client_send(const char *buf, rt_size_t size); +rt_size_t at_client_recv(char *buf, rt_size_t size); + +/* AT response structure create and delete */ +at_response_t at_create_resp(rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout); +void at_delete_resp(at_response_t resp); +at_response_t at_resp_set_info(at_response_t resp, rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout); + +/* AT response line buffer get and parse response buffer arguments */ +const char *at_resp_get_line(at_response_t resp, rt_size_t resp_line); +const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword); +int at_resp_parse_line_args(at_response_t resp, rt_size_t resp_line, const char *resp_expr, ...); +int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...); + +/* Set URC(Unsolicited Result Code) table */ +void at_set_urc_table(const struct at_urc * table, rt_size_t size); +#endif /* AT_USING_CLIENT */ + +/* ========================== User port function ============================ */ + +#ifdef AT_USING_SERVER +/* AT server device reset */ +void at_port_reset(void); + +/* AT server device factory reset */ +void at_port_factory_reset(void); +#endif + +#ifdef AT_USING_CLIENT +/* AT client port initialization */ +int at_client_port_init(void); +#endif + +#endif /* __AT_H__ */ diff --git a/components/net/at/src/at_base_cmd.c b/components/net/at/src/at_base_cmd.c new file mode 100644 index 0000000000..b334b1dba4 --- /dev/null +++ b/components/net/at/src/at_base_cmd.c @@ -0,0 +1,131 @@ +/* + * File : at_base_cmd.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-04-01 armink first version + * 2018-04-04 chenyong add base commands + */ + +#include +#include +#include + +#include + +#define AT_ECHO_MODE_CLOSE 0 +#define AT_ECHO_MODE_OPEN 1 + +extern at_server_t at_get_server(void); + +static at_result_t at_exec(void) +{ + return AT_RESULT_OK; +} +AT_CMD_EXPORT("AT", RT_NULL, RT_NULL, RT_NULL, RT_NULL, at_exec); + +static at_result_t atz_exec(void) +{ + at_server_printfln("OK"); + + at_port_factory_reset(); + + return AT_RESULT_NULL; +} +AT_CMD_EXPORT("ATZ", RT_NULL, RT_NULL, RT_NULL, RT_NULL, atz_exec); + +static at_result_t at_rst_exec(void) +{ + at_server_printfln("OK"); + + at_port_reset(); + + return AT_RESULT_NULL; +} +AT_CMD_EXPORT("AT+RST", RT_NULL, RT_NULL, RT_NULL, RT_NULL, at_rst_exec); + +static at_result_t ate_setup(const char *args) +{ + int echo_mode = atoi(args); + + if(echo_mode == AT_ECHO_MODE_CLOSE || echo_mode == AT_ECHO_MODE_OPEN) + { + at_get_server()->echo_mode = echo_mode; + } + else + { + return AT_RESULT_FAILE; + } + + return AT_RESULT_OK; +} +AT_CMD_EXPORT("ATE", "", RT_NULL, RT_NULL, ate_setup, RT_NULL); + +static at_result_t at_show_cmd_exec(void) +{ + extern void rt_at_server_print_all_cmd(void); + + rt_at_server_print_all_cmd(); + + return AT_RESULT_OK; +} +AT_CMD_EXPORT("AT&L", RT_NULL, RT_NULL, RT_NULL, RT_NULL, at_show_cmd_exec); + +static at_result_t at_uart_query(void) +{ + struct rt_serial_device *serial = (struct rt_serial_device *)at_get_server()->device; + + at_server_printfln("AT+UART=%d,%d,%d,%d,%d", serial->config.baud_rate, serial->config.data_bits, + serial->config.stop_bits, serial->config.parity, 1); + + return AT_RESULT_OK; +} + +static at_result_t at_uart_setup(const char *args) +{ + struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; + int baudrate, databits, stopbits, parity, flow_control, argc; + const char *req_expr = "=%d,%d,%d,%d,%d"; + + argc = at_req_parse_args(args, req_expr, &baudrate, &databits, &stopbits, &parity, &flow_control); + if (argc != 5) + { + return AT_RESULT_PARSE_FAILE; + } + + at_server_printfln("UART baudrate : %d", baudrate); + at_server_printfln("UART databits : %d", databits); + at_server_printfln("UART stopbits : %d", stopbits); + at_server_printfln("UART parity : %d", parity); + at_server_printfln("UART control : %d", flow_control); + + config.baud_rate = baudrate; + config.data_bits = databits; + config.stop_bits = stopbits; + config.parity = parity; + + if(rt_device_control(at_get_server()->device, RT_DEVICE_CTRL_CONFIG, &config) != RT_EOK) + { + return AT_RESULT_FAILE; + } + + return AT_RESULT_OK; +} + +AT_CMD_EXPORT("AT+UART", "=,,,,", RT_NULL, at_uart_query, at_uart_setup, RT_NULL); diff --git a/components/net/at/src/at_cli.c b/components/net/at/src/at_cli.c new file mode 100644 index 0000000000..0136922199 --- /dev/null +++ b/components/net/at/src/at_cli.c @@ -0,0 +1,332 @@ +/* + * File : at_cli.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-04-02 armink first version + */ + +#include +#include +#include + +#include +#include +#include + +#ifdef AT_USING_CLI + +#define AT_CLI_FIFO_SIZE 256 + +static struct rt_semaphore console_rx_notice; +static struct rt_ringbuffer *console_rx_fifo = RT_NULL; +static rt_err_t (*odev_rx_ind)(rt_device_t dev, rt_size_t size) = RT_NULL; + +#ifdef AT_USING_CLIENT +static struct rt_semaphore client_rx_notice; +static struct rt_ringbuffer *client_rx_fifo = RT_NULL; +#endif + +static char console_getchar(void) +{ + char ch; + + rt_sem_take(&console_rx_notice, RT_WAITING_FOREVER); + rt_ringbuffer_getchar(console_rx_fifo, (rt_uint8_t *)&ch); + + return ch; +} + +static rt_err_t console_getchar_rx_ind(rt_device_t dev, rt_size_t size) +{ + uint8_t ch; + rt_size_t i; + + for (i = 0; i < size; i++) + { + /* read a char */ + if (rt_device_read(dev, 0, &ch, 1)) + { + rt_ringbuffer_put_force(console_rx_fifo, &ch, 1); + rt_sem_release(&console_rx_notice); + } + } + + return RT_EOK; +} + +void at_cli_init(void) +{ + rt_base_t int_lvl; + rt_device_t console; + + rt_sem_init(&console_rx_notice, "at_cli_notice", 0, RT_IPC_FLAG_FIFO); + + /* create RX FIFO */ + console_rx_fifo = rt_ringbuffer_create(AT_CLI_FIFO_SIZE); + /* created must success */ + RT_ASSERT(console_rx_fifo); + + int_lvl = rt_hw_interrupt_disable(); + console = rt_console_get_device(); + if (console) + { + /* backup RX indicate */ + odev_rx_ind = console->rx_indicate; + rt_device_set_rx_indicate(console, console_getchar_rx_ind); + } + + rt_hw_interrupt_enable(int_lvl); +} + +void at_cli_deinit(void) +{ + rt_base_t int_lvl; + rt_device_t console; + + rt_sem_detach(&console_rx_notice); + rt_ringbuffer_destroy(console_rx_fifo); + + int_lvl = rt_hw_interrupt_disable(); + console = rt_console_get_device(); + if (console && odev_rx_ind) + { + /* restore RX indicate */ + rt_device_set_rx_indicate(console, odev_rx_ind); + } + rt_hw_interrupt_enable(int_lvl); +} + +#ifdef AT_USING_SERVER +static void server_cli_parser(void) +{ + extern at_server_t at_get_server(void); + + at_server_t server = at_get_server(); + rt_base_t int_lvl; + static rt_device_t device_bak; + static char (*getchar_bak)(void); + static char endmark_back[AT_END_MARK_LEN]; + + /* backup server device and getchar function */ + { + int_lvl = rt_hw_interrupt_disable(); + + device_bak = server->device; + getchar_bak = server->get_char; + + memset(endmark_back, 0x00, AT_END_MARK_LEN); + memcpy(endmark_back, server->end_mark, strlen(server->end_mark)); + + /* setup server device as console device */ + server->device = rt_console_get_device(); + server->get_char = console_getchar; + + memset(server->end_mark, 0x00, AT_END_MARK_LEN); + server->end_mark[0] = '\r'; + + rt_hw_interrupt_enable(int_lvl); + } + + if (server) + { + rt_kprintf("======== Welcome to using RT-Thread AT command server cli ========\n"); + rt_kprintf("Input your at command for test server. Press 'ESC' to exit.\n"); + server->parser_entry(server); + } + else + { + rt_kprintf("AT client not initialized\n"); + } + + /* restore server device and getchar function */ + { + int_lvl = rt_hw_interrupt_disable(); + + server->device = device_bak; + server->get_char = getchar_bak; + + memset(server->end_mark, 0x00, AT_END_MARK_LEN); + memcpy(server->end_mark, endmark_back, strlen(endmark_back)); + + rt_hw_interrupt_enable(int_lvl); + } +} +#endif /* AT_USING_SERVER */ + +#ifdef AT_USING_CLIENT +static char client_getchar(void) +{ + char ch; + + rt_sem_take(&client_rx_notice, RT_WAITING_FOREVER); + rt_ringbuffer_getchar(client_rx_fifo, (rt_uint8_t *)&ch); + + return ch; +} + +static void at_client_entry(void *param) +{ + char ch; + + while(1) + { + ch = client_getchar(); + rt_kprintf("%c", ch); + } +} + +static rt_err_t client_getchar_rx_ind(rt_device_t dev, rt_size_t size) +{ + uint8_t ch; + rt_size_t i; + + for (i = 0; i < size; i++) + { + /* read a char */ + if (rt_device_read(dev, 0, &ch, 1)) + { + rt_ringbuffer_put_force(client_rx_fifo, &ch, 1); + rt_sem_release(&client_rx_notice); + } + } + + return RT_EOK; +} +static void client_cli_parser(void) +{ +#define ESC_KEY 0x1B +#define BACKSPACE_KEY 0x08 +#define DELECT_KEY 0x7F + + extern at_client_t rt_at_get_client(void); + at_client_t client = rt_at_get_client(); + char ch; + char cur_line[FINSH_CMD_SIZE] = { 0 }; + rt_size_t cur_line_len = 0; + static rt_err_t (*client_odev_rx_ind)(rt_device_t dev, rt_size_t size) = RT_NULL; + rt_base_t int_lvl; + rt_thread_t at_client; + + if (client) + { + /* backup client device RX indicate */ + { + int_lvl = rt_hw_interrupt_disable(); + client_odev_rx_ind = client->device->rx_indicate; + rt_device_set_rx_indicate(client->device, client_getchar_rx_ind); + rt_hw_interrupt_enable(int_lvl); + } + + rt_sem_init(&client_rx_notice, "at_cli_client_notice", 0, RT_IPC_FLAG_FIFO); + client_rx_fifo = rt_ringbuffer_create(AT_CLI_FIFO_SIZE); + + at_client = rt_thread_create("at_cli_client", at_client_entry, RT_NULL, 512, 8, 8); + if (client_rx_fifo && at_client) + { + rt_kprintf("======== Welcome to using RT-Thread AT command client cli ========\n"); + rt_kprintf("Cli will forward your command to server port(%s). Press 'ESC' to exit.\n", client->device->parent.name); + rt_thread_startup(at_client); + /* process user input */ + while (ESC_KEY != (ch = console_getchar())) + { + if (ch == BACKSPACE_KEY || ch == DELECT_KEY) + { + if (cur_line_len) + { + cur_line[--cur_line_len] = 0; + rt_kprintf("\b \b"); + } + continue; + } + else if (ch == '\r' || ch == '\n') + { + /* execute a AT request */ + if (cur_line_len) + { + rt_kprintf("\n"); + at_exec_cmd(RT_NULL, "%.*s", cur_line_len, cur_line); + } + cur_line_len = 0; + } + else + { + rt_kprintf("%c", ch); + cur_line[cur_line_len++] = ch; + } + } + + rt_thread_delete(at_client); + rt_sem_detach(&client_rx_notice); + rt_ringbuffer_destroy(client_rx_fifo); + /* restore client device RX indicate */ + { + int_lvl = rt_hw_interrupt_disable(); + rt_device_set_rx_indicate(client->device, client_odev_rx_ind); + rt_hw_interrupt_enable(int_lvl); + } + } + else + { + rt_kprintf("No mem for AT cli client\n"); + } + } + else + { + rt_kprintf("AT client not initialized\n"); + } +} +#endif /* AT_USING_CLIENT */ + +static void at(int argc, char **argv) +{ + if (argc < 2) + { + rt_kprintf("Please input 'at ' \n"); + return; + } + + at_cli_init(); + + if (!strcmp(argv[1], "server")) + { +#ifdef AT_USING_SERVER + server_cli_parser(); +#else + rt_kprintf("Not support AT server, please check your configure!\n"); +#endif + } + else if (!strcmp(argv[1], "client")) + { +#ifdef AT_USING_CLIENT + client_cli_parser(); +#else + rt_kprintf("Not support AT client, please check your configure!\n"); +#endif + } + else + { + rt_kprintf("Please input 'at ' \n"); + } + + at_cli_deinit(); +} +MSH_CMD_EXPORT(at, RT-Thread AT component cli: at ); + +#endif /* AT_USING_CLI */ diff --git a/components/net/at/src/at_client.c b/components/net/at/src/at_client.c new file mode 100644 index 0000000000..f6145232b3 --- /dev/null +++ b/components/net/at/src/at_client.c @@ -0,0 +1,716 @@ +/* + * File : at_client.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-03-30 chenyong first version + * 2018-04-12 chenyong add client implement + */ + +#include +#include +#include +#include + +#define AT_RESP_END_OK "OK" +#define AT_RESP_END_ERROR "ERROR" +#define AT_RESP_END_FAIL "FAIL" +#define AT_END_CR_LF "\r\n" + +static at_client_t at_client_local = RT_NULL; +static char cust_end_sign = 0; + +extern rt_size_t at_vprintfln(rt_device_t device, const char *format, va_list args); +extern void at_print_raw_cmd(const char *type, const char *cmd, rt_size_t size); +extern const char *at_get_last_cmd(rt_size_t *cmd_size); + +/** + * Create response structure. + * + * @param buf_size the maximum response buffer size + * @param line_num the number of setting response lines + * = 0: the response data will auto return when received 'OK' or 'ERROR' + * != 0: the response data will return when received setting lines number data + * @param timeout the maximum response time + * + * @return != RT_NULL: response structure + * = RT_NULL: no memory + */ +at_response_t at_create_resp(rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout) +{ + at_response_t resp = RT_NULL; + + resp = (at_response_t) rt_calloc(1, sizeof(struct at_response)); + if (!resp) + { + LOG_E("AT create response structure failed! No memory for response structure!"); + return RT_NULL; + } + + resp->buf = (char *) rt_calloc(1, buf_size); + if (!resp->buf) + { + LOG_E("AT create response structure failed! No memory for response buf structure!"); + rt_free(resp); + return RT_NULL; + } + + resp->buf_size = buf_size; + resp->line_num = line_num; + resp->line_counts = 0; + resp->timeout = timeout; + + return resp; +} + +/** + * Delete and free response structure. + * + * @param resp response structure + */ +void at_delete_resp(at_response_t resp) +{ + if (resp && resp->buf) + { + rt_free(resp->buf); + } + + if (resp) + { + rt_free(resp); + resp = RT_NULL; + } +} + +/** + * Set response structure information + * + * @param resp response structure + * @param buf_size the maximum response buffer size + * @param line_num the number of setting response lines + * = 0: the response data will auto return when received 'OK' or 'ERROR' + * != 0: the response data will return when received setting lines number data + * @param timeout the maximum response time + * + * @return != RT_NULL: response structure + * = RT_NULL: no memory + */ +at_response_t at_resp_set_info(at_response_t resp, rt_size_t buf_size, rt_size_t line_num, rt_int32_t timeout) +{ + RT_ASSERT(resp); + + if(resp->buf_size != buf_size) + { + resp->buf_size = buf_size; + + resp->buf = rt_realloc(resp->buf, buf_size); + if(!resp->buf) + { + LOG_D("No memory for realloc response buffer size(%d).", buf_size); + return RT_NULL; + } + } + + resp->line_num = line_num; + resp->timeout = timeout; + + return resp; +} + +/** + * Get one line AT response buffer by line number. + * + * @param resp response structure + * @param resp_line line number, start from '1' + * + * @return != RT_NULL: response line buffer + * = RT_NULL: input response line error + */ +const char *at_resp_get_line(at_response_t resp, rt_size_t resp_line) +{ + char *resp_buf = resp->buf; + char *resp_line_buf = RT_NULL; + rt_size_t line_num = 1; + + RT_ASSERT(resp); + + if (resp_line > resp->line_counts || resp_line <= 0) + { + LOG_E("AT response get line failed! Input response line(%d) error!", resp_line); + return RT_NULL; + } + + for (line_num = 1; line_num <= resp->line_counts; line_num++) + { + if (resp_line == line_num) + { + resp_line_buf = resp_buf; + + return resp_line_buf; + } + + resp_buf += strlen(resp_buf) + 1; + } + + return RT_NULL; +} + +/** + * Get one line AT response buffer by keyword + * + * @param resp response structure + * @param keyword query keyword + * + * @return != RT_NULL: response line buffer + * = RT_NULL: no matching data + */ +const char *at_resp_get_line_by_kw(at_response_t resp, const char *keyword) +{ + char *resp_buf = resp->buf; + char *resp_line_buf = RT_NULL; + rt_size_t line_num = 1; + + RT_ASSERT(resp); + RT_ASSERT(keyword); + + for (line_num = 1; line_num <= resp->line_counts; line_num++) + { + if(strstr(resp_buf, keyword)) + { + resp_line_buf = resp_buf; + + return resp_line_buf; + } + + resp_buf += strlen(resp_buf) + 1; + } + + return RT_NULL; +} + +/** + * Get and parse AT response buffer arguments by line number. + * + * @param resp response structure + * @param resp_line line number, start from '1' + * @param resp_expr response buffer expression + * + * @return -1 : input response line number error or get line buffer error + * 0 : parsed without match + * >0 : the number of arguments successfully parsed + */ +int at_resp_parse_line_args(at_response_t resp, rt_size_t resp_line, const char *resp_expr, ...) +{ + va_list args; + int resp_args_num = 0; + const char *resp_line_buf = RT_NULL; + + RT_ASSERT(resp); + RT_ASSERT(resp_expr); + + if ((resp_line_buf = at_resp_get_line(resp, resp_line)) == RT_NULL) + { + return -1; + } + + va_start(args, resp_expr); + + resp_args_num = vsscanf(resp_line_buf, resp_expr, args); + + va_end(args); + + return resp_args_num; +} + +/** + * Get and parse AT response buffer arguments by keyword. + * + * @param resp response structure + * @param keyword query keyword + * @param resp_expr response buffer expression + * + * @return -1 : input keyword error or get line buffer error + * 0 : parsed without match + * >0 : the number of arguments successfully parsed + */ +int at_resp_parse_line_args_by_kw(at_response_t resp, const char *keyword, const char *resp_expr, ...) +{ + va_list args; + int resp_args_num = 0; + const char *resp_line_buf = RT_NULL; + + RT_ASSERT(resp); + RT_ASSERT(resp_expr); + + if ((resp_line_buf = at_resp_get_line_by_kw(resp, keyword)) == RT_NULL) + { + return -1; + } + + va_start(args, resp_expr); + + resp_args_num = vsscanf(resp_line_buf, resp_expr, args); + + va_end(args); + + return resp_args_num; +} + +/** + * Send commands to AT server and wait response. + * + * @param resp AT response structure, using RT_NULL when you don't care response + * @param cmd_expr AT commands expression + * + * @return 0 : success + * -1 : response status error + * -2 : wait timeout + */ +int at_exec_cmd(at_response_t resp, const char *cmd_expr, ...) +{ + at_client_t client = at_client_local; + va_list args; + rt_size_t cmd_size = 0; + rt_err_t result = RT_EOK; + const char *cmd = RT_NULL; + + RT_ASSERT(cmd_expr); + + rt_mutex_take(client->lock, RT_WAITING_FOREVER); + + client->resp_status = AT_RESP_OK; + client->resp = resp; + + va_start(args, cmd_expr); + at_vprintfln(client->device, cmd_expr, args); + va_end(args); + + if (resp) + { + resp->line_counts = 0; + if (rt_sem_take(client->resp_notice, resp->timeout) != RT_EOK) + { + cmd = at_get_last_cmd(&cmd_size); + LOG_E("execute command (%.*s) timeout (%d ticks)!", cmd_size, cmd, resp->timeout); + client->resp_status = AT_RESP_TIMEOUT; + result = -RT_ETIMEOUT; + goto __exit; + } + if (client->resp_status != AT_RESP_OK) + { + cmd = at_get_last_cmd(&cmd_size); + LOG_E("execute command (%.*s) failed!", cmd_size, cmd); + result = -RT_ERROR; + goto __exit; + } + } + +__exit: + client->resp = RT_NULL; + + rt_mutex_release(client->lock); + + return result; +} + +/** + * Send data to AT server, send data don't have end sign(eg: \r\n). + * + * @param buf send data buffer + * @param size send fixed data size + * + * @return send data size + */ +rt_size_t at_client_send(const char *buf, rt_size_t size) +{ + at_client_t client = at_client_local; + + RT_ASSERT(buf); + +#ifdef AT_PRINT_RAW_CMD + at_print_raw_cmd("send", buf, size); +#endif + + return rt_device_write(client->device, 0, buf, size); + +} + +static char at_client_getchar(void) +{ + char ch; + at_client_t client = at_client_local; + + rt_sem_take(client->rx_notice, RT_WAITING_FOREVER); + + rt_device_read(client->device, 0, &ch, 1); + + return ch; +} + +/** + * AT client receive fixed-length data. + * + * @param buf receive data buffer + * @param size receive fixed data size + * + * @note this function can only be used in execution function of URC data + * + * @return success receive data size + */ +rt_size_t at_client_recv(char *buf, rt_size_t size) +{ + rt_size_t read_idx = 0; + char ch; + + RT_ASSERT(buf); + + while (1) + { + if (read_idx < size) + { + ch = at_client_getchar(); + + buf[read_idx++] = ch; + } + else + { + break; + } + } + +#ifdef AT_PRINT_RAW_CMD + at_print_raw_cmd("urc_recv", buf, size); +#endif + + return read_idx; + +} + +/** + * get AT client structure pointer. + * + * @return AT client structure pointer + */ +at_client_t rt_at_get_client(void) +{ + RT_ASSERT(at_client_local); + RT_ASSERT(at_client_local->status != AT_STATUS_UNINITIALIZED); + + return at_client_local; +} + +/** + * AT client set end sign. + * + * @param ch the end sign, can not be used when it is '\0' + * + * @return 0: set success + */ +int at_set_end_sign(char ch) +{ + cust_end_sign = ch; + + return 0; +} + +static const struct at_urc *get_urc_obj(char *data, rt_size_t size) +{ + rt_size_t i, prefix_len, suffix_len; + at_client_t client = at_client_local; + + if (client->urc_table == RT_NULL) + { + return RT_NULL; + } + + for (i = 0; i < client->urc_table_size; i++) + { + prefix_len = strlen(client->urc_table[i].cmd_prefix); + suffix_len = strlen(client->urc_table[i].cmd_suffix); + if (size < prefix_len + suffix_len) + { + continue; + } + if ((prefix_len ? !strncmp(data, client->urc_table[i].cmd_prefix, prefix_len) : 1) + && (suffix_len ? !strncmp(data + size - suffix_len, client->urc_table[i].cmd_suffix, suffix_len) : 1)) + { + return &client->urc_table[i]; + } + } + + return RT_NULL; +} + +static int at_recv_readline(void) +{ + rt_size_t read_len = 0; + char ch = 0, last_ch = 0; + rt_bool_t is_full = RT_FALSE; + at_client_t client = at_client_local; + + memset(client->recv_buffer, 0x00, AT_CLIENT_RECV_BUFF_LEN); + client->cur_recv_len = 0; + + while (1) + { + ch = at_client_getchar(); + + if (read_len < AT_CLIENT_RECV_BUFF_LEN) + { + client->recv_buffer[read_len++] = ch; + } + else + { + is_full = RT_TRUE; + } + + /* is newline or URC data */ + if ((ch == '\n' && last_ch == '\r') || (cust_end_sign != 0 && ch == cust_end_sign) + || get_urc_obj(client->recv_buffer, read_len)) + { + if (is_full) + { + LOG_E("read line failed. The line data length is out of buffer size(%d)!", AT_CLIENT_RECV_BUFF_LEN); + memset(client->recv_buffer, 0x00, AT_CLIENT_RECV_BUFF_LEN); + client->cur_recv_len = 0; + return -RT_EFULL; + } + client->cur_recv_len = read_len; + break; + } + last_ch = ch; + } + +#ifdef AT_PRINT_RAW_CMD + at_print_raw_cmd("recvline", client->recv_buffer, read_len); +#endif + + return read_len; +} + +static void client_parser(at_client_t client) +{ + int resp_buf_len = 0; + const struct at_urc *urc; + rt_size_t line_counts = 0; + + while(1) + { + if (at_recv_readline() > 0) + { + if ((urc = get_urc_obj(client->recv_buffer, client->cur_recv_len)) != RT_NULL) + { + /* current receive is request, try to execute related operations */ + if (urc->func != RT_NULL) + { + urc->func(client->recv_buffer, client->cur_recv_len); + } + } + else if (client->resp != RT_NULL) + { + /* current receive is response */ + client->recv_buffer[client->cur_recv_len - 1] = '\0'; + if (resp_buf_len + client->cur_recv_len < client->resp->buf_size) + { + /* copy response lines, separated by '\0' */ + memcpy(client->resp->buf + resp_buf_len, client->recv_buffer, client->cur_recv_len); + resp_buf_len += client->cur_recv_len; + + line_counts++; + } + else + { + client->resp_status = AT_RESP_BUFF_FULL; + LOG_E("Read response buffer failed. The Response buffer size is out of buffer size(%d)!", client->resp->buf_size); + } + /* check response result */ + if (memcmp(client->recv_buffer, AT_RESP_END_OK, strlen(AT_RESP_END_OK)) == 0 + && client->resp->line_num == 0) + { + /* get the end data by response result, return response state END_OK. */ + client->resp_status = AT_RESP_OK; + } + else if ((memcmp(client->recv_buffer, AT_RESP_END_ERROR, strlen(AT_RESP_END_ERROR)) == 0) + || (memcmp(client->recv_buffer, AT_RESP_END_FAIL, strlen(AT_RESP_END_FAIL)) == 0)) + { + client->resp_status = AT_RESP_ERROR; + } + else if (line_counts == client->resp->line_num && client->resp->line_num) + { + /* get the end data by response line, return response state END_OK.*/ + client->resp_status = AT_RESP_OK; + } + else + { + continue; + } + client->resp->line_counts = line_counts; + + client->resp = RT_NULL; + rt_sem_release(client->resp_notice); + resp_buf_len = 0, line_counts = 0; + } + else + { +// log_d("unrecognized line: %.*s", client->cur_recv_len, client->recv_buffer); + } + } + } +} + +static rt_err_t at_client_rx_ind(rt_device_t dev, rt_size_t size) +{ + rt_sem_release(at_client_local->rx_notice); + return RT_EOK; +} + +/** + * Set URC(Unsolicited Result Code) table + * + * @param table URC table + * @param size table size + */ +void at_set_urc_table(const struct at_urc *table, rt_size_t size) +{ + rt_size_t idx; + + for(idx = 0; idx < size; idx++) + { + RT_ASSERT(table[idx].cmd_prefix); + RT_ASSERT(table[idx].cmd_suffix); + } + + at_client_local->urc_table = table; + at_client_local->urc_table_size = size; + +} + +int at_client_init(void) +{ + int result = RT_EOK; + + if (at_client_local) + { + return result; + } + + at_client_local = (at_client_t) rt_calloc(1, sizeof(struct at_client)); + if (!at_client_local) + { + result = -RT_ERROR; + LOG_E("AT client session initialize failed! No memory for at_client structure !"); + goto __exit; + } + + at_client_local->status = AT_STATUS_UNINITIALIZED; + at_client_local->lock = rt_mutex_create("at_lock", RT_IPC_FLAG_FIFO); + if(!at_client_local->lock) + { + LOG_E("AT client session initialize failed! at_client_recv_lock create failed!"); + result = -RT_ENOMEM; + goto __exit; + } + + at_client_local->cur_recv_len = 0; + + at_client_local->rx_notice = rt_sem_create("at_client_notice", 0, RT_IPC_FLAG_FIFO); + if (!at_client_local->rx_notice) + { + LOG_E("AT client session initialize failed! at_client_notice semaphore create failed!"); + result = -RT_ENOMEM; + goto __exit; + } + + at_client_local->resp_notice = rt_sem_create("at_client_resp", 0, RT_IPC_FLAG_FIFO); + if (!at_client_local->resp_notice) + { + LOG_E("AT client session initialize failed! at_client_resp semaphore create failed!"); + result = -RT_ENOMEM; + goto __exit; + } + + /* Find and open command device */ + at_client_local->device = rt_device_find(AT_CLIENT_DEVICE); + if (at_client_local->device) + { + RT_ASSERT(at_client_local->device->type == RT_Device_Class_Char); + + rt_device_open(at_client_local->device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX); + + rt_device_set_rx_indicate(at_client_local->device, at_client_rx_ind); + } + else + { + LOG_E("AT client device initialize failed! Not find the device : %s.", AT_CLIENT_DEVICE); + result = -RT_ERROR; + goto __exit; + } + + at_client_local->urc_table = RT_NULL; + at_client_local->urc_table_size = 0; + + at_client_local->parser = rt_thread_create("at_client", + (void (*)(void *parameter))client_parser, + at_client_local, + 1024 + 512, + RT_THREAD_PRIORITY_MAX / 3 - 1, + 5); + if (at_client_local->parser == RT_NULL) + { + result = -RT_ENOMEM; + goto __exit; + } + + if ((result = at_client_port_init()) != RT_EOK) + { + LOG_E("AT client port initialize failed(%d).", result); + } + +__exit: + if (!result) + { + at_client_local->status = AT_STATUS_INITIALIZED; + + rt_thread_startup(at_client_local->parser); + + LOG_I("RT-Thread AT client (V%s) initialize success.", AT_SW_VERSION); + } + else + { + if (at_client_local) + { + rt_free(at_client_local); + } + + LOG_E("RT-Thread AT client (V%s) initialize failed(%d).", AT_SW_VERSION, result); + } + + return result; +} +INIT_COMPONENT_EXPORT(at_client_init); + +RT_WEAK int at_client_port_init(void) +{ + at_client_local->urc_table = RT_NULL; + at_client_local->urc_table_size = 0; + + LOG_E("The client porting initialize for AT client is not implement."); + + return 0; +} diff --git a/components/net/at/src/at_server.c b/components/net/at/src/at_server.c new file mode 100644 index 0000000000..a2ff765ee8 --- /dev/null +++ b/components/net/at/src/at_server.c @@ -0,0 +1,554 @@ +/* + * File : at_server.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-03-30 chenyong first version + * 2018-04-14 chenyong modify parse arguments + */ + +#include +#include +#include +#include + +#include + +#define AT_CMD_CHAR_0 '0' +#define AT_CMD_CHAR_9 '9' +#define AT_CMD_QUESTION_MARK '?' +#define AT_CMD_EQUAL_MARK '=' +#define AT_CMD_L_SQ_BRACKET '[' +#define AT_CMD_R_SQ_BRACKET ']' +#define AT_CMD_L_ANGLE_BRACKET '<' +#define AT_CMD_R_ANGLE_BRACKET '>' +#define AT_CMD_COMMA_MARK ',' +#define AT_CMD_SEMICOLON ';' +#define AT_CMD_CR '\r' +#define AT_CMD_LF '\n' + +static at_server_t at_server_local = RT_NULL; +static at_cmd_t cmd_table = RT_NULL; +static rt_size_t cmd_num; + +extern void at_vprintf(rt_device_t device, const char *format, va_list args); +extern void at_vprintfln(rt_device_t device, const char *format, va_list args); + +/** + * AT server send data to AT device + * + * @param format the input format + */ +void at_server_printf(const char *format, ...) +{ + va_list args; + + va_start(args, format); + + at_vprintf(at_server_local->device, format, args); + + va_end(args); +} + +/** + * AT server send data and newline to AT device + * + * @param format the input format + */ +void at_server_printfln(const char *format, ...) +{ + va_list args; + + va_start(args, format); + + at_vprintfln(at_server_local->device, format, args); + + va_end(args); +} + + +/** + * AT server request arguments parse arguments + * + * @param req_args request arguments + * @param req_expr request expression + * + * @return -1 : parse arguments failed + * 0 : parse without match + * >0 : The number of arguments successfully parsed + */ +int at_req_parse_args(const char *req_args, const char *req_expr, ...) +{ + va_list args; + int req_args_num = 0; + + RT_ASSERT(req_args); + RT_ASSERT(req_expr); + + va_start(args, req_expr); + + req_args_num = vsscanf(req_args, req_expr, args); + + va_end(args); + + return req_args_num; +} + +/** + * AT server send command execute result to AT device + * + * @param result AT command execute result + */ +void at_server_print_result(at_result_t result) +{ + switch (result) + { + case AT_RESULT_OK: + at_server_printfln(""); + at_server_printfln("OK"); + break; + + case AT_RESULT_FAILE: + at_server_printfln(""); + at_server_printfln("ERROR"); + break; + + case AT_RESULT_NULL: + break; + + case AT_RESULT_CMD_ERR: + at_server_printfln("ERR CMD MATCH FAILED!"); + at_server_print_result(AT_RESULT_FAILE); + break; + + case AT_RESULT_CHECK_FAILE: + at_server_printfln("ERR CHECK ARGS FORMAT FAILED!"); + at_server_print_result(AT_RESULT_FAILE); + break; + + case AT_RESULT_PARSE_FAILE: + at_server_printfln("ERR PARSE ARGS FAILED!"); + at_server_print_result(AT_RESULT_FAILE); + break; + + default: + break; + } +} + +/** + * AT server print all commands to AT device + */ +void rt_at_server_print_all_cmd(void) +{ + rt_size_t i = 0; + + at_server_printfln("Commands list : "); + + for (i = 0; i < cmd_num; i++) + { + at_server_printf("%s", cmd_table[i].name); + + if (cmd_table[i].args_expr) + { + at_server_printfln("%s", cmd_table[i].args_expr); + } + else + { + at_server_printf("%c%c", AT_CMD_CR, AT_CMD_LF); + } + } +} + +at_server_t at_get_server(void) +{ + RT_ASSERT(at_server_local); + RT_ASSERT(at_server_local->status != AT_STATUS_UNINITIALIZED); + + return at_server_local; +} + +static rt_err_t at_check_args(const char *args, const char *args_format) +{ + rt_size_t left_sq_bracket_num = 0, right_sq_bracket_num = 0; + rt_size_t left_angle_bracket_num = 0, right_angle_bracket_num = 0; + rt_size_t comma_mark_num = 0; + rt_size_t i = 0; + + RT_ASSERT(args); + RT_ASSERT(args_format); + + for (i = 0; i < strlen(args_format); i++) + { + switch (args_format[i]) + { + case AT_CMD_L_SQ_BRACKET: + left_sq_bracket_num++; + break; + + case AT_CMD_R_SQ_BRACKET: + right_sq_bracket_num++; + break; + + case AT_CMD_L_ANGLE_BRACKET: + left_angle_bracket_num++; + break; + + case AT_CMD_R_ANGLE_BRACKET: + right_angle_bracket_num++; + break; + + default: + break; + } + } + + if (left_sq_bracket_num != right_sq_bracket_num || left_angle_bracket_num != right_angle_bracket_num + || left_sq_bracket_num > left_angle_bracket_num) + { + return -RT_ERROR; + } + + for (i = 0; i < strlen(args); i++) + { + if (args[i] == AT_CMD_COMMA_MARK) + { + comma_mark_num++; + } + } + + if ((comma_mark_num + 1 < left_angle_bracket_num - left_sq_bracket_num) + || comma_mark_num + 1 > left_angle_bracket_num) + { + return -RT_ERROR; + } + + return RT_EOK; +} + +static rt_err_t at_cmd_process(at_cmd_t cmd, const char *cmd_args) +{ + at_result_t result = AT_RESULT_OK; + + RT_ASSERT(cmd); + RT_ASSERT(cmd_args); + + if (cmd_args[0] == AT_CMD_EQUAL_MARK && cmd_args[1] == AT_CMD_QUESTION_MARK && cmd_args[2] == AT_CMD_CR) + { + if (cmd->test == RT_NULL) + { + at_server_print_result(AT_RESULT_CMD_ERR); + return -RT_ERROR; + } + + result = cmd->test(); + at_server_print_result(result); + } + else if (cmd_args[0] == AT_CMD_QUESTION_MARK && cmd_args[1] == AT_CMD_CR) + { + if (cmd->query == RT_NULL) + { + at_server_print_result(AT_RESULT_CMD_ERR); + return -RT_ERROR; + } + + result = cmd->query(); + at_server_print_result(result); + } + else if (cmd_args[0] == AT_CMD_EQUAL_MARK + || (cmd_args[0] >= AT_CMD_CHAR_0 && cmd_args[0] <= AT_CMD_CHAR_9 && cmd_args[1] == AT_CMD_CR)) + { + if (cmd->setup == RT_NULL) + { + at_server_print_result(AT_RESULT_CMD_ERR); + return -RT_ERROR; + } + + if(at_check_args(cmd_args, cmd->args_expr) < 0) + { + at_server_print_result(AT_RESULT_CHECK_FAILE); + return -RT_ERROR; + } + + result = cmd->setup(cmd_args); + at_server_print_result(result); + } + else if (cmd_args[0] == AT_CMD_CR) + { + if (cmd->exec == RT_NULL) + { + at_server_print_result(AT_RESULT_CMD_ERR); + return -RT_ERROR; + } + + result = cmd->exec(); + at_server_print_result(result); + } + else + { + return -RT_ERROR; + } + + return RT_EOK; +} + +static at_cmd_t at_find_cmd(const char *cmd) +{ + rt_size_t i = 0; + + RT_ASSERT(cmd_table); + + for (i = 0; i < cmd_num; i++) + { + if (!strcasecmp(cmd, cmd_table[i].name)) + { + return &cmd_table[i]; + } + } + return RT_NULL; +} + +static rt_err_t at_cmd_get_name(const char *cmd_buffer, char *cmd_name) +{ + rt_size_t cmd_name_len = 0, i = 0; + + RT_ASSERT(cmd_name); + RT_ASSERT(cmd_buffer); + + for (i = 0; i < strlen(cmd_buffer) + 1; i++) + { + if (*(cmd_buffer + i) == AT_CMD_QUESTION_MARK || *(cmd_buffer + i) == AT_CMD_EQUAL_MARK + || *(cmd_buffer + i) == AT_CMD_CR + || (*(cmd_buffer + i) >= AT_CMD_CHAR_0 && *(cmd_buffer + i) <= AT_CMD_CHAR_9)) + { + cmd_name_len = i; + memcpy(cmd_name, cmd_buffer, cmd_name_len); + *(cmd_name + cmd_name_len) = '\0'; + + return RT_EOK; + } + } + + return -RT_ERROR; +} + +static char at_server_gerchar(void) +{ + char ch; + + rt_sem_take(at_server_local->rx_notice, RT_WAITING_FOREVER); + + rt_device_read(at_server_local->device, 0, &ch, 1); + + return ch; +} + +static void server_parser(at_server_t server) +{ +#define ESC_KEY 0x1B +#define BACKSPACE_KEY 0x08 +#define DELECT_KEY 0x7F + + char cur_cmd_name[AT_CMD_NAME_LEN] = { 0 }; + at_cmd_t cur_cmd = RT_NULL; + char *cur_cmd_args = RT_NULL, ch, last_ch; + + RT_ASSERT(server); + RT_ASSERT(server->status != AT_STATUS_UNINITIALIZED); + + while (ESC_KEY != (ch = server->get_char())) + { + if (server->echo_mode) + { + if (ch == AT_CMD_CR || (ch == AT_CMD_LF && last_ch != AT_CMD_CR)) + { + at_server_printf("%c%c", AT_CMD_CR, AT_CMD_LF); + } + else if (ch == BACKSPACE_KEY || ch == DELECT_KEY) + { + if (server->cur_recv_len) + { + server->recv_buffer[--server->cur_recv_len] = 0; + at_server_printf("\b \b"); + } + + continue; + } + else + { + at_server_printf("%c", ch); + } + } + + server->recv_buffer[server->cur_recv_len++] = ch; + last_ch = ch; + + if(!strstr(server->recv_buffer, server->end_mark)) + { + continue; + } + + if (at_cmd_get_name(server->recv_buffer, cur_cmd_name) < 0) + { + at_server_print_result(AT_RESULT_CMD_ERR); + goto __retry; + } + + cur_cmd = at_find_cmd(cur_cmd_name); + if (!cur_cmd) + { + at_server_print_result(AT_RESULT_CMD_ERR); + goto __retry; + } + + cur_cmd_args = server->recv_buffer + strlen(cur_cmd_name); + if (at_cmd_process(cur_cmd, cur_cmd_args) < 0) + { + goto __retry; + } + +__retry: + memset(server->recv_buffer, 0x00, AT_SERVER_RECV_BUFF_LEN); + server->cur_recv_len = 0; + } +} + +static rt_err_t at_rx_ind(rt_device_t dev, rt_size_t size) +{ + rt_sem_release(at_server_local->rx_notice); + + return RT_EOK; +} + +#if defined(__ICCARM__) || defined(__ICCRX__) /* for IAR compiler */ +#pragma section="RtAtCmdTab" +#endif + +int at_server_init(void) +{ + rt_err_t result = RT_EOK; + + if (at_server_local) + { + return result; + } + + /* initialize the AT commands table.*/ +#if defined(__CC_ARM) /* ARM C Compiler */ + extern const int RtAtCmdTab$$Base; + extern const int RtAtCmdTab$$Limit; + cmd_table = (at_cmd_t)&RtAtCmdTab$$Base; + cmd_num = (at_cmd_t)&RtAtCmdTab$$Limit - cmd_table; +#elif defined (__ICCARM__) || defined(__ICCRX__) /* for IAR Compiler */ + cmd_table = (at_cmd_t)__section_begin("RtAtCmdTab"); + cmd_num = (at_cmd_t)__section_end("RtAtCmdTab") - cmd_table; +#elif defined (__GNUC__) /* for GCC Compiler */ + extern const int __rtatcmdtab_start; + extern const int __rtatcmdtab_end; + cmd_table = (at_cmd_t)&__rtatcmdtab_start; + cmd_num = (at_cmd_t) &__rtatcmdtab_end - cmd_table; +#endif /* defined(__CC_ARM) */ + + at_server_local = (at_server_t) rt_calloc(1, sizeof(struct at_server)); + if (!at_server_local) + { + result = -RT_ENOMEM; + LOG_E("AT server session initialize failed! No memory for at_server structure !"); + goto __exit; + } + + at_server_local->echo_mode = 1; + at_server_local->status = AT_STATUS_UNINITIALIZED; + + memset(at_server_local->recv_buffer, 0x00, AT_SERVER_RECV_BUFF_LEN); + at_server_local->cur_recv_len = 0; + + at_server_local->rx_notice = rt_sem_create("at_server_notice", 0, RT_IPC_FLAG_FIFO); + if (!at_server_local->rx_notice) + { + LOG_E("AT server session initialize failed! at_rx_notice semaphore create failed!"); + result = -RT_ENOMEM; + goto __exit; + } + + /* Find and open command device */ + at_server_local->device = rt_device_find(AT_SERVER_DEVICE); + if (at_server_local->device) + { + RT_ASSERT(at_server_local->device->type == RT_Device_Class_Char); + + rt_device_open(at_server_local->device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_INT_RX); + + rt_device_set_rx_indicate(at_server_local->device, at_rx_ind); + } + else + { + LOG_E("AT device initialize failed! Not find the device : %s.", AT_SERVER_DEVICE); + result = -RT_ERROR; + goto __exit; + } + + at_server_local->get_char = at_server_gerchar; + memcpy(at_server_local->end_mark, AT_CMD_END_MARK, sizeof(AT_CMD_END_MARK)); + + at_server_local->parser_entry = server_parser; + at_server_local->parser = rt_thread_create("at_server", + (void (*)(void *parameter))server_parser, + at_server_local, + 2 * 1024, + RT_THREAD_PRIORITY_MAX / 3 - 1, + 5); + if (at_server_local->parser == RT_NULL) + { + result = -RT_ENOMEM; + goto __exit; + } + +__exit: + if (!result) + { + at_server_local->status = AT_STATUS_INITIALIZED; + + rt_thread_startup(at_server_local->parser); + + LOG_I("RT-Thread AT server (V%s) initialize success.", AT_SW_VERSION); + } + else + { + if (at_server_local) + { + rt_free(at_server_local); + } + + LOG_E("RT-Thread AT server (V%s) initialize failed(%d).", AT_SW_VERSION, result); + } + + return result; +} +INIT_COMPONENT_EXPORT(at_server_init); + +RT_WEAK void at_port_reset(void) +{ + LOG_E("The reset for AT server is not implement."); +} + +RT_WEAK void at_port_factory_reset(void) +{ + LOG_E("The factory reset for AT server is not implement."); +} diff --git a/components/net/at/src/at_utils.c b/components/net/at/src/at_utils.c new file mode 100644 index 0000000000..25ee705ee6 --- /dev/null +++ b/components/net/at/src/at_utils.c @@ -0,0 +1,102 @@ +/* + * File : at_utils.c + * This file is part of RT-Thread RTOS + * COPYRIGHT (C) 2006 - 2018, RT-Thread Development Team + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Change Logs: + * Date Author Notes + * 2018-04-14 chenyong first version + */ + +#include +#include +#include + +static char send_buf[AT_CMD_MAX_LEN]; +static rt_size_t last_cmd_len = 0; + +/** + * dump hex format data to console device + * + * @param name name for hex object, it will show on log header + * @param buf hex buffer + * @param size buffer size + */ +void at_print_raw_cmd(const char *name, const char *buf, rt_size_t size) +{ +#define __is_print(ch) ((unsigned int)((ch) - ' ') < 127u - ' ') +#define WIDTH_SIZE 32 + + rt_size_t i, j; + + for (i = 0; i < size; i += WIDTH_SIZE) + { + rt_kprintf("[D/AT] %s: %04X-%04X: ", name, i, i + WIDTH_SIZE); + for (j = 0; j < WIDTH_SIZE; j++) + { + if (i + j < size) + { + rt_kprintf("%02X ", buf[i + j]); + } + else + { + rt_kprintf(" "); + } + if ((j + 1) % 8 == 0) + { + rt_kprintf(" "); + } + } + rt_kprintf(" "); + for (j = 0; j < WIDTH_SIZE; j++) + { + if (i + j < size) + { + rt_kprintf("%c", __is_print(buf[i + j]) ? buf[i + j] : '.'); + } + } + rt_kprintf("\n"); + } +} + +const char *at_get_last_cmd(rt_size_t *cmd_size) +{ + *cmd_size = last_cmd_len; + return send_buf; +} + +rt_size_t at_vprintf(rt_device_t device, const char *format, va_list args) +{ + last_cmd_len = vsnprintf(send_buf, sizeof(send_buf), format, args); + +#ifdef AT_PRINT_RAW_CMD + at_print_raw_cmd("send", send_buf, last_cmd_len); +#endif + + return rt_device_write(device, 0, send_buf, last_cmd_len); +} + +rt_size_t at_vprintfln(rt_device_t device, const char *format, va_list args) +{ + rt_size_t len; + + len = at_vprintf(device, format, args); + + rt_device_write(device, 0, "\r\n", 2); + + return len + 2; +}