zephyr/drivers/wifi/esp_at/esp.c
Marcin Niestroj b4854debd1 drivers: wifi: esp_at: rename driver from esp
Recently WiFi ESP32 driver (utilizing WiFi radio in ESP32 SoC) was
introduced into drivers/wifi/esp32/ and it already caused confusion as
there was existing drivers/wifi/esp/ directory for ESP-AT
driver (utilizing external WiFi chip, by communicating using AT commands
from any serial capable platform). So question has arisen whether it is
good to merge both, while they are totally different drivers.

Rename ESP-AT driver to be placed in drivers/wifi/esp_at/, so that it is
easier to figure out difference between "esp32" and "esp_at" just by
looking at driver name. Rename also DT compatible and all Kconfig
options for the same reason.

Signed-off-by: Marcin Niestroj <m.niestroj@grinn-global.com>
2021-05-06 13:21:39 -04:00

1141 lines
26 KiB
C

/*
* Copyright (c) 2019 Tobias Svehagen
* Copyright (c) 2020 Grinn
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT espressif_esp_at
#include <logging/log.h>
LOG_MODULE_REGISTER(wifi_esp_at, CONFIG_WIFI_LOG_LEVEL);
#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/dns_resolve.h>
#include <net/net_if.h>
#include <net/net_ip.h>
#include <net/net_offload.h>
#include <net/wifi_mgmt.h>
#include "esp.h"
/* 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_AT_RX_STACK_SIZE);
struct k_thread esp_rx_thread;
/* RX thread work queue */
K_KERNEL_STACK_DEFINE(esp_workq_stack,
CONFIG_WIFI_ESP_AT_WORKQ_STACK_SIZE);
struct esp_data esp_driver_data;
static void esp_configure_hostname(struct esp_data *data)
{
#if defined(CONFIG_NET_HOSTNAME_ENABLE)
char cmd[sizeof("AT+CWHOSTNAME=\"\"") + NET_HOSTNAME_MAX_LEN];
snprintk(cmd, sizeof(cmd), "AT+CWHOSTNAME=\"%s\"", net_hostname_get());
cmd[sizeof(cmd) - 1] = '\0';
esp_cmd_send(data, NULL, 0, cmd, ESP_CMD_TIMEOUT);
#else
ARG_UNUSED(data);
#endif
}
static inline uint8_t esp_mode_from_flags(struct esp_data *data)
{
uint8_t flags = data->flags;
uint8_t mode = 0;
if (flags & (EDF_STA_CONNECTED | EDF_STA_LOCK)) {
mode |= ESP_MODE_STA;
}
if (flags & EDF_AP_ENABLED) {
mode |= ESP_MODE_AP;
}
/*
* ESP AT 1.7 does not allow to disable radio, so enter STA mode
* instead.
*/
if (IS_ENABLED(CONFIG_WIFI_ESP_AT_VERSION_1_7) &&
mode == ESP_MODE_NONE) {
mode = ESP_MODE_STA;
}
return mode;
}
static int esp_mode_switch(struct esp_data *data, uint8_t mode)
{
char cmd[] = "AT+"_CWMODE"=X";
int err;
cmd[sizeof(cmd) - 2] = ('0' + mode);
LOG_DBG("Switch to mode %hhu", mode);
err = esp_cmd_send(data, NULL, 0, cmd, ESP_CMD_TIMEOUT);
if (err) {
LOG_WRN("Failed to switch to mode %d: %d", (int) mode, err);
}
return err;
}
static int esp_mode_switch_if_needed(struct esp_data *data)
{
uint8_t new_mode = esp_mode_from_flags(data);
uint8_t old_mode = data->mode;
int err;
if (old_mode == new_mode) {
return 0;
}
data->mode = new_mode;
err = esp_mode_switch(data, new_mode);
if (err) {
return err;
}
if (!(old_mode & ESP_MODE_STA) && (new_mode & ESP_MODE_STA)) {
/*
* Hostname change is applied only when STA is enabled.
*/
esp_configure_hostname(data);
}
return 0;
}
static void esp_mode_switch_submit_if_needed(struct esp_data *data)
{
if (data->mode != esp_mode_from_flags(data)) {
k_work_submit_to_queue(&data->workq, &data->mode_switch_work);
}
}
static void esp_mode_switch_work(struct k_work *work)
{
struct esp_data *data =
CONTAINER_OF(work, struct esp_data, mode_switch_work);
(void)esp_mode_switch_if_needed(data);
}
static inline int esp_mode_flags_set(struct esp_data *data, uint8_t flags)
{
esp_flags_set(data, flags);
return esp_mode_switch_if_needed(data);
}
static inline int esp_mode_flags_clear(struct esp_data *data, uint8_t flags)
{
esp_flags_clear(data, flags);
return esp_mode_switch_if_needed(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 void esp_dns_work(struct k_work *work)
{
struct esp_data *data = CONTAINER_OF(work, struct esp_data, dns_work);
struct dns_resolve_context *dnsctx;
struct sockaddr_in *addrs = data->dns_addresses;
const struct sockaddr *dns_servers[ESP_MAX_DNS + 1] = {};
size_t i;
int err;
for (i = 0; i < ESP_MAX_DNS; i++) {
if (!addrs[i].sin_addr.s_addr) {
break;
}
dns_servers[i] = (struct sockaddr *) &addrs[i];
}
dnsctx = dns_resolve_get_default();
err = dns_resolve_reconfigure(dnsctx, NULL, dns_servers);
if (err) {
LOG_ERR("Could not set DNS servers: %d", err);
}
LOG_DBG("DNS resolver reconfigured");
}
/* +CIPDNS:enable[,"DNS IP1"[,"DNS IP2"[,"DNS IP3"]]] */
MODEM_CMD_DEFINE(on_cmd_cipdns)
{
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
cmd_handler_data);
struct sockaddr_in *addrs = dev->dns_addresses;
char **servers = (char **)argv + 1;
size_t num_servers = argc - 1;
size_t valid_servers = 0;
size_t i;
int err;
for (i = 0; i < ESP_MAX_DNS; i++) {
if (i >= num_servers) {
addrs[i].sin_addr.s_addr = 0;
break;
}
servers[i] = str_unquote(servers[i]);
LOG_DBG("DNS[%zu]: %s", i, log_strdup(servers[i]));
err = net_addr_pton(AF_INET, servers[i], &addrs[i].sin_addr);
if (err) {
LOG_ERR("Invalid DNS address: %s",
log_strdup(servers[i]));
addrs[i].sin_addr.s_addr = 0;
break;
}
addrs[i].sin_family = AF_INET;
addrs[i].sin_port = htons(53);
valid_servers++;
}
if (valid_servers) {
k_work_submit(&dev->dns_work);
}
return 0;
}
static const 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_flags_are_set(dev, EDF_STA_CONNECTED)) {
return 0;
}
esp_flags_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_flags_are_set(dev, EDF_STA_CONNECTED)) {
return 0;
}
esp_flags_clear(dev, EDF_STA_CONNECTED);
esp_mode_switch_submit_if_needed(dev);
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;
static const struct modem_cmd cmds[] = {
MODEM_CMD("+"_CIPSTA":", on_cmd_cipsta, 2U, ":"),
};
static const struct modem_cmd dns_cmds[] = {
MODEM_CMD_ARGS_MAX("+CIPDNS:", on_cmd_cipdns, 1U, 3U, ","),
};
ret = esp_cmd_send(dev, cmds, ARRAY_SIZE(cmds), "AT+"_CIPSTA"?",
ESP_CMD_TIMEOUT);
if (ret < 0) {
LOG_WRN("Failed to query IP settings: ret %d", ret);
k_work_reschedule_for_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_AT_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
if (IS_ENABLED(CONFIG_WIFI_ESP_AT_DNS_USE)) {
ret = esp_cmd_send(dev, dns_cmds, ARRAY_SIZE(dns_cmds),
"AT+CIPDNS?", ESP_CMD_TIMEOUT);
if (ret) {
LOG_WRN("DNS fetch failed: %d", ret);
}
}
}
MODEM_CMD_DEFINE(on_cmd_got_ip)
{
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
cmd_handler_data);
k_work_reschedule_for_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_ref_from_link_id(dev, link_id);
if (!sock) {
LOG_ERR("No socket for link %d", link_id);
return 0;
}
esp_socket_unref(sock);
return 0;
}
MODEM_CMD_DEFINE(on_cmd_closed)
{
struct esp_socket *sock;
struct esp_data *dev;
uint8_t link_id;
atomic_val_t old_flags;
link_id = data->match_buf[0] - '0';
dev = CONTAINER_OF(data, struct esp_data, cmd_handler_data);
sock = esp_socket_ref_from_link_id(dev, link_id);
if (!sock) {
LOG_ERR("No socket for link %d", link_id);
return 0;
}
old_flags = esp_socket_flags_clear_and_set(sock,
ESP_SOCK_CONNECTED, ESP_SOCK_CLOSE_PENDING);
if (!(old_flags & ESP_SOCK_CONNECTED)) {
LOG_DBG("Link %d already closed", link_id);
goto socket_unref;
}
if (!(old_flags & ESP_SOCK_CLOSE_PENDING)) {
esp_socket_work_submit(sock, &sock->close_work);
}
socket_unref:
esp_socket_unref(sock);
return 0;
}
/*
* Passive mode: "+IPD,<id>,<len>\r\n"
* Other: "+IPD,<id>,<len>:<data>"
*/
#define MIN_IPD_LEN (sizeof("+IPD,I,0E") - 1)
#define MAX_IPD_LEN (sizeof("+IPD,I,4294967295E") - 1)
static int cmd_ipd_parse_hdr(struct net_buf *buf, uint16_t len,
uint8_t *link_id,
int *data_offset, int *data_len,
char *end)
{
char *endptr, ipd_buf[MAX_IPD_LEN + 1];
size_t frags_len;
size_t match_len;
frags_len = net_buf_frags_len(buf);
/* Wait until minimum cmd length is available */
if (frags_len < MIN_IPD_LEN) {
return -EAGAIN;
}
match_len = net_buf_linearize(ipd_buf, MAX_IPD_LEN,
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));
return -EBADMSG;
}
*link_id = ipd_buf[len + 1] - '0';
*data_len = strtol(&ipd_buf[len + 3], &endptr, 10);
if (endptr == &ipd_buf[len + 3] ||
(*endptr == 0 && match_len >= MAX_IPD_LEN)) {
LOG_ERR("Invalid IPD len: %s", log_strdup(ipd_buf));
return -EBADMSG;
} else if (*endptr == 0) {
return -EAGAIN;
}
*end = *endptr;
*data_offset = (endptr - ipd_buf) + 1;
return 0;
}
static int cmd_ipd_check_hdr_end(struct esp_socket *sock, char actual)
{
char expected;
/* When using passive mode, the +IPD command ends with \r\n */
if (ESP_PROTO_PASSIVE(esp_socket_ip_proto(sock))) {
expected = '\r';
} else {
expected = ':';
}
if (expected != actual) {
LOG_ERR("Invalid cmd end 0x%02x, expected 0x%02x", actual,
expected);
return -EBADMSG;
}
return 0;
}
MODEM_CMD_DIRECT_DEFINE(on_cmd_ipd)
{
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
cmd_handler_data);
struct esp_socket *sock;
int data_offset, data_len;
uint8_t link_id;
char cmd_end;
int err;
int ret;
err = cmd_ipd_parse_hdr(data->rx_buf, len, &link_id, &data_offset,
&data_len, &cmd_end);
if (err) {
if (err == -EAGAIN) {
return -EAGAIN;
}
return len;
}
sock = esp_socket_ref_from_link_id(dev, link_id);
if (!sock) {
LOG_ERR("No socket for link %d", link_id);
return len;
}
err = cmd_ipd_check_hdr_end(sock, cmd_end);
if (err) {
ret = len;
goto socket_unref;
}
/*
* 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(esp_socket_ip_proto(sock))) {
esp_socket_work_submit(sock, &sock->recvdata_work);
ret = data_offset;
goto socket_unref;
}
/* Do we have the whole message? */
if (data_offset + data_len > net_buf_frags_len(data->rx_buf)) {
ret = -EAGAIN;
goto socket_unref;
}
esp_socket_rx(sock, data->rx_buf, data_offset, data_len);
ret = data_offset + data_len;
socket_unref:
esp_socket_unref(sock);
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_flags_are_set(dev, EDF_STA_CONNECTING)) {
wifi_mgmt_raise_connect_result_event(dev->net_iface, -1);
} else if (esp_flags_are_set(dev, EDF_STA_CONNECTED)) {
wifi_mgmt_raise_disconnect_result_event(dev->net_iface, 0);
}
dev->flags = 0;
dev->mode = 0;
net_if_ipv4_addr_rm(dev->net_iface, &dev->ip);
k_work_submit_to_queue(&dev->workq, &dev->init_work);
return 0;
}
static const 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;
static const struct modem_cmd cmds[] = {
MODEM_CMD("+CWLAP:", on_cmd_cwlap, 4U, ","),
};
dev = CONTAINER_OF(work, struct esp_data, scan_work);
ret = esp_mode_flags_set(dev, EDF_STA_LOCK);
if (ret < 0) {
goto out;
}
ret = esp_cmd_send(dev, cmds, ARRAY_SIZE(cmds), "AT+CWLAP",
ESP_SCAN_TIMEOUT);
esp_mode_flags_clear(dev, EDF_STA_LOCK);
if (ret < 0) {
LOG_ERR("Failed to scan: ret %d", ret);
}
out:
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;
static const struct modem_cmd cmds[] = {
MODEM_CMD("FAIL", on_cmd_fail, 0U, ""),
};
dev = CONTAINER_OF(work, struct esp_data, connect_work);
ret = esp_mode_flags_set(dev, EDF_STA_LOCK);
if (ret < 0) {
goto out;
}
ret = esp_cmd_send(dev, cmds, ARRAY_SIZE(cmds), dev->conn_cmd,
ESP_CONNECT_TIMEOUT);
memset(dev->conn_cmd, 0, sizeof(dev->conn_cmd));
if (ret < 0) {
if (esp_flags_are_set(dev, EDF_STA_CONNECTED)) {
esp_flags_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_flags_are_set(dev, EDF_STA_CONNECTED)) {
esp_flags_set(dev, EDF_STA_CONNECTED);
wifi_mgmt_raise_connect_result_event(dev->net_iface, 0);
}
esp_mode_flags_clear(dev, EDF_STA_LOCK);
out:
esp_flags_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_flags_are_set(data, EDF_STA_CONNECTED | EDF_STA_CONNECTING)) {
return -EALREADY;
}
esp_flags_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 = esp_cmd_send(data, NULL, 0, "AT+CWQAP", 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 = esp_mode_flags_set(data, EDF_AP_ENABLED);
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 = esp_cmd_send(data, NULL, 0, cmd, ESP_CMD_TIMEOUT);
return ret;
}
static int esp_mgmt_ap_disable(const struct device *dev)
{
struct esp_data *data = dev->data;
return esp_mode_flags_clear(data, EDF_AP_ENABLED);
}
static void esp_init_work(struct k_work *work)
{
struct esp_data *dev;
int ret;
static const 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 const struct setup_cmd setup_cmds_target_baudrate[] = {
SETUP_CMD_NOHANDLE("AT"),
#endif
#if defined(CONFIG_WIFI_ESP_AT_VERSION_1_7)
SETUP_CMD_NOHANDLE(ESP_CMD_CWMODE(STA)),
#endif
#if defined(CONFIG_WIFI_ESP_AT_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_AT_IP_ADDRESS,
CONFIG_WIFI_ESP_AT_IP_GATEWAY,
CONFIG_WIFI_ESP_AT_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(ESP_CMD_CWMODE(STA)),
SETUP_CMD_NOHANDLE("AT+CWAUTOCONN=0"),
SETUP_CMD_NOHANDLE(ESP_CMD_CWMODE(NONE)),
#endif
#if defined(CONFIG_WIFI_ESP_AT_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);
if (IS_ENABLED(CONFIG_WIFI_ESP_AT_VERSION_1_7)) {
/* This is the mode entered in above setup commands */
dev->mode = ESP_MODE_STA;
/*
* In case of ESP 1.7 this is the first time CWMODE is entered
* STA mode, so request hostname change now.
*/
esp_configure_hostname(dev);
}
LOG_INF("ESP Wi-Fi ready");
net_if_up(dev->net_iface);
}
static void esp_reset(struct esp_data *dev)
{
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 ret;
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_AT_RESET_TIMEOUT));
if (ret == 0 || ret != -ETIMEDOUT) {
break;
}
}
if (ret < 0) {
LOG_ERR("Failed to reset device: %d", ret);
return;
}
#endif
}
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_work_init(&data->init_work, esp_init_work);
k_work_init_delayable(&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);
k_work_init(&data->mode_switch_work, esp_mode_switch_work);
if (IS_ENABLED(CONFIG_WIFI_ESP_AT_DNS_USE)) {
k_work_init(&data->dns_work, esp_dns_work);
}
esp_socket_init(data);
/* initialize the work queue */
k_work_queue_start(&data->workq, esp_workq_stack,
K_KERNEL_STACK_SIZEOF(esp_workq_stack),
K_PRIO_COOP(CONFIG_WIFI_ESP_AT_WORKQ_THREAD_PRIORITY),
NULL);
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_AT_RX_THREAD_PRIORITY), 0,
K_NO_WAIT);
k_thread_name_set(&esp_rx_thread, "esp_rx");
error:
return ret;
}
NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, esp_init, NULL,
&esp_driver_data, NULL,
CONFIG_WIFI_INIT_PRIORITY, &esp_api,
ESP_MTU);