mirror of
https://github.com/zephyrproject-rtos/zephyr
synced 2025-08-27 17:15:58 +00:00
- Add ESP DHCP Support - Add ESP Static IP Support including KConfig entries for configuring IP,Gateway and netMask Signed-off-by: Mohamed ElShahawi <ExtremeGTX@hotmail.com>
958 lines
23 KiB
C
958 lines
23 KiB
C
/*
|
|
* Copyright (c) 2019 Tobias Svehagen
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#define DT_DRV_COMPAT espressif_esp
|
|
|
|
#define LOG_LEVEL CONFIG_WIFI_LOG_LEVEL
|
|
#include <logging/log.h>
|
|
LOG_MODULE_REGISTER(wifi_esp);
|
|
|
|
#include <kernel.h>
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <zephyr.h>
|
|
#include <device.h>
|
|
#include <init.h>
|
|
#include <stdlib.h>
|
|
|
|
#include <drivers/gpio.h>
|
|
#include <drivers/uart.h>
|
|
|
|
#include <net/net_if.h>
|
|
#include <net/net_offload.h>
|
|
#include <net/wifi_mgmt.h>
|
|
|
|
#include "esp.h"
|
|
|
|
#define RX_NET_PKT_ALLOC_TIMEOUT \
|
|
K_MSEC(CONFIG_WIFI_ESP_RX_NET_PKT_ALLOC_TIMEOUT)
|
|
|
|
/* pin settings */
|
|
enum modem_control_pins {
|
|
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
|
|
ESP_POWER,
|
|
#endif
|
|
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
|
|
ESP_RESET,
|
|
#endif
|
|
NUM_PINS,
|
|
};
|
|
|
|
static struct modem_pin modem_pins[] = {
|
|
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
|
|
MODEM_PIN(DT_INST_GPIO_LABEL(0, power_gpios),
|
|
DT_INST_GPIO_PIN(0, power_gpios),
|
|
DT_INST_GPIO_FLAGS(0, power_gpios) | GPIO_OUTPUT_INACTIVE),
|
|
#endif
|
|
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
|
|
MODEM_PIN(DT_INST_GPIO_LABEL(0, reset_gpios),
|
|
DT_INST_GPIO_PIN(0, reset_gpios),
|
|
DT_INST_GPIO_FLAGS(0, reset_gpios) | GPIO_OUTPUT_INACTIVE),
|
|
#endif
|
|
};
|
|
|
|
NET_BUF_POOL_DEFINE(mdm_recv_pool, MDM_RECV_MAX_BUF, MDM_RECV_BUF_SIZE,
|
|
0, NULL);
|
|
|
|
/* RX thread structures */
|
|
K_KERNEL_STACK_DEFINE(esp_rx_stack,
|
|
CONFIG_WIFI_ESP_RX_STACK_SIZE);
|
|
struct k_thread esp_rx_thread;
|
|
|
|
/* RX thread work queue */
|
|
K_KERNEL_STACK_DEFINE(esp_workq_stack,
|
|
CONFIG_WIFI_ESP_WORKQ_STACK_SIZE);
|
|
|
|
struct esp_data esp_driver_data;
|
|
|
|
/*
|
|
* Modem Response Command Handlers
|
|
*/
|
|
|
|
/* Handler: OK */
|
|
MODEM_CMD_DEFINE(on_cmd_ok)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
|
|
modem_cmd_handler_set_error(data, 0);
|
|
k_sem_give(&dev->sem_response);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Handler: ERROR */
|
|
MODEM_CMD_DEFINE(on_cmd_error)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
|
|
modem_cmd_handler_set_error(data, -EIO);
|
|
k_sem_give(&dev->sem_response);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* RX thread */
|
|
static void esp_rx(struct esp_data *data)
|
|
{
|
|
while (true) {
|
|
/* wait for incoming data */
|
|
k_sem_take(&data->iface_data.rx_sem, K_FOREVER);
|
|
|
|
data->mctx.cmd_handler.process(&data->mctx.cmd_handler,
|
|
&data->mctx.iface);
|
|
|
|
/* give up time if we have a solid stream of data */
|
|
k_yield();
|
|
}
|
|
}
|
|
|
|
static char *str_unquote(char *str)
|
|
{
|
|
char *end;
|
|
|
|
if (str[0] != '"') {
|
|
return str;
|
|
}
|
|
|
|
str++;
|
|
|
|
end = strrchr(str, '"');
|
|
if (end != NULL) {
|
|
*end = 0;
|
|
}
|
|
|
|
return str;
|
|
}
|
|
|
|
/* +CIPSTAMAC:"xx:xx:xx:xx:xx:xx" */
|
|
MODEM_CMD_DEFINE(on_cmd_cipstamac)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
char *mac;
|
|
|
|
mac = str_unquote(argv[0]);
|
|
net_bytes_from_str(dev->mac_addr, sizeof(dev->mac_addr), mac);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* +CWLAP:(sec,ssid,rssi,channel) */
|
|
MODEM_CMD_DEFINE(on_cmd_cwlap)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
struct wifi_scan_result res = { 0 };
|
|
int i;
|
|
|
|
i = strtol(&argv[0][1], NULL, 10);
|
|
if (i == 0) {
|
|
res.security = WIFI_SECURITY_TYPE_NONE;
|
|
} else {
|
|
res.security = WIFI_SECURITY_TYPE_PSK;
|
|
}
|
|
|
|
argv[1] = str_unquote(argv[1]);
|
|
i = strlen(argv[1]);
|
|
if (i > sizeof(res.ssid)) {
|
|
i = sizeof(res.ssid);
|
|
}
|
|
|
|
memcpy(res.ssid, argv[1], i);
|
|
res.ssid_length = i;
|
|
res.rssi = strtol(argv[2], NULL, 10);
|
|
res.channel = strtol(argv[3], NULL, 10);
|
|
|
|
if (dev->scan_cb) {
|
|
dev->scan_cb(dev->net_iface, 0, &res);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct modem_cmd response_cmds[] = {
|
|
MODEM_CMD("OK", on_cmd_ok, 0U, ""), /* 3GPP */
|
|
MODEM_CMD("ERROR", on_cmd_error, 0U, ""), /* 3GPP */
|
|
};
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_wifi_connected)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
|
|
if (esp_flag_is_set(dev, EDF_STA_CONNECTED)) {
|
|
return 0;
|
|
}
|
|
|
|
esp_flag_set(dev, EDF_STA_CONNECTED);
|
|
wifi_mgmt_raise_connect_result_event(dev->net_iface, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_wifi_disconnected)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
|
|
if (!esp_flag_is_set(dev, EDF_STA_CONNECTED)) {
|
|
return 0;
|
|
}
|
|
|
|
esp_flag_clear(dev, EDF_STA_CONNECTED);
|
|
net_if_ipv4_addr_rm(dev->net_iface, &dev->ip);
|
|
wifi_mgmt_raise_disconnect_result_event(dev->net_iface, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* +CIPSTA:ip:"<ip>"
|
|
* +CIPSTA:gateway:"<ip>"
|
|
* +CIPSTA:netmask:"<ip>"
|
|
*/
|
|
MODEM_CMD_DEFINE(on_cmd_cipsta)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
char *ip;
|
|
|
|
ip = str_unquote(argv[1]);
|
|
|
|
if (!strcmp(argv[0], "ip")) {
|
|
net_addr_pton(AF_INET, ip, &dev->ip);
|
|
} else if (!strcmp(argv[0], "gateway")) {
|
|
net_addr_pton(AF_INET, ip, &dev->gw);
|
|
} else if (!strcmp(argv[0], "netmask")) {
|
|
net_addr_pton(AF_INET, ip, &dev->nm);
|
|
} else {
|
|
LOG_WRN("Unknown IP type %s", log_strdup(argv[0]));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void esp_ip_addr_work(struct k_work *work)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(work, struct esp_data,
|
|
ip_addr_work);
|
|
int ret;
|
|
|
|
struct modem_cmd cmds[] = {
|
|
MODEM_CMD("+"_CIPSTA":", on_cmd_cipsta, 2U, ":"),
|
|
};
|
|
|
|
ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler,
|
|
cmds, ARRAY_SIZE(cmds), "AT+"_CIPSTA"?",
|
|
&dev->sem_response, ESP_CMD_TIMEOUT);
|
|
if (ret < 0) {
|
|
LOG_WRN("Failed to query IP settings: ret %d", ret);
|
|
k_delayed_work_submit_to_queue(&dev->workq, &dev->ip_addr_work,
|
|
K_SECONDS(5));
|
|
return;
|
|
}
|
|
|
|
/* update interface addresses */
|
|
net_if_ipv4_set_gw(dev->net_iface, &dev->gw);
|
|
net_if_ipv4_set_netmask(dev->net_iface, &dev->nm);
|
|
#if defined(CONFIG_WIFI_ESP_IP_STATIC)
|
|
net_if_ipv4_addr_add(dev->net_iface, &dev->ip, NET_ADDR_MANUAL, 0);
|
|
#else
|
|
net_if_ipv4_addr_add(dev->net_iface, &dev->ip, NET_ADDR_DHCP, 0);
|
|
#endif
|
|
}
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_got_ip)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
|
|
k_delayed_work_submit_to_queue(&dev->workq, &dev->ip_addr_work,
|
|
K_SECONDS(1));
|
|
|
|
return 0;
|
|
}
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_connect)
|
|
{
|
|
struct esp_socket *sock;
|
|
struct esp_data *dev;
|
|
uint8_t link_id;
|
|
|
|
link_id = data->match_buf[0] - '0';
|
|
|
|
dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data);
|
|
sock = esp_socket_from_link_id(dev, link_id);
|
|
if (sock == NULL) {
|
|
LOG_ERR("No socket for link %d", link_id);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_closed)
|
|
{
|
|
struct esp_socket *sock;
|
|
struct esp_data *dev;
|
|
uint8_t link_id;
|
|
|
|
link_id = data->match_buf[0] - '0';
|
|
|
|
dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data);
|
|
sock = esp_socket_from_link_id(dev, link_id);
|
|
if (sock == NULL) {
|
|
LOG_ERR("No socket for link %d", link_id);
|
|
return 0;
|
|
}
|
|
|
|
if (!esp_socket_connected(sock)) {
|
|
LOG_WRN("Link %d already closed", link_id);
|
|
return 0;
|
|
}
|
|
|
|
sock->flags &= ~(ESP_SOCK_CONNECTED);
|
|
k_work_submit_to_queue(&dev->workq, &sock->recv_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct net_pkt *esp_prepare_pkt(struct esp_data *dev, struct net_buf *src,
|
|
size_t offset, size_t len)
|
|
{
|
|
struct net_buf *frag;
|
|
struct net_pkt *pkt;
|
|
size_t to_copy;
|
|
|
|
pkt = net_pkt_rx_alloc_with_buffer(dev->net_iface, len, AF_UNSPEC,
|
|
0, RX_NET_PKT_ALLOC_TIMEOUT);
|
|
if (!pkt) {
|
|
return NULL;
|
|
}
|
|
|
|
frag = src;
|
|
|
|
/* find the right fragment to start copying from */
|
|
while (frag && offset >= frag->len) {
|
|
offset -= frag->len;
|
|
frag = frag->frags;
|
|
}
|
|
|
|
/* traverse the fragment chain until len bytes are copied */
|
|
while (frag && len > 0) {
|
|
to_copy = MIN(len, frag->len - offset);
|
|
if (net_pkt_write(pkt, frag->data + offset, to_copy) != 0) {
|
|
net_pkt_unref(pkt);
|
|
return NULL;
|
|
}
|
|
|
|
/* to_copy is always <= len */
|
|
len -= to_copy;
|
|
frag = frag->frags;
|
|
|
|
/* after the first iteration, this value will be 0 */
|
|
offset = 0;
|
|
}
|
|
|
|
net_pkt_cursor_init(pkt);
|
|
|
|
return pkt;
|
|
}
|
|
|
|
/*
|
|
* Passive mode: "+IPD,<id>,<len>\r\n"
|
|
* Other: "+IPD,<id>,<len>:<data>"
|
|
*/
|
|
#define MIN_IPD_LEN (sizeof("+IPD,I,LE") - 1)
|
|
#define MAX_IPD_LEN (sizeof("+IPD,I,LLLLE") - 1)
|
|
MODEM_CMD_DIRECT_DEFINE(on_cmd_ipd)
|
|
{
|
|
char *endptr, end, ipd_buf[MAX_IPD_LEN + 1];
|
|
int data_offset, data_len, ret;
|
|
size_t match_len, frags_len;
|
|
struct esp_socket *sock;
|
|
struct esp_data *dev;
|
|
struct net_pkt *pkt;
|
|
uint8_t link_id;
|
|
|
|
dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data);
|
|
|
|
frags_len = net_buf_frags_len(data->rx_buf);
|
|
|
|
/* Wait until minimum cmd length is available */
|
|
if (frags_len < MIN_IPD_LEN) {
|
|
ret = -EAGAIN;
|
|
goto out;
|
|
}
|
|
|
|
match_len = net_buf_linearize(ipd_buf, MAX_IPD_LEN,
|
|
data->rx_buf, 0, MAX_IPD_LEN);
|
|
|
|
ipd_buf[match_len] = 0;
|
|
if (ipd_buf[len] != ',' || ipd_buf[len + 2] != ',') {
|
|
LOG_ERR("Invalid IPD: %s", log_strdup(ipd_buf));
|
|
ret = len;
|
|
goto out;
|
|
}
|
|
|
|
link_id = ipd_buf[len + 1] - '0';
|
|
sock = esp_socket_from_link_id(dev, link_id);
|
|
if (sock == NULL) {
|
|
LOG_ERR("No socket for link %d", link_id);
|
|
ret = len;
|
|
goto out;
|
|
}
|
|
|
|
/* When using passive mode, the +IPD command ends with \r\n */
|
|
if (ESP_PROTO_PASSIVE(sock->ip_proto)) {
|
|
end = '\r';
|
|
} else {
|
|
end = ':';
|
|
}
|
|
|
|
data_len = strtol(&ipd_buf[len + 3], &endptr, 10);
|
|
if (endptr == &ipd_buf[len + 3] ||
|
|
(*endptr == 0 && match_len >= MAX_IPD_LEN)) {
|
|
/* Invalid */
|
|
LOG_ERR("Invalid IPD len: %s", log_strdup(ipd_buf));
|
|
ret = len;
|
|
goto out;
|
|
} else if (*endptr == 0) {
|
|
ret = -EAGAIN;
|
|
goto out;
|
|
} else if (*endptr != end) {
|
|
LOG_ERR("Invalid cmd end 0x%02x, expected 0x%02x", *endptr,
|
|
end);
|
|
ret = len;
|
|
goto out;
|
|
}
|
|
|
|
*endptr = 0;
|
|
data_offset = strlen(ipd_buf) + 1;
|
|
|
|
/*
|
|
* When using passive TCP, the data itself is not included in the +IPD
|
|
* command but must be polled with AT+CIPRECVDATA.
|
|
*/
|
|
if (ESP_PROTO_PASSIVE(sock->ip_proto)) {
|
|
sock->bytes_avail = data_len;
|
|
k_work_submit_to_queue(&dev->workq, &sock->recvdata_work);
|
|
ret = data_offset;
|
|
goto out;
|
|
}
|
|
|
|
/* Do we have the whole message? */
|
|
if (data_offset + data_len > frags_len) {
|
|
ret = -EAGAIN;
|
|
goto out;
|
|
}
|
|
|
|
ret = data_offset + data_len; /* Skip */
|
|
|
|
if ((sock->flags & (ESP_SOCK_CONNECTED | ESP_SOCK_CLOSE_PENDING)) !=
|
|
ESP_SOCK_CONNECTED) {
|
|
LOG_DBG("Received data on closed link %d", link_id);
|
|
goto out;
|
|
}
|
|
|
|
pkt = esp_prepare_pkt(dev, data->rx_buf, data_offset, data_len);
|
|
if (!pkt) {
|
|
LOG_ERR("Failed to get net_pkt: len %d", data_len);
|
|
if (sock->type == SOCK_STREAM) {
|
|
sock->flags |= ESP_SOCK_CLOSE_PENDING;
|
|
}
|
|
goto submit_work;
|
|
}
|
|
|
|
k_fifo_put(&sock->fifo_rx_pkt, pkt);
|
|
|
|
submit_work:
|
|
k_work_submit_to_queue(&dev->workq, &sock->recv_work);
|
|
|
|
out:
|
|
return ret;
|
|
}
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_busy_sending)
|
|
{
|
|
LOG_WRN("Busy sending");
|
|
return 0;
|
|
}
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_busy_processing)
|
|
{
|
|
LOG_WRN("Busy processing");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* The 'ready' command is sent when device has booted and is ready to receive
|
|
* commands. It is only expected after a reset of the device.
|
|
*/
|
|
MODEM_CMD_DEFINE(on_cmd_ready)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
k_sem_give(&dev->sem_if_ready);
|
|
|
|
if (net_if_is_up(dev->net_iface)) {
|
|
net_if_down(dev->net_iface);
|
|
LOG_ERR("Unexpected reset");
|
|
}
|
|
|
|
if (esp_flag_is_set(dev, EDF_STA_CONNECTING)) {
|
|
esp_flag_clear(dev, EDF_STA_CONNECTING);
|
|
wifi_mgmt_raise_connect_result_event(dev->net_iface, -1);
|
|
} else if (esp_flag_is_set(dev, EDF_STA_CONNECTED)) {
|
|
esp_flag_clear(dev, EDF_STA_CONNECTED);
|
|
wifi_mgmt_raise_disconnect_result_event(dev->net_iface, 0);
|
|
}
|
|
|
|
net_if_ipv4_addr_rm(dev->net_iface, &dev->ip);
|
|
k_work_submit_to_queue(&dev->workq, &dev->init_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct modem_cmd unsol_cmds[] = {
|
|
MODEM_CMD("WIFI CONNECTED", on_cmd_wifi_connected, 0U, ""),
|
|
MODEM_CMD("WIFI DISCONNECT", on_cmd_wifi_disconnected, 0U, ""),
|
|
MODEM_CMD("WIFI GOT IP", on_cmd_got_ip, 0U, ""),
|
|
MODEM_CMD("0,CONNECT", on_cmd_connect, 0U, ""),
|
|
MODEM_CMD("1,CONNECT", on_cmd_connect, 0U, ""),
|
|
MODEM_CMD("2,CONNECT", on_cmd_connect, 0U, ""),
|
|
MODEM_CMD("3,CONNECT", on_cmd_connect, 0U, ""),
|
|
MODEM_CMD("4,CONNECT", on_cmd_connect, 0U, ""),
|
|
MODEM_CMD("0,CLOSED", on_cmd_closed, 0U, ""),
|
|
MODEM_CMD("1,CLOSED", on_cmd_closed, 0U, ""),
|
|
MODEM_CMD("2,CLOSED", on_cmd_closed, 0U, ""),
|
|
MODEM_CMD("3,CLOSED", on_cmd_closed, 0U, ""),
|
|
MODEM_CMD("4,CLOSED", on_cmd_closed, 0U, ""),
|
|
MODEM_CMD("busy s...", on_cmd_busy_sending, 0U, ""),
|
|
MODEM_CMD("busy p...", on_cmd_busy_processing, 0U, ""),
|
|
MODEM_CMD("ready", on_cmd_ready, 0U, ""),
|
|
MODEM_CMD_DIRECT("+IPD", on_cmd_ipd),
|
|
};
|
|
|
|
static void esp_mgmt_scan_work(struct k_work *work)
|
|
{
|
|
struct esp_data *dev;
|
|
int ret;
|
|
struct modem_cmd cmds[] = {
|
|
MODEM_CMD("+CWLAP:", on_cmd_cwlap, 4U, ","),
|
|
};
|
|
|
|
dev = CONTAINER_OF(work, struct esp_data, scan_work);
|
|
|
|
ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler,
|
|
cmds, ARRAY_SIZE(cmds), "AT+CWLAP",
|
|
&dev->sem_response, ESP_SCAN_TIMEOUT);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to scan: ret %d", ret);
|
|
}
|
|
|
|
dev->scan_cb(dev->net_iface, 0, NULL);
|
|
dev->scan_cb = NULL;
|
|
}
|
|
|
|
static int esp_mgmt_scan(const struct device *dev, scan_result_cb_t cb)
|
|
{
|
|
struct esp_data *data = dev->data;
|
|
|
|
if (data->scan_cb != NULL) {
|
|
return -EINPROGRESS;
|
|
}
|
|
|
|
if (!net_if_is_up(data->net_iface)) {
|
|
return -EIO;
|
|
}
|
|
|
|
data->scan_cb = cb;
|
|
|
|
k_work_submit_to_queue(&data->workq, &data->scan_work);
|
|
|
|
return 0;
|
|
};
|
|
|
|
MODEM_CMD_DEFINE(on_cmd_fail)
|
|
{
|
|
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
|
|
cmd_handler_data);
|
|
|
|
modem_cmd_handler_set_error(data, -EIO);
|
|
k_sem_give(&dev->sem_response);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void esp_mgmt_connect_work(struct k_work *work)
|
|
{
|
|
struct esp_data *dev;
|
|
int ret;
|
|
struct modem_cmd cmds[] = {
|
|
MODEM_CMD("FAIL", on_cmd_fail, 0U, ""),
|
|
};
|
|
|
|
dev = CONTAINER_OF(work, struct esp_data, connect_work);
|
|
|
|
ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler,
|
|
cmds, ARRAY_SIZE(cmds), dev->conn_cmd,
|
|
&dev->sem_response, ESP_CONNECT_TIMEOUT);
|
|
|
|
memset(dev->conn_cmd, 0, sizeof(dev->conn_cmd));
|
|
|
|
if (ret < 0) {
|
|
if (esp_flag_is_set(dev, EDF_STA_CONNECTED)) {
|
|
esp_flag_clear(dev, EDF_STA_CONNECTED);
|
|
wifi_mgmt_raise_disconnect_result_event(dev->net_iface,
|
|
0);
|
|
} else {
|
|
wifi_mgmt_raise_connect_result_event(dev->net_iface,
|
|
ret);
|
|
}
|
|
} else if (!esp_flag_is_set(dev, EDF_STA_CONNECTED)) {
|
|
esp_flag_set(dev, EDF_STA_CONNECTED);
|
|
wifi_mgmt_raise_connect_result_event(dev->net_iface, 0);
|
|
}
|
|
|
|
esp_flag_clear(dev, EDF_STA_CONNECTING);
|
|
}
|
|
|
|
static int esp_mgmt_connect(const struct device *dev,
|
|
struct wifi_connect_req_params *params)
|
|
{
|
|
struct esp_data *data = dev->data;
|
|
int len;
|
|
|
|
if (!net_if_is_up(data->net_iface)) {
|
|
return -EIO;
|
|
}
|
|
|
|
if (esp_flag_is_set(data, EDF_STA_CONNECTED) ||
|
|
esp_flag_is_set(data, EDF_STA_CONNECTING)) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
esp_flag_set(data, EDF_STA_CONNECTING);
|
|
|
|
len = snprintk(data->conn_cmd, sizeof(data->conn_cmd),
|
|
"AT+"_CWJAP"=\"");
|
|
memcpy(&data->conn_cmd[len], params->ssid, params->ssid_length);
|
|
len += params->ssid_length;
|
|
|
|
if (params->security == WIFI_SECURITY_TYPE_PSK) {
|
|
len += snprintk(&data->conn_cmd[len],
|
|
sizeof(data->conn_cmd) - len, "\",\"");
|
|
memcpy(&data->conn_cmd[len], params->psk, params->psk_length);
|
|
len += params->psk_length;
|
|
}
|
|
|
|
len += snprintk(&data->conn_cmd[len], sizeof(data->conn_cmd) - len,
|
|
"\"");
|
|
|
|
k_work_submit_to_queue(&data->workq, &data->connect_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int esp_mgmt_disconnect(const struct device *dev)
|
|
{
|
|
struct esp_data *data = dev->data;
|
|
int ret;
|
|
|
|
ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler,
|
|
NULL, 0, "AT+CWQAP", &data->sem_response,
|
|
ESP_CMD_TIMEOUT);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int esp_mgmt_ap_enable(const struct device *dev,
|
|
struct wifi_connect_req_params *params)
|
|
{
|
|
char cmd[sizeof("AT+"_CWSAP"=\"\",\"\",xx,x") + WIFI_SSID_MAX_LEN +
|
|
WIFI_PSK_MAX_LEN];
|
|
struct esp_data *data = dev->data;
|
|
int ecn = 0, len, ret;
|
|
|
|
ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler,
|
|
NULL, 0, "AT+"_CWMODE"=3", &data->sem_response,
|
|
ESP_CMD_TIMEOUT);
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to enable AP mode, ret %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
len = snprintk(cmd, sizeof(cmd), "AT+"_CWSAP"=\"");
|
|
memcpy(&cmd[len], params->ssid, params->ssid_length);
|
|
len += params->ssid_length;
|
|
|
|
if (params->security == WIFI_SECURITY_TYPE_PSK) {
|
|
len += snprintk(&cmd[len], sizeof(cmd) - len, "\",\"");
|
|
memcpy(&cmd[len], params->psk, params->psk_length);
|
|
len += params->psk_length;
|
|
ecn = 3;
|
|
} else {
|
|
len += snprintk(&cmd[len], sizeof(cmd) - len, "\",\"");
|
|
}
|
|
|
|
snprintk(&cmd[len], sizeof(cmd) - len, "\",%d,%d", params->channel,
|
|
ecn);
|
|
|
|
ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler,
|
|
NULL, 0, cmd, &data->sem_response,
|
|
ESP_CMD_TIMEOUT);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int esp_mgmt_ap_disable(const struct device *dev)
|
|
{
|
|
struct esp_data *data = dev->data;
|
|
int ret;
|
|
|
|
ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler,
|
|
NULL, 0, "AT+"_CWMODE"=1", &data->sem_response,
|
|
ESP_CMD_TIMEOUT);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void esp_init_work(struct k_work *work)
|
|
{
|
|
struct esp_data *dev;
|
|
int ret;
|
|
static struct setup_cmd setup_cmds[] = {
|
|
SETUP_CMD_NOHANDLE("AT"),
|
|
/* turn off echo */
|
|
SETUP_CMD_NOHANDLE("ATE0"),
|
|
SETUP_CMD_NOHANDLE("AT+UART_CUR="_UART_CUR),
|
|
#if DT_INST_NODE_HAS_PROP(0, target_speed)
|
|
};
|
|
static struct setup_cmd setup_cmds_target_baudrate[] = {
|
|
SETUP_CMD_NOHANDLE("AT"),
|
|
#endif
|
|
SETUP_CMD_NOHANDLE("AT+"_CWMODE"=1"),
|
|
#if defined(CONFIG_WIFI_ESP_IP_STATIC)
|
|
/* enable Static IP Config */
|
|
SETUP_CMD_NOHANDLE(ESP_CMD_DHCP_ENABLE(STATION, 0)),
|
|
SETUP_CMD_NOHANDLE(ESP_CMD_SET_IP(CONFIG_WIFI_ESP_IP_ADDRESS,
|
|
CONFIG_WIFI_ESP_IP_GATEWAY,
|
|
CONFIG_WIFI_ESP_IP_MASK)),
|
|
#else
|
|
/* enable DHCP */
|
|
SETUP_CMD_NOHANDLE(ESP_CMD_DHCP_ENABLE(STATION, 1)),
|
|
#endif
|
|
/* enable multiple socket support */
|
|
SETUP_CMD_NOHANDLE("AT+CIPMUX=1"),
|
|
/* only need ecn,ssid,rssi,channel */
|
|
SETUP_CMD_NOHANDLE("AT+CWLAPOPT=0,23"),
|
|
#if defined(CONFIG_WIFI_ESP_AT_VERSION_2_0)
|
|
SETUP_CMD_NOHANDLE("AT+CWAUTOCONN=0"),
|
|
#endif
|
|
#if defined(CONFIG_WIFI_ESP_PASSIVE_MODE)
|
|
SETUP_CMD_NOHANDLE("AT+CIPRECVMODE=1"),
|
|
#endif
|
|
SETUP_CMD("AT+"_CIPSTAMAC"?", "+"_CIPSTAMAC":",
|
|
on_cmd_cipstamac, 1U, ""),
|
|
};
|
|
|
|
dev = CONTAINER_OF(work, struct esp_data, init_work);
|
|
|
|
ret = modem_cmd_handler_setup_cmds(&dev->mctx.iface,
|
|
&dev->mctx.cmd_handler, setup_cmds,
|
|
ARRAY_SIZE(setup_cmds),
|
|
&dev->sem_response,
|
|
ESP_INIT_TIMEOUT);
|
|
if (ret < 0) {
|
|
LOG_ERR("Init failed %d", ret);
|
|
return;
|
|
}
|
|
|
|
#if DT_INST_NODE_HAS_PROP(0, target_speed)
|
|
static const struct uart_config uart_config = {
|
|
.baudrate = DT_INST_PROP(0, target_speed),
|
|
.parity = UART_CFG_PARITY_NONE,
|
|
.stop_bits = UART_CFG_STOP_BITS_1,
|
|
.data_bits = UART_CFG_DATA_BITS_8,
|
|
.flow_ctrl = DT_PROP(ESP_BUS, hw_flow_control) ?
|
|
UART_CFG_FLOW_CTRL_RTS_CTS : UART_CFG_FLOW_CTRL_NONE,
|
|
};
|
|
|
|
ret = uart_configure(device_get_binding(DT_INST_BUS_LABEL(0)),
|
|
&uart_config);
|
|
if (ret < 0) {
|
|
LOG_ERR("Baudrate change failed %d", ret);
|
|
return;
|
|
}
|
|
|
|
/* arbitrary sleep period to give ESP enough time to reconfigure */
|
|
k_sleep(K_MSEC(100));
|
|
|
|
ret = modem_cmd_handler_setup_cmds(&dev->mctx.iface,
|
|
&dev->mctx.cmd_handler,
|
|
setup_cmds_target_baudrate,
|
|
ARRAY_SIZE(setup_cmds_target_baudrate),
|
|
&dev->sem_response,
|
|
ESP_INIT_TIMEOUT);
|
|
if (ret < 0) {
|
|
LOG_ERR("Init failed %d", ret);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
net_if_set_link_addr(dev->net_iface, dev->mac_addr,
|
|
sizeof(dev->mac_addr), NET_LINK_ETHERNET);
|
|
|
|
LOG_INF("ESP Wi-Fi ready");
|
|
|
|
net_if_up(dev->net_iface);
|
|
|
|
k_sem_give(&dev->sem_if_up);
|
|
}
|
|
|
|
static void esp_reset(struct esp_data *dev)
|
|
{
|
|
int ret;
|
|
|
|
if (net_if_is_up(dev->net_iface)) {
|
|
net_if_down(dev->net_iface);
|
|
}
|
|
|
|
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
|
|
modem_pin_write(&dev->mctx, ESP_POWER, 0);
|
|
k_sleep(K_MSEC(100));
|
|
modem_pin_write(&dev->mctx, ESP_POWER, 1);
|
|
#elif DT_INST_NODE_HAS_PROP(0, reset_gpios)
|
|
modem_pin_write(&dev->mctx, ESP_RESET, 1);
|
|
k_sleep(K_MSEC(100));
|
|
modem_pin_write(&dev->mctx, ESP_RESET, 0);
|
|
#else
|
|
int retries = 3;
|
|
|
|
while (retries--) {
|
|
ret = modem_cmd_send(&dev->mctx.iface, &dev->mctx.cmd_handler,
|
|
NULL, 0, "AT+RST", &dev->sem_if_ready,
|
|
K_MSEC(CONFIG_WIFI_ESP_RESET_TIMEOUT));
|
|
if (ret == 0 || ret != -ETIMEDOUT) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ret < 0) {
|
|
LOG_ERR("Failed to reset device: %d", ret);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
LOG_INF("Waiting for interface to come up");
|
|
|
|
ret = k_sem_take(&dev->sem_if_up, ESP_INIT_TIMEOUT);
|
|
if (ret == -EAGAIN) {
|
|
LOG_ERR("Timeout waiting for interface");
|
|
}
|
|
}
|
|
|
|
static void esp_iface_init(struct net_if *iface)
|
|
{
|
|
const struct device *dev = net_if_get_device(iface);
|
|
struct esp_data *data = dev->data;
|
|
|
|
net_if_flag_set(iface, NET_IF_NO_AUTO_START);
|
|
data->net_iface = iface;
|
|
esp_offload_init(iface);
|
|
esp_reset(data);
|
|
}
|
|
|
|
static const struct net_wifi_mgmt_offload esp_api = {
|
|
.iface_api.init = esp_iface_init,
|
|
.scan = esp_mgmt_scan,
|
|
.connect = esp_mgmt_connect,
|
|
.disconnect = esp_mgmt_disconnect,
|
|
.ap_enable = esp_mgmt_ap_enable,
|
|
.ap_disable = esp_mgmt_ap_disable,
|
|
};
|
|
|
|
static int esp_init(const struct device *dev)
|
|
{
|
|
struct esp_data *data = dev->data;
|
|
int ret = 0;
|
|
|
|
k_sem_init(&data->sem_tx_ready, 0, 1);
|
|
k_sem_init(&data->sem_response, 0, 1);
|
|
k_sem_init(&data->sem_if_ready, 0, 1);
|
|
k_sem_init(&data->sem_if_up, 0, 1);
|
|
|
|
k_work_init(&data->init_work, esp_init_work);
|
|
k_delayed_work_init(&data->ip_addr_work, esp_ip_addr_work);
|
|
k_work_init(&data->scan_work, esp_mgmt_scan_work);
|
|
k_work_init(&data->connect_work, esp_mgmt_connect_work);
|
|
|
|
esp_socket_init(data);
|
|
|
|
/* initialize the work queue */
|
|
k_work_q_start(&data->workq, esp_workq_stack,
|
|
K_KERNEL_STACK_SIZEOF(esp_workq_stack),
|
|
K_PRIO_COOP(CONFIG_WIFI_ESP_WORKQ_THREAD_PRIORITY));
|
|
k_thread_name_set(&data->workq.thread, "esp_workq");
|
|
|
|
/* cmd handler */
|
|
data->cmd_handler_data.cmds[CMD_RESP] = response_cmds;
|
|
data->cmd_handler_data.cmds_len[CMD_RESP] = ARRAY_SIZE(response_cmds);
|
|
data->cmd_handler_data.cmds[CMD_UNSOL] = unsol_cmds;
|
|
data->cmd_handler_data.cmds_len[CMD_UNSOL] = ARRAY_SIZE(unsol_cmds);
|
|
data->cmd_handler_data.match_buf = &data->cmd_match_buf[0];
|
|
data->cmd_handler_data.match_buf_len = sizeof(data->cmd_match_buf);
|
|
data->cmd_handler_data.buf_pool = &mdm_recv_pool;
|
|
data->cmd_handler_data.alloc_timeout = K_NO_WAIT;
|
|
data->cmd_handler_data.eol = "\r\n";
|
|
ret = modem_cmd_handler_init(&data->mctx.cmd_handler,
|
|
&data->cmd_handler_data);
|
|
if (ret < 0) {
|
|
goto error;
|
|
}
|
|
|
|
/* modem interface */
|
|
data->iface_data.hw_flow_control = DT_PROP(ESP_BUS, hw_flow_control);
|
|
data->iface_data.rx_rb_buf = &data->iface_rb_buf[0];
|
|
data->iface_data.rx_rb_buf_len = sizeof(data->iface_rb_buf);
|
|
ret = modem_iface_uart_init(&data->mctx.iface, &data->iface_data,
|
|
DT_INST_BUS_LABEL(0));
|
|
if (ret < 0) {
|
|
goto error;
|
|
}
|
|
|
|
/* pin setup */
|
|
data->mctx.pins = modem_pins;
|
|
data->mctx.pins_len = ARRAY_SIZE(modem_pins);
|
|
|
|
data->mctx.driver_data = data;
|
|
|
|
ret = modem_context_register(&data->mctx);
|
|
if (ret < 0) {
|
|
LOG_ERR("Error registering modem context: %d", ret);
|
|
goto error;
|
|
}
|
|
|
|
/* start RX thread */
|
|
k_thread_create(&esp_rx_thread, esp_rx_stack,
|
|
K_KERNEL_STACK_SIZEOF(esp_rx_stack),
|
|
(k_thread_entry_t)esp_rx,
|
|
data, NULL, NULL,
|
|
K_PRIO_COOP(CONFIG_WIFI_ESP_RX_THREAD_PRIORITY), 0,
|
|
K_NO_WAIT);
|
|
k_thread_name_set(&esp_rx_thread, "esp_rx");
|
|
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
NET_DEVICE_OFFLOAD_INIT(wifi_esp, CONFIG_WIFI_ESP_NAME,
|
|
esp_init, device_pm_control_nop, &esp_driver_data, NULL,
|
|
CONFIG_WIFI_INIT_PRIORITY, &esp_api,
|
|
ESP_MTU);
|