1389 lines
33 KiB
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;
|
|
}
|