zephyr/drivers/wifi/esp_at/esp.c

1389 lines
33 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 <zephyr/logging/log.h>
LOG_MODULE_REGISTER(wifi_esp_at, CONFIG_WIFI_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <ctype.h>
#include <errno.h>
#include <zephyr/device.h>
#include <zephyr/init.h>
#include <stdlib.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/net/dns_resolve.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/net/net_offload.h>
#include <zephyr/net/wifi_mgmt.h>
#include "esp.h"
struct esp_config {
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
const struct gpio_dt_spec power;
#endif
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
const struct gpio_dt_spec reset;
#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);
static const struct esp_config esp_driver_config = {
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
.power = GPIO_DT_SPEC_INST_GET(0, power_gpios),
#endif
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
.reset = GPIO_DT_SPEC_INST_GET(0, reset_gpios),
#endif
};
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(void *p1, void *p2, void *p3)
{
ARG_UNUSED(p2);
ARG_UNUSED(p3);
struct esp_data *data = p1;
while (true) {
/* wait for incoming data */
modem_iface_uart_rx_wait(&data->mctx.iface, K_FOREVER);
modem_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) */
/* with: CONFIG_WIFI_ESP_AT_SCAN_MAC_ADDRESS: +CWLAP:<ecn>,<ssid>,<rssi>,<mac>,<ch>*/
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);
if (IS_ENABLED(CONFIG_WIFI_ESP_AT_SCAN_MAC_ADDRESS)) {
argv[3] = str_unquote(argv[3]);
res.mac_length = WIFI_MAC_ADDR_LEN;
if (net_bytes_from_str(res.mac, sizeof(res.mac), argv[3]) < 0) {
LOG_ERR("Invalid MAC address");
res.mac_length = 0;
}
res.channel = (argc > 4) ? strtol(argv[4], NULL, 10) : -1;
} else {
res.channel = strtol(argv[3], NULL, 10);
}
if (dev->scan_cb) {
dev->scan_cb(dev->net_iface, 0, &res);
}
return 0;
}
/* +CWJAP:(ssid,bssid,channel,rssi) */
MODEM_CMD_DEFINE(on_cmd_cwjap)
{
struct esp_data *dev = CONTAINER_OF(data, struct esp_data,
cmd_handler_data);
struct wifi_iface_status *status = dev->wifi_status;
const char *ssid = str_unquote(argv[0]);
const char *bssid = str_unquote(argv[1]);
const char *channel = argv[2];
const char *rssi = argv[3];
uint8_t flags = dev->flags;
int err;
status->band = WIFI_FREQ_BAND_2_4_GHZ;
status->iface_mode = WIFI_MODE_INFRA;
if (flags & EDF_STA_CONNECTED) {
status->state = WIFI_STATE_COMPLETED;
} else if (flags & EDF_STA_CONNECTING) {
status->state = WIFI_STATE_SCANNING;
} else {
status->state = WIFI_STATE_DISCONNECTED;
}
strncpy(status->ssid, ssid, sizeof(status->ssid));
status->ssid_len = strlen(status->ssid);
err = net_bytes_from_str(status->bssid, sizeof(status->bssid), bssid);
if (err) {
LOG_WRN("Invalid MAC address");
memset(status->bssid, 0x0, sizeof(status->bssid));
}
status->channel = strtol(channel, NULL, 10);
status->rssi = strtol(rssi, NULL, 10);
return 0;
}
static void esp_dns_work(struct k_work *work)
{
#if defined(ESP_MAX_DNS)
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");
#endif
}
/* +CIPDNS:enable[,"DNS IP1"[,"DNS IP2"[,"DNS IP3"]]] */
MODEM_CMD_DEFINE(on_cmd_cipdns)
{
#if defined(ESP_MAX_DNS)
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, servers[i]);
err = net_addr_pton(AF_INET, servers[i], &addrs[i].sin_addr);
if (err) {
LOG_ERR("Invalid DNS address: %s",
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);
}
#endif
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);
net_if_dormant_off(dev->net_iface);
return 0;
}
static void esp_mgmt_disconnect_work(struct k_work *work)
{
struct esp_socket *sock;
struct esp_data *dev;
dev = CONTAINER_OF(work, struct esp_data, disconnect_work);
/* Cleanup any sockets that weren't closed */
for (int i = 0; i < ARRAY_SIZE(dev->sockets); i++) {
sock = &dev->sockets[i];
if (esp_socket_connected(sock)) {
LOG_WRN("Socket %d left open, manually closing", i);
esp_socket_close(sock);
}
}
esp_flags_clear(dev, EDF_STA_CONNECTED);
esp_mode_switch_submit_if_needed(dev);
net_if_ipv4_addr_rm(dev->net_iface, &dev->ip);
net_if_dormant_on(dev->net_iface);
wifi_mgmt_raise_disconnect_result_event(dev->net_iface, 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)) {
k_work_submit_to_queue(&dev->workq, &dev->disconnect_work);
}
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", argv[0]);
}
return 0;
}
static void esp_ip_addr_work(struct k_work *work)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
struct esp_data *dev = CONTAINER_OF(dwork, 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';
LOG_DBG("Link %d closed", link_id);
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", 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", 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_carrier_ok(dev->net_iface)) {
net_if_dormant_on(dev->net_iface);
net_if_carrier_off(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;
}
#if defined(CONFIG_WIFI_ESP_AT_FETCH_VERSION)
static int cmd_version_log(struct modem_cmd_handler_data *data,
const char *type, const char *version)
{
LOG_INF("%s: %s", type, version);
return 0;
}
MODEM_CMD_DEFINE(on_cmd_at_version)
{
return cmd_version_log(data, "AT version", argv[0]);
}
MODEM_CMD_DEFINE(on_cmd_sdk_version)
{
return cmd_version_log(data, "SDK version", argv[0]);
}
MODEM_CMD_DEFINE(on_cmd_compile_time)
{
return cmd_version_log(data, "compile time", argv[0]);
}
MODEM_CMD_DEFINE(on_cmd_bin_version)
{
return cmd_version_log(data, "Bin version", argv[0]);
}
#endif /* CONFIG_WIFI_ESP_AT_FETCH_VERSION */
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, ""),
#if defined(CONFIG_WIFI_ESP_AT_FETCH_VERSION)
MODEM_CMD("AT version:", on_cmd_at_version, 1U, ""),
MODEM_CMD("SDK version:", on_cmd_sdk_version, 1U, ""),
MODEM_CMD("Compile time", on_cmd_compile_time, 1U, ""),
MODEM_CMD("Bin version:", on_cmd_bin_version, 1U, ""),
#endif
MODEM_CMD_DIRECT("+IPD", on_cmd_ipd),
};
static void esp_mgmt_iface_status_work(struct k_work *work)
{
struct esp_data *data = CONTAINER_OF(work, struct esp_data, iface_status_work);
struct wifi_iface_status *status = data->wifi_status;
int ret;
static const struct modem_cmd cmds[] = {
MODEM_CMD("+CWJAP:", on_cmd_cwjap, 4U, ","),
};
ret = esp_cmd_send(data, cmds, ARRAY_SIZE(cmds), "AT+CWJAP?",
ESP_IFACE_STATUS_TIMEOUT);
if (ret < 0) {
LOG_WRN("Failed to request STA status: ret %d", ret);
status->state = WIFI_STATE_UNKNOWN;
}
k_sem_give(&data->wifi_status_sem);
}
static int esp_mgmt_iface_status(const struct device *dev,
struct wifi_iface_status *status)
{
struct esp_data *data = dev->data;
memset(status, 0x0, sizeof(*status));
status->state = WIFI_STATE_UNKNOWN;
status->band = WIFI_FREQ_BAND_UNKNOWN;
status->iface_mode = WIFI_MODE_UNKNOWN;
status->link_mode = WIFI_LINK_MODE_UNKNOWN;
status->security = WIFI_SECURITY_TYPE_UNKNOWN;
status->mfp = WIFI_MFP_UNKNOWN;
if (!net_if_is_carrier_ok(data->net_iface)) {
status->state = WIFI_STATE_INTERFACE_DISABLED;
return 0;
}
data->wifi_status = status;
k_sem_init(&data->wifi_status_sem, 0, 1);
k_work_submit_to_queue(&data->workq, &data->iface_status_work);
k_sem_take(&data->wifi_status_sem, K_FOREVER);
return 0;
}
static void esp_mgmt_scan_work(struct k_work *work)
{
struct esp_data *dev;
int ret;
static const struct modem_cmd cmds[] = {
#if defined(CONFIG_WIFI_ESP_AT_SCAN_MAC_ADDRESS)
MODEM_CMD("+CWLAP:", on_cmd_cwlap, 5U, ","),
#else
MODEM_CMD("+CWLAP:", on_cmd_cwlap, 4U, ","),
#endif
};
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),
ESP_CMD_CWLAP,
ESP_SCAN_TIMEOUT);
esp_mode_flags_clear(dev, EDF_STA_LOCK);
LOG_DBG("ESP Wi-Fi scan: cmd = %s", ESP_CMD_CWLAP);
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,
struct wifi_scan_params *params,
scan_result_cb_t cb)
{
struct esp_data *data = dev->data;
ARG_UNUSED(params);
if (data->scan_cb != NULL) {
return -EINPROGRESS;
}
if (!net_if_is_carrier_ok(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) {
net_if_dormant_on(dev->net_iface);
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);
net_if_dormant_off(dev->net_iface);
}
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_carrier_ok(data->net_iface) ||
!net_if_is_admin_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;
len += snprintk(&data->conn_cmd[len],
sizeof(data->conn_cmd) - len, "\",\"");
if (params->security == WIFI_SECURITY_TYPE_PSK) {
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_FETCH_VERSION)
SETUP_CMD_NOHANDLE("AT+GMR"),
#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"),
SETUP_CMD_NOHANDLE(
ESP_CMD_CWLAPOPT(ESP_CMD_CWLAPOPT_ORDERED, ESP_CMD_CWLAPOPT_MASK)),
#if !defined(CONFIG_WIFI_ESP_AT_VERSION_1_7)
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_DT_GET(DT_INST_BUS(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");
/* L1 network layer (physical layer) is up */
net_if_carrier_on(dev->net_iface);
k_sem_give(&dev->sem_if_up);
}
static int esp_reset(const struct device *dev)
{
struct esp_data *data = dev->data;
int ret = -EAGAIN;
if (net_if_is_carrier_ok(data->net_iface)) {
net_if_carrier_off(data->net_iface);
}
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
const struct esp_config *config = dev->config;
gpio_pin_set_dt(&config->power, 0);
k_sleep(K_MSEC(100));
gpio_pin_set_dt(&config->power, 1);
#elif DT_INST_NODE_HAS_PROP(0, reset_gpios)
const struct esp_config *config = dev->config;
gpio_pin_set_dt(&config->reset, 1);
k_sleep(K_MSEC(100));
gpio_pin_set_dt(&config->reset, 0);
#else
#if DT_INST_NODE_HAS_PROP(0, external_reset)
/* Wait to see if the interface comes up by itself */
ret = k_sem_take(&data->sem_if_ready, K_MSEC(CONFIG_WIFI_ESP_AT_RESET_TIMEOUT));
#endif
int retries = 3;
/* Don't need to run this if the interface came up by itself */
while ((ret != 0) && retries--) {
ret = modem_cmd_send(&data->mctx.iface, &data->mctx.cmd_handler,
NULL, 0, "AT+RST", &data->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 -EAGAIN;
}
#endif
LOG_INF("Waiting for interface to come up");
ret = k_sem_take(&data->sem_if_up, ESP_INIT_TIMEOUT);
if (ret == -EAGAIN) {
LOG_ERR("Timeout waiting for interface");
}
return ret;
}
static void esp_iface_init(struct net_if *iface)
{
esp_offload_init(iface);
/* Not currently connected to a network */
net_if_dormant_on(iface);
}
static enum offloaded_net_if_types esp_offload_get_type(void)
{
return L2_OFFLOADED_NET_IF_TYPE_WIFI;
}
static const struct wifi_mgmt_ops esp_mgmt_ops = {
.scan = esp_mgmt_scan,
.connect = esp_mgmt_connect,
.disconnect = esp_mgmt_disconnect,
.ap_enable = esp_mgmt_ap_enable,
.ap_disable = esp_mgmt_ap_disable,
.iface_status = esp_mgmt_iface_status,
};
static const struct net_wifi_mgmt_offload esp_api = {
.wifi_iface.iface_api.init = esp_iface_init,
.wifi_iface.get_type = esp_offload_get_type,
.wifi_mgmt_api = &esp_mgmt_ops,
};
static int esp_init(const struct device *dev);
/* The network device must be instantiated above the init function in order
* for the struct net_if that the macro declares to be visible inside the
* function. An `extern` declaration does not work as the struct is static.
*/
NET_DEVICE_DT_INST_OFFLOAD_DEFINE(0, esp_init, NULL,
&esp_driver_data, &esp_driver_config,
CONFIG_WIFI_INIT_PRIORITY, &esp_api,
ESP_MTU);
static int esp_init(const struct device *dev)
{
#if DT_INST_NODE_HAS_PROP(0, power_gpios) || DT_INST_NODE_HAS_PROP(0, reset_gpios)
const struct esp_config *config = dev->config;
#endif
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_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->disconnect_work, esp_mgmt_disconnect_work);
k_work_init(&data->iface_status_work, esp_mgmt_iface_status_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 */
const struct modem_cmd_handler_config cmd_handler_config = {
.match_buf = &data->cmd_match_buf[0],
.match_buf_len = sizeof(data->cmd_match_buf),
.buf_pool = &mdm_recv_pool,
.alloc_timeout = K_NO_WAIT,
.eol = "\r\n",
.user_data = NULL,
.response_cmds = response_cmds,
.response_cmds_len = ARRAY_SIZE(response_cmds),
.unsol_cmds = unsol_cmds,
.unsol_cmds_len = ARRAY_SIZE(unsol_cmds),
};
ret = modem_cmd_handler_init(&data->mctx.cmd_handler, &data->cmd_handler_data,
&cmd_handler_config);
if (ret < 0) {
goto error;
}
/* modem interface */
const struct modem_iface_uart_config uart_config = {
.rx_rb_buf = &data->iface_rb_buf[0],
.rx_rb_buf_len = sizeof(data->iface_rb_buf),
.dev = DEVICE_DT_GET(DT_INST_BUS(0)),
.hw_flow_control = DT_PROP(ESP_BUS, hw_flow_control),
};
ret = modem_iface_uart_init(&data->mctx.iface, &data->iface_data, &uart_config);
if (ret < 0) {
goto error;
}
/* pin setup */
#if DT_INST_NODE_HAS_PROP(0, power_gpios)
ret = gpio_pin_configure_dt(&config->power, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure %s pin", "power");
goto error;
}
#endif
#if DT_INST_NODE_HAS_PROP(0, reset_gpios)
ret = gpio_pin_configure_dt(&config->reset, GPIO_OUTPUT_INACTIVE);
if (ret < 0) {
LOG_ERR("Failed to configure %s pin", "reset");
goto error;
}
#endif
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),
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");
/* Retrieve associated network interface so asynchronous messages can be processed early */
data->net_iface = NET_IF_GET(Z_DEVICE_DT_DEV_ID(DT_DRV_INST(0)), 0);
/* Reset the modem */
ret = esp_reset(dev);
error:
return ret;
}