From b3c90a44847a5c5206abc388e2921797701bcd44 Mon Sep 17 00:00:00 2001 From: Pierre-Noel Bouteville Date: Sun, 4 Dec 2016 08:35:02 -0600 Subject: [PATCH] esp8266 update cosmetic and many bug fix --- include/netutils/esp8266.h | 32 +- netutils/esp8266/Kconfig | 4 + netutils/esp8266/esp8266.c | 2070 +++++++++++++++++++++++++++++------- 3 files changed, 1714 insertions(+), 392 deletions(-) diff --git a/include/netutils/esp8266.h b/include/netutils/esp8266.h index 2df3e4e0a..1c0be8ed3 100644 --- a/include/netutils/esp8266.h +++ b/include/netutils/esp8266.h @@ -1,7 +1,7 @@ /**************************************************************************** * apps/include/netutils/esp8266.h * - * Copyright (C) 2015 Pierre-Noel Bouteville. All rights reserved. + * Copyright (C) 2015-2016 Pierre-Noel Bouteville. All rights reserved. * Author: Pierre-Noel Bouteville * * Redistribution and use in source and binary forms, with or without @@ -50,7 +50,8 @@ * Pre-processor Definitions ****************************************************************************/ -#define lespBSSID_SIZE 6 +#define lespSSID_SIZE 32 /* Number of character max of SSID (null char not included) */ +#define lespBSSID_SIZE 6 #define lespIP(x1,x2,x3,x4) ((x1) << 24 | (x2) << 16 | (x3) << 8 | (x4) << 0) @@ -58,7 +59,7 @@ * Public Types ****************************************************************************/ -typedef enum +typedef enum { lesp_eMODE_AP = 0, lesp_eMODE_STATION = 1, @@ -67,7 +68,7 @@ typedef enum typedef enum { - lesp_eSECURITY_NONE=0, + lesp_eSECURITY_NONE = 0, lesp_eSECURITY_WEP, lesp_eSECURITY_WPA_PSK, lesp_eSECURITY_WPA2_PSK, @@ -78,9 +79,10 @@ typedef enum typedef struct { lesp_security_t security; - const char *ssid; + char ssid[lespSSID_SIZE+1]; /* +1 for null char */ uint8_t bssid[lespBSSID_SIZE]; - int rssi; + int rssi; + int channel; } lesp_ap_t; /**************************************************************************** @@ -92,18 +94,19 @@ int lesp_soft_reset(void); const char *lesp_security_to_str(lesp_security_t security); -int lesp_ap_connect(const char* ssid_name, const char* ap_key, int timeout_s); +int lesp_ap_connect(const char *ssid_name, const char *ap_key, int timeout_s); +int lesp_ap_get(lesp_ap_t *ap); int lesp_ap_is_connected(void); -int lesp_set_dhcp(lesp_mode_t mode,bool enable); -int lesp_set_net(lesp_mode_t mode, - in_addr_t ip, - in_addr_t mask, - in_addr_t gateway - ); +int lesp_set_dhcp(lesp_mode_t mode, bool enable); +int lesp_get_dhcp(bool *ap_enable, bool *sta_enable); +int lesp_set_net(lesp_mode_t mode, in_addr_t ip, in_addr_t mask, + in_addr_t gateway); +int lesp_get_net(lesp_mode_t mode, in_addr_t *ip, in_addr_t *mask, + in_addr_t *gw); -typedef void (*lesp_cb_t)(lesp_ap_t* wlan); +typedef void (*lesp_cb_t)(lesp_ap_t *wlan); int lesp_list_access_points(lesp_cb_t cb); @@ -117,6 +120,7 @@ ssize_t lesp_send(int sockfd, FAR const uint8_t *buf, size_t len, int flags); ssize_t lesp_recv(int sockfd, FAR uint8_t *buf, size_t len, int flags); int lesp_setsockopt(int sockfd, int level, int option, FAR const void *value, socklen_t value_len); +FAR struct hostent *lesp_gethostbyname(FAR const char *hostname); #endif /* CONFIG_NETUTILS_ESP8266 */ #endif /* __APPS_INCLUDE_NETUTILS_ESP8266_H */ diff --git a/netutils/esp8266/Kconfig b/netutils/esp8266/Kconfig index 37468b78d..1f3a625b3 100644 --- a/netutils/esp8266/Kconfig +++ b/netutils/esp8266/Kconfig @@ -28,6 +28,10 @@ config NETUTILS_ESP8266_MAXRXLEN int "Max. RX length" default 256 +config NETUTILS_ESP8266_WORKER_BUF_LEN + int "Max. Worker RX length" + default 256 + config NETUTILS_ESP8266_THREADPRIO int "Worker thread priority" default 100 diff --git a/netutils/esp8266/esp8266.c b/netutils/esp8266/esp8266.c index 20d313547..67331a462 100644 --- a/netutils/esp8266/esp8266.c +++ b/netutils/esp8266/esp8266.c @@ -44,6 +44,7 @@ #include #include +#include #include #include @@ -56,6 +57,7 @@ #include #include +#include #include #include "netutils/esp8266.h" @@ -76,15 +78,21 @@ # define CONFIG_NETUTILS_ESP8266_MAXRXLEN 256 #endif +#if (CONFIG_NETUTILS_ESP8266_MAXRXLEN < CONFIG_NETUTILS_ESP8266_WORKER_BUF_LEN) +# error "CONFIG_NETUTILS_ESP8266_WORKER_BUF_LEN whould be bigger than CONFIG_NETUTILS_ESP8266_MAXRXLEN" +#endif + #define BUF_CMD_LEN CONFIG_NETUTILS_ESP8266_MAXTXLEN #define BUF_ANS_LEN CONFIG_NETUTILS_ESP8266_MAXRXLEN -#define BUF_WORKER_LEN 1024 +#define BUF_WORKER_LEN CONFIG_NETUTILS_ESP8266_WORKER_BUF_LEN + #define CON_NBR 4 #define ESP8266_ACCESS_POINT_NBR_MAX 32 #define lespWAITING_OK_POLLING_MS 250 +#define lespTIMEOUT_FLUSH_MS 100 #define lespTIMEOUT_MS 1000 #define lespTIMEOUT_MS_SEND 1000 #define lespTIMEOUT_MS_CONNECTION 30000 @@ -100,19 +108,32 @@ #define SOCKET_FIFO_SIZE 2048 #define SOCKET_NBR 4 -#define FLAGS_SOCK_USED (1 << 0) -#define FLAGS_SOCK_CONNECTED (1 << 1) +#define FLAGS_SOCK_USED (1 << 0) +#define FLAGS_SOCK_CONNECTED (1 << 1) + +#define FLAGS_SOCK_TYPE_MASK (3 << 2) +#define FLAGS_SOCK_TYPE_TCP (0 << 2) +#define FLAGS_SOCK_TYPE_UDP (1 << 2) +#define FLAGS_SOCK_TYPE_SSL (2 << 2) /* non standard but useful */ /**************************************************************************** * Private Types ****************************************************************************/ +typedef enum +{ + lesp_eERR = -1, + lesp_eNONE = 0, + lesp_eOK = 1 +}lesp_ans_t; + typedef struct { sem_t *sem; uint8_t flags; uint16_t inndx; uint16_t outndx; + struct timespec rcv_timeo; uint8_t rxbuf[SOCKET_FIFO_SIZE]; } lesp_socket_t; @@ -120,8 +141,13 @@ typedef struct { bool running; pthread_t thread; - uint8_t buf[BUF_WORKER_LEN]; - int bufsize; + + char rxbuf[BUF_WORKER_LEN]; + + sem_t sem; /* Inform that something is received */ + char buf[BUF_ANS_LEN]; /* Last complete line received */ + lesp_ans_t ans; /* Last ans received (OK,FAIL or ERROR) */ + pthread_mutex_t mutex; } lesp_worker_t; typedef struct @@ -131,19 +157,25 @@ typedef struct int fd; lesp_worker_t worker; lesp_socket_t sockets[SOCKET_NBR]; - sem_t sem; - char buf[BUF_ANS_LEN]; + lesp_ans_t ans; char bufans[BUF_ANS_LEN]; char bufcmd[BUF_CMD_LEN]; + struct hostent hostent; + + /* ESP Got only One ip + 1 for NULL that indicate end of list */ + + in_addr_t * h_addr_list_buf[2]; + + in_addr_t in_addr; } lesp_state_t; /**************************************************************************** * Private Functions prototypes ****************************************************************************/ -static int lesp_low_level_read(uint8_t* buf,int size); -static inline int lesp_read_ipd(void); -static lesp_socket_t* get_sock(int sockfd); +static int lesp_low_level_read(uint8_t *buf, int size); +static inline int lesp_read_ipd(int sockfd, int len); +static lesp_socket_t *get_sock(int sockfd); /**************************************************************************** * Private Data @@ -155,12 +187,79 @@ lesp_state_t g_lesp_state = .is_initialized = false, .fd = -1, .worker.running = false, + .worker.ans = lesp_eNONE, + .worker.mutex = PTHREAD_MUTEX_INITIALIZER, + .ans = lesp_eNONE, }; /**************************************************************************** * Private Functions ****************************************************************************/ +/**************************************************************************** + * Name: lesp_clear_read_buffer + * + * Description: + * + * Input Parmeters: + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline void lesp_clear_read_buffer(void) +{ + g_lesp_state.bufans[0] = '\0'; +} + +/**************************************************************************** + * Name: lesp_clear_read_ans + * + * Description: + * + * Input Parmeters: + * + * Returned Value: + * None + * + ****************************************************************************/ + +static inline void lesp_clear_read_ans(void) +{ + g_lesp_state.ans = lesp_eNONE; +} + +/**************************************************************************** + * Name: lesp_str_to_unsigned + * + * Description: + * + * Input Parmeters: + * + * Returned Value: + * unsigned value + * + ****************************************************************************/ + +static inline int lesp_str_to_unsigned(char **p_ptr, char end) +{ + int nbr = 0; + char *ptr = *p_ptr; + + while (*ptr != end) + { + char c = *ptr++ - '0'; + if ((c < 0) || (c >= 10)) + return -1; + nbr *= 10; + nbr += c; + } + + *p_ptr = ptr+1; /* Pass the end char */ + return nbr; +} + /**************************************************************************** * Name: lesp_set_baudrate * @@ -177,30 +276,30 @@ lesp_state_t g_lesp_state = #ifdef CONFIG_SERIAL_TERMIOS static int lesp_set_baudrate(int baudrate) - { - struct termios term; +{ + struct termios term; - if (ioctl(g_lesp_state.fd,TCGETS,(unsigned long)&term) < 0) - { - nerr("ERROR: TCGETS failed.\n"); - return -1; - } + if (ioctl(g_lesp_state.fd, TCGETS, (unsigned long)&term) < 0) + { + nerr("ERROR: TCGETS failed.\n"); + return -1; + } - if ((cfsetispeed(&term, baudrate) < 0) || - (cfsetospeed(&term, baudrate) < 0)) - { - nerr("ERROR: Connot set baudrate %0x08X\n",baudrate); - return -1; - } + if ((cfsetispeed(&term, baudrate) < 0) || + (cfsetospeed(&term, baudrate) < 0)) + { + nerr("ERROR: Connot set baudrate %0x08X\n", baudrate); + return -1; + } - if (ioctl(g_lesp_state.fd,TCSETS,(unsigned long)&term) < 0) - { - nerr("ERROR: TCSETS failed.\n"); - return -1; - } + if (ioctl(g_lesp_state.fd, TCSETS, (unsigned long)&term) < 0) + { + nerr("ERROR: TCSETS failed.\n"); + return -1; + } - return 0; - } + return 0; +} #endif /**************************************************************************** @@ -219,20 +318,25 @@ static int lesp_set_baudrate(int baudrate) static lesp_socket_t *get_sock(int sockfd) { + DEBUGASSERT(sockfd >= 0); + if (!g_lesp_state.is_initialized) { + errno = ENETDOWN; ninfo("Esp8266 not initialized; can't list access points\n"); return NULL; } if (((unsigned int)sockfd) >= SOCKET_NBR) { + errno = EINVAL; ninfo("Esp8266 invalid sockfd\n", sockfd); return NULL; } if ((g_lesp_state.sockets[sockfd].flags & FLAGS_SOCK_USED) == 0) { + errno = EPERM; nerr("ERROR: Connection id %d not Created!\n", sockfd); return NULL; } @@ -240,6 +344,67 @@ static lesp_socket_t *get_sock(int sockfd) return &g_lesp_state.sockets[sockfd]; } +/**************************************************************************** + * Name: get_sock_protected + * + * Description: + * Get socket structure pointer of sockfd with worker.mutex portection. + * + * Input Parmeters: + * sockfd : socket id + * + * Returned Value: + * socket id pointer, NULL on error. + * + ****************************************************************************/ + +static lesp_socket_t *get_sock_protected(int sockfd) +{ + lesp_socket_t *ret; + + pthread_mutex_lock(&g_lesp_state.worker.mutex); + ret = get_sock(sockfd); + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + return ret; +} + +/**************************************************************************** + * Name: set_sock_closed + * + * Description: + * Set socket sockfd mark as closed without check and don't send message to + * esp8266. + * + * Input Parmeters: + * sockfd : socket id + * + * Returned Value: + * None + * + ****************************************************************************/ + +static void set_sock_closed(int sockfd) +{ + sem_t *sem; + lesp_socket_t *sock; + + DEBUGASSERT(((unsigned int)sockfd) < SOCKET_NBR); + + sock = &g_lesp_state.sockets[sockfd]; + sem = sock->sem; + sock->sem = NULL; + sock->flags = 0; + sock->inndx = 0; + sock->outndx = 0; + + ninfo("Socket %d closed\n", sockfd); + + if (sem != NULL) + { + sem_post(sem); + } +} + /**************************************************************************** * Name: lesp_low_level_read * @@ -255,7 +420,7 @@ static lesp_socket_t *get_sock(int sockfd) * ****************************************************************************/ -static int lesp_low_level_read(uint8_t* buf, int size) +static int lesp_low_level_read(uint8_t *buf, int size) { int ret = 0; @@ -299,61 +464,35 @@ static int lesp_low_level_read(uint8_t* buf, int size) * Try to treat an '+IPD' command in worker buffer. Worker buffer should * already contain '+IPD,,:' * + * Note: + * g_lesp_state.worker.mutex should be locked. + * * Input Parmeters: - * None + * sockfd : socker number to put received data. + * len : data size to read on serial port. * * Returned Value: * 1 was an IPD, 0 is not an IPD, -1 on error. * ****************************************************************************/ -static inline int lesp_read_ipd(void) +static inline int lesp_read_ipd(int sockfd, int len) { - int sockfd; lesp_socket_t *sock; - char *ptr; - int len; - - ptr = (char *)g_lesp_state.worker.buf; - - /* Put a null at end */ - - *(ptr + g_lesp_state.worker.bufsize) = '\0'; - - if (memcmp(ptr,"+IPD,",5) != 0) - { - return 0; - } - - ptr += 5; - - sockfd = strtol(ptr, &ptr, 10); sock = get_sock(sockfd); - if (sock == NULL) - { - return -1; - } - - if (*ptr++ != ',') - { - return -1; - } - - len = strtol(ptr,&ptr,10); - - if (*ptr != ':') - { - return -1; - } - ninfo("Read %d bytes for socket %d \n", len, sockfd); - while(len) + if (sock == NULL) + { + nwarn("socket not opened: drop all data.\n"); + } + + while (len) { int size; - uint8_t *buf = g_lesp_state.worker.buf; + uint8_t *buf = (uint8_t *)g_lesp_state.worker.rxbuf; size = len; if (size >= BUF_WORKER_LEN) @@ -361,62 +500,71 @@ static inline int lesp_read_ipd(void) size = BUF_WORKER_LEN; } - size = lesp_low_level_read(buf,size); + size = lesp_low_level_read(buf, size); if (size <= 0) { return -1; } len -= size; - pthread_mutex_lock(&g_lesp_state.mutex); - while (size--) + + if (sock != NULL) { - int next; - uint8_t b; - - /* Read the next byte from the buffer */ - - b = *buf++; - - /* Pre-calculate the next 'inndx'. We do this so that we can - * check if the FIFO is full. - */ - - next = sock->inndx + 1; - if (next >= SOCKET_FIFO_SIZE) + while (size--) { - next -= SOCKET_FIFO_SIZE; + int next; + uint8_t b; + + /* Read the next byte from the buffer */ + + b = *buf++; + + /* Pre-calculate the next 'inndx'. We do this so that we can + * check if the FIFO is full. + */ + + next = sock->inndx + 1; + if (next >= SOCKET_FIFO_SIZE) + { + next -= SOCKET_FIFO_SIZE; + } + + /* Is there space in the circular buffer for another byte? If + * the next 'inndx' would be equal to the 'outndx', then the + * circular buffer is full. + */ + + if (next == sock->outndx) + { + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + usleep(100); /* leave time of aplicative to read buffer */ + pthread_mutex_lock(&g_lesp_state.worker.mutex); + } + + if (next != sock->outndx) + { + /* Yes.. add the byte to the circular buffer */ + + sock->rxbuf[sock->inndx] = b; + sock->inndx = next; + } + else + { + /* No.. the we have lost data */ + + nwarn("overflow socket 0x%02X\n", b); + } } - /* Is there space in the circular buffer for another byte? If - * the next 'inndx' would be equal to the 'outndx', then the - * circular buffer is full. - */ - - if (next != sock->outndx) + if (sock->sem) { - /* Yes.. add the byte to the circular buffer */ - - sock->rxbuf[sock->inndx] = b; - sock->inndx = next; - } - else - { - /* No.. the we have lost data */ - - ninfo("overflow socket 0x%02X\n", b); + ninfo("post %p \n", sock->sem); + sem_post(sock->sem); } } - - if (sock->sem) - { - sem_post(sock->sem); - } - - pthread_mutex_unlock(&g_lesp_state.mutex); } - return 0; + return 1; } /**************************************************************************** @@ -438,17 +586,17 @@ int lesp_vsend_cmd(FAR const IPTR char *format, va_list ap) { int ret = 0; - ret = vsnprintf(g_lesp_state.bufcmd,BUF_CMD_LEN,format,ap); + ret = vsnprintf(g_lesp_state.bufcmd, BUF_CMD_LEN, format, ap); if (ret >= BUF_CMD_LEN) { - g_lesp_state.bufcmd[BUF_CMD_LEN-1]='\0'; + g_lesp_state.bufcmd[BUF_CMD_LEN-1] = '\0'; ninfo("Buffer too small for '%s'...\n", g_lesp_state.bufcmd); ret = -1; } ninfo("Write:%s\n", g_lesp_state.bufcmd); - ret = write(g_lesp_state.fd,g_lesp_state.bufcmd,ret); + ret = write(g_lesp_state.fd, g_lesp_state.bufcmd, ret); if (ret < 0) { ret = -1; @@ -467,6 +615,8 @@ int lesp_vsend_cmd(FAR const IPTR char *format, va_list ap) * format : null terminated format string to send. * ... : format values. * + * Note it clear AT Rx buffer (not socket buffer) + * * Returned Value: * len of cmd on success, -1 on error. * @@ -477,6 +627,9 @@ static int lesp_send_cmd(FAR const IPTR char *format, ...) int ret = 0; va_list ap; + lesp_clear_read_buffer(); + lesp_clear_read_ans(); + /* Let lesp_vsend_cmd do the real work */ va_start(ap, format); @@ -512,36 +665,50 @@ static int lesp_read(int timeout_ms) return -1; } - if (clock_gettime(CLOCK_REALTIME,&ts) < 0) + if (clock_gettime(CLOCK_REALTIME, &ts) < 0) { return -1; } - ts.tv_sec += (timeout_ms/1000) + lespTIMEOUT_FLOODING_OFFSET_S; + ts.tv_nsec += (timeout_ms%1000) * 1000000; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_nsec -= 1000000000; + ts.tv_sec += 1; + } + + ts.tv_sec += (timeout_ms/1000); do { - if (sem_timedwait(&g_lesp_state.sem,&ts) < 0) + if (sem_timedwait(&g_lesp_state.worker.sem, &ts) < 0) { return -1; } - pthread_mutex_lock(&g_lesp_state.mutex); + pthread_mutex_lock(&g_lesp_state.worker.mutex); - ret = strlen(g_lesp_state.buf); - if (ret > 0) + if (g_lesp_state.worker.ans != lesp_eNONE) { - memcpy(g_lesp_state.bufans,g_lesp_state.buf,ret+1); /* +1 to copy null */ + g_lesp_state.ans = g_lesp_state.worker.ans; + g_lesp_state.worker.ans = lesp_eNONE; } - g_lesp_state.buf[0] = '\0'; + ret = strlen(g_lesp_state.worker.buf); + if (ret > 0) + { + /* +1 to copy null */ - pthread_mutex_unlock(&g_lesp_state.mutex); + memcpy(g_lesp_state.bufans, g_lesp_state.worker.buf, ret+1); + } + g_lesp_state.worker.buf[0] = '\0'; /* buffer is read */ + pthread_mutex_unlock(&g_lesp_state.worker.mutex); } - while (ret <= 0); + while ((ret <= 0) && (g_lesp_state.ans == lesp_eNONE)); - ninfo("read %d=>%s\n", ret, g_lesp_state.bufans); + ninfo("lesp_read %d=>%s and ans = %d \n", ret, g_lesp_state.bufans, + g_lesp_state.ans); return ret; } @@ -562,7 +729,12 @@ static int lesp_read(int timeout_ms) static void lesp_flush(void) { - while (lesp_read(lespTIMEOUT_MS) >= 0); + do + { + lesp_clear_read_buffer(); + lesp_clear_read_ans(); + } + while (lesp_read(lespTIMEOUT_FLUSH_MS) >= 0); } /**************************************************************************** @@ -581,41 +753,29 @@ static void lesp_flush(void) int lesp_read_ans_ok(int timeout_ms) { + int ret = 0; time_t end; + end = time(NULL) + (timeout_ms/1000) + lespTIMEOUT_FLOODING_OFFSET_S; - do + + while (g_lesp_state.ans != lesp_eOK) { - if (lesp_read(timeout_ms) < 0) - { - return -1; - } + ret = lesp_read(timeout_ms); - /* Answers sorted in probability case */ - - if (strcmp(g_lesp_state.bufans,"OK") == 0) + if ((ret < 0) || (g_lesp_state.ans == lesp_eERR) || \ + (time(NULL) > end)) { - return 0; - } - - if (strcmp(g_lesp_state.bufans,"FAIL") == 0) - { - return -1; - } - - if (strcmp(g_lesp_state.bufans,"ERROR") == 0) - { - return -1; + ret = -1; + break; } ninfo("Got:%s\n", g_lesp_state.bufans); - - /* Ro quit in case of message flooding */ } - while (time(NULL) < end); - lesp_flush(); + lesp_clear_read_ans(); + lesp_clear_read_buffer(); - return -1; + return ret; } /**************************************************************************** @@ -650,6 +810,316 @@ static int lesp_ask_ans_ok(int timeout_ms, FAR const IPTR char *format, ...) return ret; } +/**************************************************************************** + * Name: lesp_check + * + * Description: + * check if esp is ready (initialized and AT return OK) + * + * Input Parmeters: + * None + * + * Returned Value: + * 0 : all is ok, -1 in case of error. + * + ****************************************************************************/ + +static int lesp_check(void) +{ + if (! g_lesp_state.is_initialized) + { + nerr("ERROR: ESP8266 not initialized\n"); + return -1; + } + + lesp_flush(); + + if (lesp_ask_ans_ok(lespTIMEOUT_MS, "AT\r\n") < 0) + { + nerr("ERROR: ESP8266 not answer at AT command\n"); + return -1; + } + + return 0; +} + +/**************************************************************************** + * Name: lesp_parse_cipdomain_ans_line + * + * Description: + * Try to decode line start with: + * +CIPDOMAIN: + * see in: + * https://room-15.github.io/blog/2015/03/26/esp8266-at-command-reference/ + * or + * http://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf + * + * net ip + * +CIPDOMAIN:"192.168.1.1" + * + * Note: + * - Content of ptr is modified and string in ap point into ptr string. + * - in current version, only one ip is returned by ESP8266. + * + * Input Parmeters: + * ptr : +CIPDOMAIN line null terminated string pointer. + * ip : ip of hostname + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + +static int lesp_parse_cwdomain_ans_line(const char *ptr, in_addr_t *ip) +{ + int field_idx; + char *ptr_next; + + for (field_idx = 0; field_idx <= 1; field_idx++) + { + if (field_idx == 0) + { + ptr_next = strchr(ptr, ':'); + } + else if (field_idx == 1) + { + ptr_next = strchr(ptr, '\0'); + } + + if (ptr_next == NULL) + { + return -1; + } + + *ptr_next = '\0'; + + switch (field_idx) + { + case 0: + if (strncmp(ptr, "+CIP", 4) != 0) + { + return -1; + } + + break; + + case 1: + /* No '"' for this command ! */ + + if (inet_pton(AF_INET, ptr, ip) < 0) + { + return -1; + } + break; + } + + ptr = ptr_next + 1; + } + + return 0; +} + +/**************************************************************************** + * Name: lesp_parse_cipxxx_ans_line + * + * Description: + * Try to decode line start with: + * +CIPAP? + * +CIPAP_XXX? + * +CIPSTA? + * +CIPSTA_XXX? + * see in: + * https://room-15.github.io/blog/2015/03/26/esp8266-at-command-reference/ + * or + * http://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf + * + * net ip net mask + * +CIPxxxx:ip:"192.168.1.1","255.255.255.0","192.168.1.1", + * + * Note: + * - Content of ptr is modified and string in ap point into ptr string. + * - in current version, only net ip is returned by ESP8266. + * + * Input Parmeters: + * ptr : +CWLAP line null terminated string pointer. + * ap : ap result of parsing. + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + +static int lesp_parse_cipxxx_ans_line(const char *ptr, in_addr_t *ip) +{ + int field_idx; + char *ptr_next; + + for (field_idx = 0; field_idx <= 2; field_idx++) + { + if (field_idx <= 1) + { + ptr_next = strchr(ptr, ':'); + } + else if (field_idx == 2) + { + ptr_next = strchr(ptr, '\0'); + } + else + { + ptr_next = strchr(ptr, ','); + } + + if (ptr_next == NULL) + { + return -1; + } + + *ptr_next = '\0'; + + switch (field_idx) + { + case 0: + if (strncmp(ptr, "+CIP", 4) != 0) + { + return -1; + } + + break; + + case 1: + /* ip label */ + + break; + case 2: + ptr++; /* Remove first '"' */ + *(ptr_next - 1) = '\0'; + if (inet_pton(AF_INET, ptr, ip) < 0) + { + return -1; + } + break; + } + + ptr = ptr_next + 1; + } + + return 0; +} + +/**************************************************************************** + * Name: lesp_parse_cwjap_ans_line + * + * Description: + * Try to decode @b +CWJAP? or +CWJAP_XXX? line. + * see in: + * https://room-15.github.io/blog/2015/03/26/esp8266-at-command-reference/ + * or + * http://www.espressif.com/sites/default/files/documentation/4a-esp8266_at_instruction_set_en.pdf + * + * SSID BSSID ch RSSI + * +CWLAP:"FreeWifi","00:07:cb:07:b6:00", 1, -90 + * or + * +CWJAP_CUR:"WLACTAP","f4:f2:6d:e8:5e:74",6,-59 + * + * Note: Content of ptr is modified and string in ap point into ptr string. + * + * Input Parmeters: + * ptr : +CWLAP line null terminated string pointer. + * ap : ap result of parsing. + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + +static int lesp_parse_cwjap_ans_line(char *ptr, lesp_ap_t *ap) +{ + int field_idx; + char *ptr_next; + + for (field_idx = 0; field_idx <= 4; field_idx++) + { + if (field_idx == 0) + { + ptr_next = strchr(ptr, ':'); + } + else if (field_idx == 4) + { + ptr_next = strchr(ptr, '\0'); + } + else + { + ptr_next = strchr(ptr, ','); + } + + if (ptr_next == NULL) + { + return -1; + } + + *ptr_next = '\0'; + + switch (field_idx) + { + case 0: + if (strncmp(ptr, "+CWJAP", 6) != 0) + { + return -1; + } + break; + + case 1: + ptr++; /* Remove first '"' */ + *(ptr_next - 1) = '\0'; + strncpy(ap->ssid, ptr, lespSSID_SIZE); + ap->ssid[lespSSID_SIZE] = '\0'; + break; + + case 2: + { + int i; + + ptr++; /* Remove first '"' */ + *(ptr_next - 1) = '\0'; + + for (i = 0; i < lespBSSID_SIZE ; i++) + { + ap->bssid[i] = strtol(ptr, &ptr, 16); + if (*ptr == ':') + { + ptr++; + } + } + } + break; + + case 3: + { + int i = atoi(ptr); + ap->channel = i; + } + break; + + case 4: + { + int i = atoi(ptr); + + if (i > 0) + { + i = -i; + } + + ap->rssi = i; + } + break; + } + + ptr = ptr_next + 1; + } + + return 0; +} + /**************************************************************************** * Name: * @@ -675,24 +1145,24 @@ static int lesp_ask_ans_ok(int timeout_ms, FAR const IPTR char *format, ...) * ****************************************************************************/ -static int lesp_parse_cwlap_ans_line(char* ptr, lesp_ap_t *ap) +static int lesp_parse_cwlap_ans_line(char *ptr, lesp_ap_t *ap) { int field_idx; - char* ptr_next; + char *ptr_next; - for(field_idx = 0; field_idx <= 5; field_idx++) + for (field_idx = 0; field_idx <= 5; field_idx++) { if (field_idx == 0) { - ptr_next = strchr(ptr,'('); + ptr_next = strchr(ptr, '('); } else if (field_idx == 5) { - ptr_next = strchr(ptr,')'); + ptr_next = strchr(ptr, ')'); } else { - ptr_next = strchr(ptr,','); + ptr_next = strchr(ptr, ','); } if (ptr_next == NULL) @@ -705,62 +1175,62 @@ static int lesp_parse_cwlap_ans_line(char* ptr, lesp_ap_t *ap) switch (field_idx) { case 0: - if (strcmp(ptr,"+CWLAP:") != 0) - { - return -1; - } - - break; - - case 1: - { - int i = *ptr - '0'; - - if ((i < 0) || (i >= lesp_eSECURITY_NBR)) + if (strcmp(ptr, "+CWLAP:") != 0) { return -1; } + break; - ap->security = i; - } - break; + case 1: + { + int i = *ptr - '0'; + + if ((i < 0) || (i >= lesp_eSECURITY_NBR)) + { + return -1; + } + + ap->security = i; + } + break; case 2: ptr++; /* Remove first '"' */ *(ptr_next - 1) = '\0'; - ap->ssid = ptr; + strncpy(ap->ssid, ptr, lespSSID_SIZE); + ap->ssid[lespSSID_SIZE] = '\0'; break; case 3: - { - int i = atoi(ptr); - - if (i > 0) { - i = -i; - } + int i = atoi(ptr); - ap->rssi = i; - } - break; + if (i > 0) + { + i = -i; + } + + ap->rssi = i; + } + break; case 4: - { - int i; - - ptr++; /* Remove first '"' */ - *(ptr_next - 1) = '\0'; - - for (i = 0; i < lespBSSID_SIZE ; i++) { - ap->bssid[i] = strtol(ptr,&ptr,16); - if (*ptr == ':') + int i; + + ptr++; /* Remove first '"' */ + *(ptr_next - 1) = '\0'; + + for (i = 0; i < lespBSSID_SIZE ; i++) { - ptr++; + ap->bssid[i] = strtol(ptr, &ptr, 16); + if (*ptr == ':') + { + ptr++; + } } } - } - break; + break; } ptr = ptr_next + 1; @@ -769,24 +1239,36 @@ static int lesp_parse_cwlap_ans_line(char* ptr, lesp_ap_t *ap) return 0; } +/**************************************************************************** + * Name: lesp_worker + * + * Description: + * Esp8266 worker thread. + * + * Input Parmeters: + * args : unused + * + * Returned Value: + * NULL + * + ****************************************************************************/ + static void *lesp_worker(void *args) { int ret = 0; + int rxlen = 0; - lesp_worker_t *p = &g_lesp_state.worker; + lesp_worker_t *worker = &g_lesp_state.worker; UNUSED(args); - pthread_mutex_lock(&g_lesp_state.mutex); ninfo("worker Started \n"); - p->bufsize = 0; - pthread_mutex_unlock(&g_lesp_state.mutex); - while(p->running) + while (worker->running) { uint8_t c; - ret = lesp_low_level_read(&c,1); + ret = lesp_low_level_read(&c, 1); if (ret < 0) { @@ -794,46 +1276,116 @@ static void *lesp_worker(void *args) } else if (ret > 0) { - //ninfo("c:0x%02X (%c)\n", c); + /* ninfo("c:0x%02X (%c)\n", c); */ - pthread_mutex_lock(&g_lesp_state.mutex); + pthread_mutex_lock(&(worker->mutex)); if (c == '\n') { - if (p->buf[p->bufsize-1] == '\r') + if (worker->rxbuf[rxlen-1] == '\r') { - p->bufsize--; + rxlen--; } - if (p->bufsize != 0) + DEBUGASSERT(rxlen >= 0); + DEBUGASSERT(rxlen < BUF_WORKER_LEN); + + worker->rxbuf[rxlen] = '\0'; + + if (rxlen != 0) { - p->buf[p->bufsize] = '\0'; - memcpy(g_lesp_state.buf,p->buf,p->bufsize+1); - ninfo("Read data:%s\n", g_lesp_state.buf); - sem_post(&g_lesp_state.sem); - p->bufsize = 0; + if (strcmp(worker->rxbuf, "OK") == 0) + { + worker->ans = lesp_eOK; + } + else if ((strcmp(worker->rxbuf, "FAIL") == 0) || + (strcmp(worker->rxbuf, "ERROR") == 0) + ) + { + worker->ans = lesp_eERR; + } + else if ((rxlen == 8) && + (memcmp(worker->rxbuf+1, ",CLOSED", 7) == 0)) + { + unsigned int sockid = worker->rxbuf[0] - '0'; + if (sockid < SOCKET_NBR) + { + set_sock_closed(sockid); + } + } + else + { + if (worker->buf[0] != '\0') + { + pthread_mutex_unlock(&(worker->mutex)); + usleep(100); /* leave time of aplicative to read buffer */ + pthread_mutex_lock(&(worker->mutex)); + } + + /* ninfo("Worker Read data:%s\n", worker->rxbuf); */ + + if (rxlen+1 <= BUF_ANS_LEN) + { + memcpy(worker->buf, worker->rxbuf, rxlen+1); + } + else + { + nerr("Worker ans line is too long:%s\n", worker->rxbuf); + } + } + + sem_post(&worker->sem); + worker->rxbuf[0] = '\0'; + rxlen = 0; } } - else if (p->bufsize < BUF_ANS_LEN - 1) + else if (rxlen < BUF_WORKER_LEN - 1) { - p->buf[p->bufsize++] = c; + worker->rxbuf[rxlen++] = c; + if ((c == ':') && (memcmp(worker->rxbuf, "+IPD,", 5) == 0)) + { + int sockfd; + int len; + char *ptr = worker->rxbuf+5; + + sockfd = lesp_str_to_unsigned(&ptr, ','); + if (sockfd >= 0) + { + len = lesp_str_to_unsigned(&ptr, ':'); + if (len >= 0) + { + lesp_read_ipd(sockfd, len); + } + } + + rxlen = 0; + } } else { - ninfo("Read char overflow:%c\n", c); + nerr("Read char overflow:%c\n", c); } - pthread_mutex_unlock(&g_lesp_state.mutex); - - if ((c == ':') && (lesp_read_ipd() != 0)) - { - p->bufsize = 0; - } + pthread_mutex_unlock(&(worker->mutex)); } } return NULL; } +/**************************************************************************** + * Name: lesp_create_worker + * + * Description: + * start Esp8266 worker thread. + * + * Input Parmeters: + * priority : POSIX priority of worker thread. + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + static inline int lesp_create_worker(int priority) { int ret = 0; @@ -854,11 +1406,11 @@ static inline int lesp_create_worker(int priority) } else { - ret = pthread_attr_getschedparam(&thread_attr,¶m); + ret = pthread_attr_getschedparam(&thread_attr, ¶m); if (ret >= 0) { param.sched_priority += priority; - ret = pthread_attr_setschedparam(&thread_attr,¶m); + ret = pthread_attr_setschedparam(&thread_attr, ¶m); } else { @@ -875,6 +1427,13 @@ static inline int lesp_create_worker(int priority) g_lesp_state.worker.running = false; } +#if CONFIG_TASK_NAME_SIZE > 0 + if (ret >= 0) + { + pthread_setname_np(g_lesp_state.worker.thread, "ESP8266"); + } +#endif + if (pthread_attr_destroy(&thread_attr) < 0) { nerr("ERROR: Cannot destroy thread attribute (%d)\n", ret); @@ -888,6 +1447,23 @@ static inline int lesp_create_worker(int priority) * Public Functions ****************************************************************************/ +/**************************************************************************** + * Name: lesp_initialize + * + * Description: + * initialise Esp8266 class. + * - open port + * - configure port + * - etc... + * + * Input Parmeters: + * None + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + int lesp_initialize(void) { int ret = 0; @@ -896,28 +1472,29 @@ int lesp_initialize(void) if (g_lesp_state.is_initialized) { - ninfo("Esp8266 already initialized\n"); pthread_mutex_unlock(&g_lesp_state.mutex); + ninfo("Esp8266 already initialized\n"); return 0; } + pthread_mutex_lock(&g_lesp_state.worker.mutex); + ninfo("Initializing Esp8266...\n"); memset(g_lesp_state.sockets, 0, SOCKET_NBR * sizeof(lesp_socket_t)); - if (sem_init(&g_lesp_state.sem, 0, 0) < 0) + if (sem_init(&g_lesp_state.worker.sem, 0, 0) < 0) { ninfo("Cannot create semaphore\n"); - pthread_mutex_unlock(&g_lesp_state.mutex); - return -1; + ret = -1; } - if (g_lesp_state.fd < 0) + if (ret >= 0 && g_lesp_state.fd < 0) { g_lesp_state.fd = open(CONFIG_NETUTILS_ESP8266_DEV_PATH, O_RDWR); } - if (g_lesp_state.fd < 0) + if (ret >= 0 && g_lesp_state.fd < 0) { nerr("ERROR: Cannot open %s\n", CONFIG_NETUTILS_ESP8266_DEV_PATH); ret = -1; @@ -936,35 +1513,58 @@ int lesp_initialize(void) ret = lesp_create_worker(CONFIG_NETUTILS_ESP8266_THREADPRIO); } - pthread_mutex_unlock(&g_lesp_state.mutex); - if (ret < 0) { ninfo("Esp8266 initialisation failed!\n"); - return -1; + ret = -1; + } + else + { + g_lesp_state.is_initialized = true; + ninfo("Esp8266 initialized\n"); } - g_lesp_state.is_initialized = true; - ninfo("Esp8266 initialized\n"); + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + pthread_mutex_unlock(&g_lesp_state.mutex); return 0; } +/**************************************************************************** + * Name: lesp_soft_reset + * + * Description: + * reset esp8266 (command "AT+RST"); + * + * Input Parmeters: + * None + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + int lesp_soft_reset(void) { int ret = 0; int i; + pthread_mutex_lock(&g_lesp_state.mutex); + /* Rry to close opened reset */ - for(i = 0; i < SOCKET_NBR; i++) + pthread_mutex_lock(&g_lesp_state.worker.mutex); + + for (i = 0; i < SOCKET_NBR; i++) { if ((g_lesp_state.sockets[i].flags & FLAGS_SOCK_USED) != 0) { - lesp_closesocket(i); + set_sock_closed(i); } } + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + /* Leave time to close socket */ sleep(1); @@ -979,7 +1579,7 @@ int lesp_soft_reset(void) lesp_flush(); - while(lesp_ask_ans_ok(lespTIMEOUT_MS, "ATE0\r\n") < 0) + while (lesp_ask_ans_ok(lespTIMEOUT_MS, "ATE0\r\n") < 0) { sleep(1); lesp_flush(); @@ -987,21 +1587,21 @@ int lesp_soft_reset(void) if (ret >= 0) { - ret = lesp_ask_ans_ok(lespTIMEOUT_MS,"AT+GMR\r\n"); + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+GMR\r\n"); } /* Enable the module to act as a “Station” */ if (ret >= 0) { - ret = lesp_ask_ans_ok(lespTIMEOUT_MS,"AT+CWMODE_CUR=1\r\n"); + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+CWMODE_CUR=1\r\n"); } /* Enable the multi connection */ if (ret >= 0) { - ret = lesp_ask_ans_ok(lespTIMEOUT_MS,"AT+CIPMUX=1\r\n"); + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+CIPMUX=1\r\n"); } if (ret < 0) @@ -1009,27 +1609,43 @@ int lesp_soft_reset(void) ret = -1; } + pthread_mutex_unlock(&g_lesp_state.mutex); + return 0; } -int lesp_ap_connect(const char* ssid_name, const char* ap_key, int timeout_s) +/**************************************************************************** + * Name: lesp_soft_reset + * + * Description: + * reset esp8266 (command "AT+RST"); + * + * Input Parmeters: + * None + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + +int lesp_ap_connect(const char *ssid_name, const char *ap_key, int timeout_s) { int ret = 0; ninfo("Starting manual connect...\n"); - if (! g_lesp_state.is_initialized) + pthread_mutex_lock(&g_lesp_state.mutex); + + ret = lesp_check(); + + if (ret >= 0) { - nerr("ERROR: ESP8266 not initialized; can't run manual connect\n"); - ret = -1; - } - else - { - ret = lesp_ask_ans_ok(timeout_s*1000, - "AT+CWJAP=\"%s\",\"%s\"\r\n", - ssid_name,ap_key); + ret = lesp_ask_ans_ok(timeout_s*1000, "AT+CWJAP=\"%s\",\"%s\"\r\n", + ssid_name, ap_key); } + pthread_mutex_unlock(&g_lesp_state.mutex); + if (ret < 0) { return -1; @@ -1039,6 +1655,166 @@ int lesp_ap_connect(const char* ssid_name, const char* ap_key, int timeout_s) return 0; } +/**************************************************************************** + * Name: lesp_ap_get + * + * Description: + * Read the current connected Access Point. + * + * Input Parmeters: + * None + * + * Output Parmeters: + * ap : details of connected acces point + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + +int lesp_ap_get(lesp_ap_t *ap) +{ + int ret = 0; + ninfo("Get Access Point info...\n"); + + pthread_mutex_lock(&g_lesp_state.mutex); + + ret = lesp_check(); + + if (ret >= 0) + { + ret = lesp_send_cmd("AT+CWJAP_CUR?\r\n"); + } + + if (ret >= 0) + { + ret = lesp_read(lespTIMEOUT_MS); + } + + if (ret >= 0) + { + ninfo("Read:%s\n", g_lesp_state.bufans); + ret = lesp_parse_cwjap_ans_line(g_lesp_state.bufans, ap); + if (ret < 0) + { + nerr("ERROR: Line badly formed.\n"); + } + } + + if (ret >= 0) + { + ret = lesp_read_ans_ok(lespTIMEOUT_MS); + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + if (ret < 0) + { + nerr("ERROR: Get access point information.\n"); + return -1; + } + + ninfo("Connected to %s\n", ap->ssid); + return 0; +} + +/**************************************************************************** + * Name: lesp_get_net + * + * Description: + * Read the current network details. + * + * Input Parmeters: + * mode : lesp_eMODE_AP or lesp_eMODE_STATION + * + * Output Parmeters: + * ip : ip of interface + * mask : mask of interface (not implemented by esp8266) + * gw : gateway of interface (not implemented by esp8266) + * + * Returned Value: + * 0 on success, -1 in case of error. + * + ****************************************************************************/ + +int lesp_get_net(lesp_mode_t mode, in_addr_t *ip, in_addr_t *mask, in_addr_t *gw) +{ + int ret = 0; + ninfo("Get IP info...\n"); + + pthread_mutex_lock(&g_lesp_state.mutex); + + ret = lesp_check(); + + if (ret >= 0) + { + ret = lesp_send_cmd("AT+CIP%s_CUR?\r\n", + (mode == lesp_eMODE_STATION)?"STA":"AP"); + } + + if (ret >= 0) + { + ret = lesp_read(lespTIMEOUT_MS); + } + if (ret >= 0) + { + ninfo("Read:%s\n", g_lesp_state.bufans); + + ret = lesp_parse_cipxxx_ans_line(g_lesp_state.bufans, ip); + if (ret < 0) + { + nerr("ERROR: Line badly formed.\n"); + } + } + + if (ret >= 0) + { + ret = lesp_read(lespTIMEOUT_MS); + } + + if (ret >= 0) + { + ninfo("Read:%s\n", g_lesp_state.bufans); + + ret = lesp_parse_cipxxx_ans_line(g_lesp_state.bufans, mask); + if (ret < 0) + { + nerr("ERROR: Line badly formed.\n"); + } + } + + if (ret >= 0) + { + ret = lesp_read(lespTIMEOUT_MS); + } + + if (ret >= 0) + { + ninfo("Read:%s\n", g_lesp_state.bufans); + + ret = lesp_parse_cipxxx_ans_line(g_lesp_state.bufans, mask); + if (ret < 0) + { + nerr("ERROR: Line badly formed.\n"); + } + } + + if (ret >= 0) + { + ret = lesp_read_ans_ok(lespTIMEOUT_MS); + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + if (ret < 0) + { + nerr("ERROR: Get network information.\n"); + return -1; + } + + return 0; +} + /**************************************************************************** * Name: lesp_set_net * @@ -1061,25 +1837,26 @@ int lesp_set_net(lesp_mode_t mode, in_addr_t ip, in_addr_t mask, in_addr_t gatew { int ret = 0; - ret = lesp_ask_ans_ok(lespTIMEOUT_MS, - "AT+CIP%s_CUR=" - "\"%d.%d.%d.%d\"," - "\"%d.%d.%d.%d\"," - "\"%d.%d.%d.%d\"" - "\r\n", - (mode==lesp_eMODE_STATION)?"STA":"AP", - *((uint8_t*)&(ip)+0), - *((uint8_t*)&(ip)+1), - *((uint8_t*)&(ip)+2), - *((uint8_t*)&(ip)+3), - *((uint8_t*)&(gateway)+0), - *((uint8_t*)&(gateway)+1), - *((uint8_t*)&(gateway)+2), - *((uint8_t*)&(gateway)+3), - *((uint8_t*)&(mask)+0), - *((uint8_t*)&(mask)+1), - *((uint8_t*)&(mask)+2), - *((uint8_t*)&(mask)+3)); + pthread_mutex_lock(&g_lesp_state.mutex); + + ret = lesp_check(); + + if (ret >= 0) + { + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+CIP%s_CUR=\"%d.%d.%d.%d\"," + "\"%d.%d.%d.%d\",\"%d.%d.%d.%d\"\r\n", + (mode == lesp_eMODE_STATION)?"STA":"AP", + *((uint8_t *)&(ip)+0), *((uint8_t *)&(ip)+1), + *((uint8_t *)&(ip)+2), *((uint8_t *)&(ip)+3), + *((uint8_t *)&(gateway)+0), + *((uint8_t *)&(gateway)+1), + *((uint8_t *)&(gateway)+2), + *((uint8_t *)&(gateway)+3), + *((uint8_t *)&(mask)+0), *((uint8_t *)&(mask)+1), + *((uint8_t *)&(mask)+2), *((uint8_t *)&(mask)+3)); + } + + pthread_mutex_unlock(&g_lesp_state.mutex); if (ret < 0) { @@ -1104,13 +1881,21 @@ int lesp_set_net(lesp_mode_t mode, in_addr_t ip, in_addr_t mask, in_addr_t gatew * ****************************************************************************/ -int lesp_set_dhcp(lesp_mode_t mode,bool enable) +int lesp_set_dhcp(lesp_mode_t mode, bool enable) { int ret = 0; - ret = lesp_ask_ans_ok(lespTIMEOUT_MS, - "AT+CWDHCP_CUR=%d,%c\r\n", - mode,(enable)?'1':'0'); + pthread_mutex_lock(&g_lesp_state.mutex); + + ret = lesp_check(); + + if (ret >= 0) + { + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+CWDHCP_CUR=%d,%c\r\n", + mode, (enable)?'1':'0'); + } + + pthread_mutex_unlock(&g_lesp_state.mutex); if (ret < 0) { @@ -1120,6 +1905,89 @@ int lesp_set_dhcp(lesp_mode_t mode,bool enable) return 0; } +/**************************************************************************** + * Name: lesp_get_dhcp + * + * Description: + * It will get if DHCP is Enable or disable for each mode. + * + * Input Parmeters: + * ap_enable : true DHCP is enable in Access Point mode, false otherwise. + * sta_enable : true DHCP is enable in Station mode, false otherwise. + * + * Returned Value: + * 0 on success, -1 on error. + * + ****************************************************************************/ + +int lesp_get_dhcp(bool *ap_enable, bool *sta_enable) +{ + int ret = 0; + + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("Get DHCP State...\n"); + + ret = lesp_check(); + + if (ret >= 0) + { + ret = lesp_send_cmd("AT+CWDHCP_CUR?\r\n"); + } + + if (ret >= 0) + { + ret = lesp_read(lespTIMEOUT_MS); + } + + if (ret >= 0) + { + char *ptr = g_lesp_state.bufans; + + ninfo("Read:%s\n", ptr); + + ptr = strchr(ptr, ':'); + ptr++; + switch (*ptr - '0') + { + case 0: + *ap_enable = 0; + *sta_enable = 0; + break; + case 1: + *ap_enable = 1; + *sta_enable = 0; + break; + case 2: + *ap_enable = 0; + *sta_enable = 1; + break; + case 3: + *ap_enable = 1; + *sta_enable = 1; + break; + default: + nerr("ERROR: Line badly formed.\n"); + break; + } + } + + if (ret >= 0) + { + ret = lesp_read_ans_ok(lespTIMEOUT_MS); + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + if (ret < 0) + { + nerr("ERROR: Get DHCP.\n"); + return -1; + } + + return 0; +} + /**************************************************************************** * Name: lesp_list_access_points * @@ -1140,9 +2008,16 @@ int lesp_list_access_points(lesp_cb_t cb) int ret = 0; int number = 0; - /* Check esp */ + pthread_mutex_lock(&g_lesp_state.mutex); - ret = lesp_ask_ans_ok(lespTIMEOUT_MS,"AT\r\n"); + ninfo("List access point(s)...\n"); + + ret = lesp_check(); + + if (ret >= 0) + { + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT\r\n"); + } if (ret >= 0) { @@ -1159,12 +2034,12 @@ int lesp_list_access_points(lesp_cb_t cb) ninfo("Read:%s\n", g_lesp_state.bufans); - if (strcmp(g_lesp_state.bufans,"OK") == 0) + if (strcmp(g_lesp_state.bufans, "OK") == 0) { break; } - ret = lesp_parse_cwlap_ans_line(g_lesp_state.bufans,&ap); + ret = lesp_parse_cwlap_ans_line(g_lesp_state.bufans, &ap); if (ret < 0) { nerr("ERROR: Line badly formed."); @@ -1174,8 +2049,11 @@ int lesp_list_access_points(lesp_cb_t cb) number++; } + pthread_mutex_unlock(&g_lesp_state.mutex); + if (ret < 0) { + nerr("ERROR: list access points."); return -1; } @@ -1184,54 +2062,205 @@ int lesp_list_access_points(lesp_cb_t cb) return number; } +/**************************************************************************** + * Name: lesp_security_to_str + * + * Description: + * return corresponding string of security enum. + * + * Input Parmeters: + * security : enum value of string + * + * Returned Value: + * String corresponding to security value. + * + ****************************************************************************/ + const char *lesp_security_to_str(lesp_security_t security) { - switch(security) + switch (security) { case lesp_eSECURITY_NONE: - return "NONE"; + return "NONE"; case lesp_eSECURITY_WEP: - return "WEP"; + return "WEP"; case lesp_eSECURITY_WPA_PSK: - return "WPA_PSK"; + return "WPA_PSK"; case lesp_eSECURITY_WPA2_PSK: - return "WPA2_PSK"; + return "WPA2_PSK"; case lesp_eSECURITY_WPA_WPA2_PSK: - return "WPA_WPA2_PSK"; + return "WPA_WPA2_PSK"; default: - return "Unknown"; + return "Unknown"; } } +/**************************************************************************** + * Name: lesp_socket + * + * Description: + * Equivalent of POSIX socket for esp8266. + * socket() creates an endpoint for communication and returns a descriptor. + * + * Input Parameters: + * domain : only PF_INET is supported + * type : only SOCK_STREAM is supported + * protocol : only IPPROTO_TCP is supported + * + * Returned Value: + * A non-negative socket descriptor on success; -1 on error. + * + ****************************************************************************/ + int lesp_socket(int domain, int type, int protocol) { int ret = 0; int i = CON_NBR; + int flags = 0; - if ((domain != PF_INET) && (type != SOCK_STREAM) && (IPPROTO_TCP)) + if (domain != PF_INET) { - nerr("ERROR: Not Implemented!\n"); + nerr("ERROR: only PF_INET Implemented!\n"); return -1; } - pthread_mutex_lock(&g_lesp_state.mutex); + switch (type) + { + case SOCK_STREAM: + flags |= FLAGS_SOCK_TYPE_TCP; + break; + case SOCK_DGRAM: + flags |= FLAGS_SOCK_TYPE_UDP; + break; + case -1: + flags |= FLAGS_SOCK_TYPE_SSL; + break; + default: + nerr("ERROR: Only SOCK_DGRAM and SOCK_STREAM Implemented!\n"); + errno = ESOCKTNOSUPPORT; + return -1; + } + + flags |= FLAGS_SOCK_USED; + + pthread_mutex_lock(&g_lesp_state.worker.mutex); ret = -1; if (!g_lesp_state.is_initialized) { ninfo("Esp8266 not initialized; can't list access points\n"); + errno = ENETDOWN; } else { - for (i = 0; i < CON_NBR;i++) + for (i = 0; i < CON_NBR; i++) { if ((g_lesp_state.sockets[i].flags & FLAGS_SOCK_USED) == 0) { - g_lesp_state.sockets[i].flags |= FLAGS_SOCK_USED; + g_lesp_state.sockets[i].flags = flags; + g_lesp_state.sockets[i].rcv_timeo.tv_sec = lespTIMEOUT_MS_RECV_S; + g_lesp_state.sockets[i].rcv_timeo.tv_nsec = 0; ret = i; break; } } + + if (ret < 0) + { + errno = EAGAIN; + } + } + + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + + return ret; +} + +/**************************************************************************** + * Name: lesp_closesocket + * + * Description: + * Equivalent of POSIX close for esp8266. + * close socket creates with lesp_socket. + * + * Input Parameters: + * sockfd : socket indentifer. + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + +int lesp_closesocket(int sockfd) +{ + int ret = 0; + lesp_socket_t *sock = NULL; + + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("List access point(s)...\n"); + + ret = lesp_check(); + + if (ret >= 0) + { + sock = get_sock_protected(sockfd); + if (sock == NULL) + { + ret = -1; + } + } + + if (ret >= 0) + { + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+CIPCLOSE=%d\r\n", sockfd); + + pthread_mutex_lock(&g_lesp_state.worker.mutex); + set_sock_closed(sockfd); + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + if (ret < 0) + { + nerr("ERROR: Close socket %d.\n", sockfd); + return -1; + } + + return 0; +} + +/**************************************************************************** + * Name: lesp_bind + * + * Description: + * Equivalent of POSIX bind for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor of the socket to bind + * addr Socket local address + * addrlen Length of 'addr' + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + +int lesp_bind(int sockfd, FAR const struct sockaddr *addr, socklen_t addrlen) +{ + int ret = 0; + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("Bind socket %d...\n", sockfd); + + ret = lesp_check(); + + if (ret >= 0) + { + nerr("ERROR: Not implemented %s\n", __func__); + errno = EIO; + ret = -1; } pthread_mutex_unlock(&g_lesp_state.mutex); @@ -1239,121 +2268,223 @@ int lesp_socket(int domain, int type, int protocol) return ret; } -int lesp_closesocket(int sockfd) -{ - int ret = 0; - lesp_socket_t *sock; - - sock = get_sock(sockfd); - if (sock == NULL) - { - ret = -1; - } - - if (ret >= 0) - { - ret = lesp_ask_ans_ok(lespTIMEOUT_MS, - "AT+CIPCLOSE=%d\r\n", - sockfd); - - memset(sock, 0, sizeof(lesp_socket_t)); - sock->flags = 0; - sock->inndx = 0; - sock->outndx = 0; - } - - if (ret < 0) - { - return -1; - } - - return 0; -} - -int lesp_bind(int sockfd, FAR const struct sockaddr *addr, socklen_t addrlen) -{ - nerr("ERROR: Not implemented %s\n", __func__); - return -1; -} +/**************************************************************************** + * Name: lesp_connect + * + * Description: + * Equivalent of POSIX connect for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor returned by socket() + * addr Server address (form depends on type of socket) + * addrlen Length of actual 'addr' + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ int lesp_connect(int sockfd, FAR const struct sockaddr *addr, socklen_t addrlen) { int ret = 0; + const char *proto_str; lesp_socket_t *sock; struct sockaddr_in *in; unsigned short port; in_addr_t ip; - in = (struct sockaddr_in*)addr; - port = ntohs(in->sin_port); // e.g. htons(3490) + in = (struct sockaddr_in *)addr; + port = ntohs(in->sin_port); /* e.g. htons(3490) */ ip = in->sin_addr.s_addr; - DEBUGASSERT(in->sin_family = AF_INET); + DEBUGASSERT(in->sin_family == AF_INET); + DEBUGASSERT(addrlen == sizeof(struct sockaddr_in)); - sock = get_sock(sockfd); - if (sock == NULL) + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("Connect %d...\n", sockfd); + + ret = lesp_check(); + + if (ret >= 0) { - ret = -1; + pthread_mutex_lock(&g_lesp_state.worker.mutex); + sock = get_sock(sockfd); + if (sock == NULL) + { + ret = -1; + } } if (ret >= 0) { - ret = lesp_ask_ans_ok(lespTIMEOUT_MS, - "AT+CIPSTART=%d," - "\"TCP\"," - "\"%d.%d.%d.%d\"," - "%d" - "\r\n", - sockfd, - *((uint8_t*)&(ip)+0), - *((uint8_t*)&(ip)+1), - *((uint8_t*)&(ip)+2), - *((uint8_t*)&(ip)+3), - port); + switch (FLAGS_SOCK_TYPE_MASK & sock->flags) + { + case FLAGS_SOCK_TYPE_TCP: + proto_str = "TCP"; + break; + case FLAGS_SOCK_TYPE_UDP: + proto_str = "UDP"; + break; + case FLAGS_SOCK_TYPE_SSL: + proto_str = "SSL"; + break; + default: + errno = ESOCKTNOSUPPORT; + ret = -1; + } } + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + + if (ret >= 0) + { + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+CIPSTART=%d,\"%s\"," + "\"%d.%d.%d.%d\",%d\r\n", sockfd, proto_str, + *((uint8_t *)&(ip)+0), *((uint8_t *)&(ip)+1), + *((uint8_t *)&(ip)+2), *((uint8_t *)&(ip)+3), port); + if (ret < 0) + { + errno = EIO; + } + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + if (ret < 0) { + nerr("ERROR: Connect socket %d.\n", sockfd); return -1; } return 0; } +/**************************************************************************** + * Name: lesp_listen + * + * Description: + * Equivalent of POSIX listen for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor returned by socket() + * backlog The maximum length the queue of pending connections may grow. + * If a connection request arrives with the queue full, the client + * may receive an error with an indication of ECONNREFUSED or, + * if the underlying protocol supports retransmission, the request + * may be ignored so that retries succeed. + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + int lesp_listen(int sockfd, int backlog) { - nerr("ERROR: Not implemented %s\n", __func__); - return -1; + int ret = 0; + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("Connect %d...\n", sockfd); + + ret = lesp_check(); + + if (ret >= 0) + { + nerr("ERROR: Not implemented %s\n", __func__); + errno = EIO; + ret = -1; + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + return ret; } +/**************************************************************************** + * Name: lesp_accept + * + * Description: + * Equivalent of POSIX accept for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor returned by socket() + * addr Receives the address of the connecting client + * addrlen Input: allocated size of 'addr', Return: returned size of 'addr' + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + int lesp_accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { - nerr("ERROR: Not implemented %s\n", __func__); - return -1; + int ret = 0; + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("Connect %d...\n", sockfd); + + ret = lesp_check(); + + if (ret >= 0) + { + nerr("ERROR: Not implemented %s\n", __func__); + errno = EIO; + ret = -1; + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + return ret; } +/**************************************************************************** + * Name: lesp_send + * + * Description: + * Equivalent of POSIX send for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor returned by socket() + * buf Data to send + * len Length of data to send + * flags Send flags + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + ssize_t lesp_send(int sockfd, FAR const uint8_t *buf, size_t len, int flags) { int ret = 0; - lesp_socket_t *sock; + lesp_socket_t *sock = NULL; UNUSED(flags); - sock = get_sock(sockfd); - if (sock == NULL) + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("Send %d bytes in %d socket...\n", len, sockfd); + + ret = lesp_check(); + + if (ret >= 0) { - ret = -1; + sock = get_sock_protected(sockfd); + if (sock == NULL) + { + ret = -1; + } } if (ret >= 0) { - ret = lesp_ask_ans_ok(lespTIMEOUT_MS,"AT+CIPSEND=%d,%d\r\n",sockfd,len); + ret = lesp_ask_ans_ok(lespTIMEOUT_MS, "AT+CIPSEND=%d,%d\r\n", sockfd, len); } if (ret >= 0) { - ninfo("Sending in socket %d, %d bytes\n", sockfd,len); - ret = write(g_lesp_state.fd,buf,len); + ninfo("Sending in socket %d, %d bytes\n", sockfd, len); + ret = write(g_lesp_state.fd, buf, len); } while (ret >= 0) @@ -1373,15 +2504,12 @@ ssize_t lesp_send(int sockfd, FAR const uint8_t *buf, size_t len, int flags) if (*ptr == 'S') { - if (strcmp(ptr,"SEND OK") == 0) + if (strcmp(ptr, "SEND OK") == 0) break; } } - if (ret >= 0) - { - ninfo("Sent\n"); - } + pthread_mutex_unlock(&g_lesp_state.mutex); if (ret < 0) { @@ -1389,9 +2517,28 @@ ssize_t lesp_send(int sockfd, FAR const uint8_t *buf, size_t len, int flags) return -1; } - return 0; + ninfo("Sent\n"); + + return len; } +/**************************************************************************** + * Name: lesp_recv + * + * Description: + * Equivalent of POSIX recv for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor returned by socket() + * buf Data to receive + * len Length of data to receive + * flags Send flags + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + ssize_t lesp_recv(int sockfd, FAR uint8_t *buf, size_t len, int flags) { int ret = 0; @@ -1404,7 +2551,7 @@ ssize_t lesp_recv(int sockfd, FAR uint8_t *buf, size_t len, int flags) return -1; } - pthread_mutex_lock(&g_lesp_state.mutex); + pthread_mutex_lock(&g_lesp_state.worker.mutex); UNUSED(flags); @@ -1418,19 +2565,27 @@ ssize_t lesp_recv(int sockfd, FAR uint8_t *buf, size_t len, int flags) { struct timespec ts; - if (clock_gettime(CLOCK_REALTIME,&ts) < 0) + if (clock_gettime(CLOCK_REALTIME, &ts) < 0) { ret = -1; } else { - ts.tv_sec += lespTIMEOUT_MS_RECV_S; + ts.tv_sec += sock->rcv_timeo.tv_sec; + ts.tv_nsec += sock->rcv_timeo.tv_nsec; + if (ts.tv_nsec >= 1000000000) + { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + sock->sem = &sem; + while (ret >= 0 && sock->inndx == sock->outndx) { - pthread_mutex_unlock(&g_lesp_state.mutex); - ret = sem_timedwait(&sem,&ts); - pthread_mutex_lock(&g_lesp_state.mutex); + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + ret = sem_timedwait(&sem, &ts); + pthread_mutex_lock(&g_lesp_state.worker.mutex); } sock->sem = NULL; @@ -1462,7 +2617,7 @@ ssize_t lesp_recv(int sockfd, FAR uint8_t *buf, size_t len, int flags) } } - pthread_mutex_unlock(&g_lesp_state.mutex); + pthread_mutex_unlock(&g_lesp_state.worker.mutex); if (ret < 0) { @@ -1472,32 +2627,191 @@ ssize_t lesp_recv(int sockfd, FAR uint8_t *buf, size_t len, int flags) return ret; } +/**************************************************************************** + * Name: lesp_setsockopt + * + * Description: + * Equivalent of POSIX setsockopt for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor returned by socket() + * level Protocol level to set the option + * option identifies the option to set + * value Points to the argument value + * value_len The length of the argument value + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + int lesp_setsockopt(int sockfd, int level, int option, FAR const void *value, socklen_t value_len) { - nerr("ERROR: Not implemented %s\n", __func__); - return -1; + int ret = 0; + lesp_socket_t *sock; + + if (level != SOL_SOCKET) + { + nerr("ERROR: %s:Not implemented level:%d\n", __func__, level); + errno = EINVAL; + return -1; + } + + pthread_mutex_lock(&g_lesp_state.worker.mutex); + + sock = get_sock(sockfd); + if (sock == NULL) + { + ret = -1; + } + else + { + switch (option) + { + case SO_RCVTIMEO: + if (value_len == sizeof(struct timeval)) + { + sock->rcv_timeo.tv_sec = ((struct timeval *)(value))->tv_sec; + sock->rcv_timeo.tv_nsec = ((struct timeval *)(value))->tv_usec; + sock->rcv_timeo.tv_nsec *= 1000; /* tv_usec to tv_nsec */ + } + else + { + nerr("ERROR: Wrong size:%d\n", level); + errno = EINVAL; + ret = -1; + } + break; + + default: + nerr("ERROR: Not implemented %s\n", __func__); + errno = EINVAL; + ret = -1; + break; + } + } + + pthread_mutex_unlock(&g_lesp_state.worker.mutex); + + return ret; } +/**************************************************************************** + * Name: lesp_getsockopt + * + * Description: + * Equivalent of POSIX getsockopt for esp8266. + * + * Input Parameters: + * sockfd Socket descriptor returned by socket() + * level Protocol level to set the option + * option identifies the option to set + * value Points to the argument value + * value_len The length of the argument value + * + * Returned Value: + * A 0 on success; -1 on error. + * + ****************************************************************************/ + int lesp_getsockopt(int sockfd, int level, int option, FAR void *value, FAR socklen_t *value_len) { - nerr("ERROR: Not implemented %s\n", __func__); - return -1; + int ret = 0; + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("getsockopt on %d socket...\n", sockfd); + + ret = lesp_check(); + + if (ret >= 0) + { + nerr("ERROR: Not implemented %s\n", __func__); + errno = EIO; + ret = -1; + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + return ret; } -int lesp_gethostbyname(char *hostname, uint16_t usNameLen, - unsigned long *out_ip_addr) -{ - nerr("ERROR: Not implemented %s\n", __func__); - return -1; -} +/**************************************************************************** + * Name: lesp_gethostbyname + * + * Description: + * Equivalent of POSIX gethostbyname for esp8266. + * + * Input Parameters: + * name - The name of the host to find. + * + * Returned Value: + * Upon successful completion, this function will return a pointer to a + * hostent structure if the requested entry was found, and a null pointer + * if the end of the database was reached or the requested entry was not + * found. + * + * Upon unsuccessful completion, gethostbyname() return NULL. + * + ****************************************************************************/ -int lesp_mdnsadvertiser(uint16_t mdnsEnabled, char *deviceServiceName, - uint16_t deviceServiceNameLength) +FAR struct hostent *lesp_gethostbyname(FAR const char *hostname) { - nerr("ERROR: Not implemented %s\n", __func__); - return -1; + int ret = 0; + + memset(&g_lesp_state.hostent, 0, sizeof(g_lesp_state.hostent)); + + g_lesp_state.hostent.h_addr_list = (char**)&g_lesp_state.h_addr_list_buf; + g_lesp_state.hostent.h_addrtype = AF_INET; + g_lesp_state.hostent.h_length = sizeof(struct sockaddr_in); + + g_lesp_state.h_addr_list_buf[0] = &g_lesp_state.in_addr; + g_lesp_state.h_addr_list_buf[1] = NULL; + + pthread_mutex_lock(&g_lesp_state.mutex); + + ninfo("Get host by name '%s' ...\n", hostname); + + ret = lesp_check(); + + if (ret >= 0) + { + ret = lesp_send_cmd("AT+CIPDOMAIN=\"%s\"\r\n", hostname); + } + + if (ret >= 0) + { + ret = lesp_read(lespTIMEOUT_MS); + } + + if (ret >= 0) + { + ninfo("Read:%s\n", g_lesp_state.bufans); + + ret = lesp_parse_cwdomain_ans_line(g_lesp_state.bufans, + &g_lesp_state.in_addr); + + if (ret < 0) + { + nerr("ERROR: Line badly formed.\n"); + errno = EIO; + } + } + if (ret >= 0) + { + ret = lesp_read_ans_ok(lespTIMEOUT_MS); + } + + pthread_mutex_unlock(&g_lesp_state.mutex); + + if (ret < 0) + { + nerr("ERROR: Get host by name.\n"); + return NULL; + } + + return &g_lesp_state.hostent; } #endif /* CONFIG_NETUTILS_ESP8266 */