提交 8b887e76 编写于 作者: Lawlieta's avatar Lawlieta

[net][at] Add AT commands component

上级 2316dc3f
......@@ -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
......
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
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')
/*
* 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 <at.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <at_socket.h>
#ifdef SAL_USING_POSIX
#include <dfs_poll.h>
#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);
/*
* 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 <rtthread.h>
#include <rtdevice.h>
#include <rthw.h>
#include <netdb.h>
#include <sys/socket.h>
#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__ */
/*
* 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 <rtthread.h>
#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 <rtdbg.h>
#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__ */
/*
* 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 <at.h>
#include <stdlib.h>
#include <string.h>
#include <rtdevice.h>
#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", "<value>", 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", "=<baudrate>,<databits>,<stopbits>,<parity>,<flow_control>", RT_NULL, at_uart_query, at_uart_setup, RT_NULL);
/*
* 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 <at.h>
#include <stdio.h>
#include <string.h>
#include <rtthread.h>
#include <rtdevice.h>
#include <rthw.h>
#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 <server|client>' \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 <server|client>' \n");
}
at_cli_deinit();
}
MSH_CMD_EXPORT(at, RT-Thread AT component cli: at <server|client>);
#endif /* AT_USING_CLI */
/*
* 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 <at.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}
/*
* 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 <at.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <rthw.h>
#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.");
}
/*
* 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 <at.h>
#include <stdlib.h>
#include <stdio.h>
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;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册