0
0
Fork 0

Compare commits

..

No commits in common. "67b97b33960d1c0b1b1e8111b797fd71fa5c119b" and "4600f384dd4142ad8320e9e672748fae5d516aa1" have entirely different histories.

9 changed files with 97 additions and 1234 deletions

107
README.md
View File

@ -20,7 +20,7 @@ For Keil users, we now also support [elaphureLink](https://github.com/windowsair
- [x] ESP32
- [x] ESP32C3
- [x] ESP32S3
2. Debug Communication Mode
- [x] SWD
- [x] JTAG
@ -35,8 +35,6 @@ For Keil users, we now also support [elaphureLink](https://github.com/windowsair
5. More..
- [x] SWD protocol based on SPI acceleration (Up to 40MHz)
- [x] Support for [elaphureLink](https://github.com/windowsair/elaphureLink), fast Keil debug without drivers
- [x] Support for [elaphure-dap.js](https://github.com/windowsair/elaphure-dap.js), online ARM Cortex-M firmware flash
- [x] Support for OpenOCD/pyOCD
- [x] ...
@ -45,53 +43,14 @@ For Keil users, we now also support [elaphureLink](https://github.com/windowsair
### WIFI
The default connected WIFI SSID is `无线DAP` , password `12345678`
Support for specifying multiple possible WAP. It can be added here: [wifi_configuration.h](main/wifi_configuration.h)
You can also specify your IP in the above file (We recommend using the static address binding feature of the router).
![WIFI](https://user-images.githubusercontent.com/17078589/118365659-517e7880-b5d0-11eb-9a5b-afe43348c2ba.png)
There is built-in ipv4 only mDNS server. You can access the device using `dap.local`.
![mDNS](https://user-images.githubusercontent.com/17078589/149659052-7b29533f-9660-4811-8125-f8f50490d762.png)
### Debugger
<details>
<summary>ESP32</summary>
| SWD | |
|----------------|--------|
| SWCLK | GPIO14 |
| SWDIO | GPIO13 |
| TVCC | 3V3 |
| GND | GND |
--------------
| JTAG | |
|--------------------|---------|
| TCK | GPIO14 |
| TMS | GPIO13 |
| TDI | GPIO18 |
| TDO | GPIO19 |
| nTRST \(optional\) | GPIO25 |
| nRESET | GPIO26 |
| TVCC | 3V3 |
| GND | GND |
</details>
<details>
<summary>ESP32C3</summary>
@ -121,6 +80,32 @@ There is built-in ipv4 only mDNS server. You can access the device using `dap.lo
</details>
<details>
<summary>ESP32</summary>
| SWD | |
|----------------|--------|
| SWCLK | GPIO14 |
| SWDIO | GPIO13 |
| TVCC | 3V3 |
| GND | GND |
--------------
| JTAG | |
|--------------------|---------|
| TCK | GPIO14 |
| TMS | GPIO13 |
| TDI | GPIO18 |
| TDO | GPIO19 |
| nTRST \(optional\) | GPIO25 |
| nRESET | GPIO26 |
| TVCC | 3V3 |
| GND | GND |
</details>
<details>
<summary>ESP32S3</summary>
@ -149,6 +134,7 @@ There is built-in ipv4 only mDNS server. You can access the device using `dap.lo
</details>
----
@ -261,12 +247,18 @@ When you select max clock, we will take the following actions:
This project was originally designed to run on Keil, but now you can also perform firmware flash on OpenOCD.
Note that if you want to use a 40MHz SPI acceleration, you need to specify the speed after the target device is connected, otherwise it will fail with the beginning.
```bash
# Run before approaching the flash command
> adapter speed 10000
> halt
> flash write_image [erase] [unlock] filename [offset] [type]
```
> pyOCD is now supported.
> Keil's timing handling is somewhat different from OpenOCD's. For example, OpenOCD lacks the SWD line reset sequence before reading the `IDCODE` registers.
### Uart TCP Bridge
@ -275,6 +267,8 @@ TCP server on PORT 1234.
UART default baud: 74880
This feature provides a bridge between TCP and Uart:
```
Send data -> TCP -> Uart TX -> external devices
@ -319,30 +313,6 @@ For example, sending the ASCII text `115200` will switch the baud rate to 115200
</details>
2020.12.1
TCP transmission speed needs to be further improved.
2020.11.11
Winusb is now available, but it is very slow.
2020.2.4
Due to the limitation of USB-HID (I'm not sure if this is a problem with USBIP or Windows), now each URB packet can only reach 255 bytes (About 1MBps bandwidth), which has not reached the upper limit of ESP8266 transmission bandwidth.
I now have an idea to construct a Man-in-the-middle between the two to forward traffic, thereby increasing the bandwidth of each transmission.
2020.1.31
At present, the adaptation to WCID, WinUSB, etc. has all been completed. However, when transmitting data on the endpoint, we received an error message from USBIP. This is most likely a problem with the USBIP project itself.
Due to the completeness of the USBIP protocol document, we have not yet understood its role in the Bulk transmission process, which may also lead to errors in subsequent processes.
We will continue to try to make it work on USB HID. Once the USBIP problem is solved, we will immediately transfer it to work on WinUSB
------
## Credit
@ -364,5 +334,4 @@ Credits to the following project, people and organizations:
## License
[MIT LICENSE](LICENSE)
[Apache2.0 LICENSE](LICENSE)

View File

@ -29,16 +29,14 @@
5. 其它
- [x] 通过SPI接口加速的SWD协议最高可达40MHz
- [x] 支持 [elaphureLink](https://github.com/windowsair/elaphureLink)无需驱动的快速Keil 调试
- [x] 支持 [elaphure-dap.js](https://github.com/windowsair/elaphure-dap.js),网页端的 ARM Cortex-M 设备固件烧录调试
- [x] 支持 OpenOCD/pyOCD
- [x] 支持[elaphureLink](https://github.com/windowsair/elaphureLink)无需驱动的快速Keil调试
- [x] ...
## 连接你的开发板
### WIFI连接
固件默认的WIFI SSID是`无线DAP`或者`OTA`,密码是`12345678`。
固件中已经内置了一个mDNS服务。你可以通过`dap.local`的地址访问到设备。
@ -47,32 +45,6 @@
### 调试接口连接
<details>
<summary>ESP32</summary>
| SWD | |
|----------------|--------|
| SWCLK | GPIO14 |
| SWDIO | GPIO13 |
| TVCC | 3V3 |
| GND | GND |
--------------
| JTAG | |
|--------------------|---------|
| TCK | GPIO14 |
| TMS | GPIO13 |
| TDI | GPIO18 |
| TDO | GPIO19 |
| nTRST \(optional\) | GPIO25 |
| nRESET | GPIO26 |
| TVCC | 3V3 |
| GND | GND |
</details>
<details>
<summary>ESP32C3</summary>
@ -97,6 +69,31 @@
| GND | GND |
</details>
<details>
<summary>ESP32</summary>
| SWD | |
|----------------|--------|
| SWCLK | GPIO14 |
| SWDIO | GPIO13 |
| TVCC | 3V3 |
| GND | GND |
--------------
| JTAG | |
|--------------------|---------|
| TCK | GPIO14 |
| TMS | GPIO13 |
| TDI | GPIO18 |
| TDO | GPIO19 |
| nTRST \(optional\) | GPIO25 |
| nRESET | GPIO26 |
| TVCC | 3V3 |
| GND | GND |
</details>
<details>
@ -161,6 +158,9 @@ idf.py -p /dev/ttyS5 flash
> 位于项目根目录的`idf.py`脚本仅适用于较老的ESP8266设备请不要在ESP32设备上使用。
> 我们还提供了预编译固件用于快速评估。详见 [Releases](https://github.com/windowsair/wireless-esp8266-dap/releases)
</details>
## 使用
@ -228,13 +228,17 @@ idf.py -p /dev/ttyS5 flash
### 对于OpenOCD用户
这个项目最初是为在Keil上运行而设计的但现在你也可以在OpenOCD上通过它来烧录程序。
注意如果你想使用40MHz的SPI加速器你需要在连接目标设备后指定速度否则会在开始时失败。
```bash
# 在使用flash指令前需要先运行
> adapter speed 10000
> halt
> flash write_image [erase] [unlock] filename [offset] [type]
```
> 现已支持 pyOCD
> Keil的操作时序与OpenOCD的有些不同。例如OpenOCD在读取 "IDCODE "寄存器之前缺少SWD线复位序列。
### TCP转发的串口
@ -274,4 +278,4 @@ TCP端口1234
## 许可证
[MIT 许可证](LICENSE)
[Apache 2.0 许可证](LICENSE)

View File

@ -3,5 +3,5 @@ file(GLOB SOURCES *.c)
idf_component_register(
SRCS ${SOURCES}
INCLUDE_DIRS "."
PRIV_REQUIRES DAP USBIP esp_ringbuf mbedtls
PRIV_REQUIRES DAP USBIP esp_ringbuf
)

View File

@ -1,79 +0,0 @@
/**
* Copyright (c) 2021 windowsair <msdn_02 at sina.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef _CORSACOTA_H_
#define _CORSACOTA_H_
#ifdef __cplusplus
extern "C" {
#endif
typedef signed int co_err_t;
#define CO_OK 0
#define CO_FAIL -1
#define CO_ERROR_IO_PENDING -2
#define CO_ERROR_NO_MEM 0x101
#define CO_ERROR_INVALID_ARG 0x102
#define CO_ERROR_INVALID_SIZE 0x104
#define CO_ERROR_INVALID_OTA_PTN -3
#define CO_RES_SUCCESS 0
#define CO_RES_SYSTEM_ERROR 1
#define CO_RES_INVALID_ARG 2
#define CO_RES_INVALID_SIZE 3
#define CO_RES_INVALID_STATUS 4
/**
* @brief corsacOTA instance handle. Only one instance is allowed.
*
*/
typedef void *co_handle_t;
typedef struct co_config {
char *thread_name; // corsacOTA thread name
int stack_size; // corsacOTA thread stack size
int thread_prio; // corsacOTA thread priority
int listen_port; // corsacOTA server listen port
int max_listen_num; // Maximum number of connections. In fact, after the handshake is complete, there is only one connection to provide services.
int wait_timeout_sec; // Timeout (in seconds)
int wait_timeout_usec; // Timeout (in microseconds)
} co_config_t;
/**
* @brief Start the corsacOTA server
*
* @param config Configuration for new instance of the server
* @return
* - ESP_OK : Server Init successfully
* - ESP_ERR_INVALID_ARG : Null argument
* - ESP_ERR_NO_MEM : Failed to allocate memory for instance
*/
int corsacOTA_init(co_handle_t *handle, co_config_t *config);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -28,7 +28,7 @@ int kSock = -1;
void tcp_server_task(void *pvParameters)
{
uint8_t tcp_rx_buffer[1500] = {0};
uint8_t tcp_rx_buffer[1500];
char addr_str[128];
enum usbip_server_state_t usbip_state = WAIT_DEVLIST;
uint8_t *data;
@ -41,31 +41,31 @@ void tcp_server_task(void *pvParameters)
while (1)
{
#ifdef CONFIG_EXAMPLE_IPV4
struct sockaddr_in destAddr;
destAddr.sin_addr.s_addr = htonl(INADDR_ANY);
destAddr.sin_family = AF_INET;
destAddr.sin_port = htons(DAP_PROXY_PORT);
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;
inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1);
#else // IPV6
struct sockaddr_in6 destAddr;
#ifdef CONFIG_EXAMPLE_IPV6
struct sockaddr_in6 destAddr;
bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un));
destAddr.sin6_family = AF_INET6;
destAddr.sin6_port = htons(DAP_PROXY_PORT);
addr_family = AF_INET6;
ip_protocol = IPPROTO_IPV6;
inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1);
#else // IPV6
struct sockaddr_in destAddr;
destAddr.sin_addr.s_addr = htonl(INADDR_ANY);
destAddr.sin_family = AF_INET;
destAddr.sin_port = htons(DAP_PROXY_PORT);
addr_family = AF_INET;
ip_protocol = IPPROTO_IP;
inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1);
#endif
int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
if (listen_sock < 0)
{
printf("Unable to create socket: errno %d\r\n", errno);
printf("Unable to create socket: errno %d\r\n", errno);
break;
}
printf("Socket created\r\n");
printf("Socket created\r\n");
setsockopt(listen_sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
setsockopt(listen_sock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
@ -73,18 +73,18 @@ void tcp_server_task(void *pvParameters)
int err = bind(listen_sock, (struct sockaddr *)&destAddr, sizeof(destAddr));
if (err != 0)
{
printf("Socket unable to bind: errno %d\r\n", errno);
printf("Socket unable to bind: errno %d\r\n", errno);
break;
}
printf("Socket binded\r\n");
printf("Socket binded\r\n");
err = listen(listen_sock, 1);
if (err != 0)
{
printf("Error occured during listen: errno %d\r\n", errno);
printf("Error occured during listen: errno %d\r\n", errno);
break;
}
printf("Socket listening\r\n");
printf("Socket listening\r\n");
#ifdef CONFIG_EXAMPLE_IPV6
struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6
@ -97,12 +97,12 @@ void tcp_server_task(void *pvParameters)
kSock = accept(listen_sock, (struct sockaddr *)&sourceAddr, &addrLen);
if (kSock < 0)
{
printf("Unable to accept connection: errno %d\r\n", errno);
printf("Unable to accept connection: errno %d\r\n", errno);
break;
}
setsockopt(kSock, SOL_SOCKET, SO_KEEPALIVE, (void *)&on, sizeof(on));
setsockopt(kSock, IPPROTO_TCP, TCP_NODELAY, (void *)&on, sizeof(on));
printf("Socket accepted\r\n");
printf("Socket accepted\r\n");
// Read header
sz = 4;
@ -127,10 +127,6 @@ void tcp_server_task(void *pvParameters)
else
usbip_state = WAIT_IMPORT;
usbip_worker(tcp_rx_buffer, sizeof(tcp_rx_buffer), &usbip_state);
} else if (header == 0x47455420) { // string "GET "
#ifdef CONFIG_USE_WEBSOCKET_DAP
websocket_worker(kSock, tcp_rx_buffer, sizeof(tcp_rx_buffer));
#endif
} else {
printf("Unknown protocol\n");
}
@ -138,7 +134,7 @@ void tcp_server_task(void *pvParameters)
cleanup:
if (kSock != -1)
{
printf("Shutting down socket and restarting...\r\n");
printf("Shutting down socket and restarting...\r\n");
//shutdown(kSock, 0);
close(kSock);
@ -147,7 +143,7 @@ cleanup:
kRestartDAPHandle = RESET_HANDLE;
if (kDAPTaskHandle)
xTaskNotifyGive(kDAPTaskHandle);
xTaskNotifyGive(kDAPTaskHandle);
//shutdown(listen_sock, 0);
//close(listen_sock);

View File

@ -1,946 +0,0 @@
/**
* Copyright (c) 2021 windowsair <msdn_02 at sina.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#include <stdint.h>
#include <string.h>
#include <sys/param.h>
// share header file
#include "corsacOTA.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "mbedtls/base64.h"
#include "mbedtls/sha1.h"
#include "esp_system.h"
#include "lwip/err.h"
#include "lwip/sockets.h"
#include "lwip/sys.h"
#include <lwip/netdb.h>
#include "sdkconfig.h"
static const char *CO_TAG = "corsacOTA";
#define CONFIG_CO_SOCKET_BUFFER_SIZE 1500
#define CONFIG_CO_WS_TEXT_BUFFER_SIZE 100
#define LOG_FMT(x) "%s: " x, __func__
#define min(a, b) ((a) < (b) ? (a) : (b))
#define CO_NO_RETURN __attribute__((noreturn))
#define CO_INLINE __attribute__((always_inline))
#define CO_TEST_MODE 0
#if (CO_TEST_MODE == 1)
#warning corsacOTA test mode is in use
#endif
extern void free_dap_ringbuf();
extern uint32_t DAP_ExecuteCommand(const uint8_t *request, uint8_t *response);
static void co_websocket_process_dap(uint8_t *data, size_t len);
uint8_t* ws_process_buffer = NULL;
/**
* @brief corsacOTA websocket control block
*
*/
typedef struct co_websocket_cb {
uint8_t FIN;
uint8_t OPCODE;
uint8_t MASK;
size_t payload_len; // it is used not only for the 7-bit payload len,
// but also for the total length of the payload after the extended payload length is included.
size_t payload_read_len; // the number of payload bytes already read
union {
uint32_t val;
uint8_t data[4];
} mask;
bool skip_frame; // skip too long text frames
} co_websocket_cb_t;
/**
* @brief corsacOTA socket control block
*
*/
typedef struct co_socket_cb {
int fd; // The file descriptor for this socket
enum co_socket_status {
CO_SOCKET_ACCEPT = 0,
CO_SOCKET_HANDSHAKE, // not handshake, or in progress
CO_SOCKET_WEBSOCKET_HEADER, // already handshake, now reading the header of websocket frame
CO_SOCKET_WEBSOCKET_EXTEND_LENGTH, // reading the extended length of websocket header
CO_SOCKET_WEBSOCKET_MASK, // reading the mask part of websocket header
CO_SOCKET_WEBSOCKET_PAYLOAD, // reading the payload of websocket frame
CO_SOCKET_CLOSING // waiting to close
} status;
char *buf; // data from raw socket
size_t remaining_len; // the number of available bytes remaining in buf
size_t read_len; // the number of bytes that have been processed
co_websocket_cb_t wcb; // websocket control block
} co_socket_cb_t;
/**
* @brief corsacOTA OTA control block
*
*/
typedef struct co_ota_cb {
enum co_ota_status {
CO_OTA_INIT = 0,
CO_OTA_LOAD,
CO_OTA_DONE,
CO_OTA_STOP,
CO_OTA_ERROR,
CO_OTA_FATAL_ERROR,
} status;
int32_t error_code; //// TODO: ?
int32_t total_size; // Total firmware size
int32_t offset; // Current processed size
int32_t chunk_size; // The response will be made every time the chunk size is reached
int32_t last_index_offset; // The offset recorded in the last response
} co_ota_cb_t;
/**
* @brief corsacOTA http control block
*
*/
typedef struct co_cb {
int listen_fd; // server listener FD
int websocket_fd; // only one websocket is allowed.
uint8_t *recv_data; // recv buffer at websocket stage (text mode)
int recv_data_offset; // (text mode)
int max_listen_num; // maxium number of connections. In fact, after the handshake is complete, there is only one connection to provide services
int wait_timeout_sec; // timeout (in seconds)
int wait_timeout_usec; // timeout (in microseconds)
co_socket_cb_t **socket_list; // socket control block list
co_socket_cb_t *websocket; // the only valid socket in the list
int accept_num; // current number of established connections
int closing_num; // current number of closing socket
co_ota_cb_t ota; // ota control block
} co_cb_t;
static co_cb_t *global_cb = NULL;
/* RFC 6455: The WebSocket Protocol
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
*/
#define WS_FIN 0x80
#define WS_RSV1 0x40
#define WS_RSV2 0x20
#define WS_RSV3 0x10
#define WS_OPCODE_CONTINUTAION 0x00
#define WS_OPCODE_TEXT 0x01
#define WS_OPCODE_BINARY 0x02
#define WS_OPCODE_CLOSE 0x08
#define WS_OPCODE_PING 0x09
#define WS_OPCODE_PONG 0x0A
#define WS_MASK 0x80
static inline int co_websocket_get_res_payload_offset(int payload_len) {
// promise: payload_len <= 65535
return 2 + (payload_len >= 126 ? 2 : 0);
}
static co_err_t co_websocket_process_header(co_cb_t *cb, co_socket_cb_t *scb) {
uint8_t opcode, fin, mask;
uint64_t payload_len;
uint8_t *data;
data = (uint8_t *)scb->buf;
if (scb->status == CO_SOCKET_WEBSOCKET_HEADER) {
if (scb->remaining_len < 2) {
return CO_OK;
}
// check RSV
if (data[0] & 0b1110000) {
return CO_FAIL; // no extension defining RSV
}
// first byte
fin = (data[0] & WS_FIN) == WS_FIN;
opcode = data[0] & 0b1111;
// second byte
mask = (data[1] & WS_MASK) == WS_MASK;
payload_len = data[1] & 0x7F;
switch (opcode) {
case WS_OPCODE_CONTINUTAION:
// nothing to do
break;
case WS_OPCODE_TEXT:
case WS_OPCODE_BINARY:
scb->wcb.OPCODE = opcode;
break;
case WS_OPCODE_PING:
case WS_OPCODE_PONG:
scb->wcb.OPCODE = opcode;
break;
case WS_OPCODE_CLOSE:
scb->wcb.OPCODE = opcode;
break;
default:
return CO_FAIL;
break;
}
scb->wcb.FIN = fin;
scb->wcb.MASK = mask;
scb->wcb.payload_len = payload_len;
// extended payload length should be read
if (payload_len == 126 || payload_len == 127) {
scb->status = CO_SOCKET_WEBSOCKET_EXTEND_LENGTH;
} else if (mask == 1) {
scb->status = CO_SOCKET_WEBSOCKET_MASK;
}
scb->read_len = 2; // first 2 byte header
}
if (scb->status == CO_SOCKET_WEBSOCKET_EXTEND_LENGTH) {
if (scb->wcb.payload_len == 126) {
if (scb->remaining_len < scb->read_len + 2) { // 2 byte extended length
return CO_OK;
}
payload_len = data[2] << 8 | data[3]; // 0 + scb->read_len == 2
scb->read_len += 2;
} else if (scb->wcb.payload_len == 127) { // 8 byte extended length
if (scb->remaining_len < scb->read_len + 8) {
return CO_OK;
}
payload_len = ((uint64_t)(data[9]) << 0);
payload_len |= ((uint64_t)(data[8]) << 8);
payload_len |= ((uint64_t)(data[7]) << 16);
payload_len |= ((uint64_t)(data[6]) << 24);
payload_len |= ((uint64_t)(data[5]) << 32);
payload_len |= ((uint64_t)(data[4]) << 40);
payload_len |= ((uint64_t)(data[3]) << 48);
payload_len |= ((uint64_t)(data[2]) << 56);
// most significant bit MUST be 0
if (((payload_len >> 63) & 0b1) == 0x1) {
ESP_LOGE(CO_TAG, "wrong payload length");
return CO_FAIL;
}
scb->read_len += 8;
} else {
payload_len = scb->wcb.payload_len;
}
scb->wcb.payload_len = payload_len;
scb->status = scb->wcb.MASK == 1 ? CO_SOCKET_WEBSOCKET_MASK : CO_SOCKET_WEBSOCKET_PAYLOAD;
}
if (scb->status == CO_SOCKET_WEBSOCKET_MASK) {
if (scb->remaining_len < scb->read_len + 4) { // 4 byte mask
return CO_OK;
}
memcpy(&scb->wcb.mask.data[0], &data[scb->read_len], 4);
scb->read_len += 4;
scb->status = CO_SOCKET_WEBSOCKET_PAYLOAD;
} else {
scb->status = CO_SOCKET_WEBSOCKET_PAYLOAD;
}
return CO_OK;
}
// We promise that the length of the payload should not exceed 65535
static co_err_t co_websocket_send_frame(void *frame_buffer, size_t payload_len, int frame_type) {
int sz;
uint16_t payload_length;
uint8_t *p;
payload_length = payload_len;
sz = co_websocket_get_res_payload_offset(payload_len) + payload_len;
p = frame_buffer;
// 2 bytes
*p++ = WS_FIN | frame_type; // frame_type
*p++ = (payload_length >= 126 ? 126 : payload_length);
// extended length
if (payload_length >= 126) {
payload_length = htons(payload_length);
memcpy(p, &payload_length, 2);
p += 2;
}
// no mask
send(global_cb->websocket->fd, frame_buffer, sz, 0);
return CO_OK;
}
// Create a new frame buffer, construct text and send frame.
static co_err_t co_websocket_send_msg_with_code(int code, const char *msg) {
char *buffer;
int len, ret;
int offset;
len = strlen(msg);
offset = co_websocket_get_res_payload_offset(len);
buffer = malloc(offset + len + 25);
if (buffer == NULL) {
ret = CO_ERROR_NO_MEM;
goto cleanup;
}
if (code == CO_RES_SUCCESS) {
ret = snprintf(buffer + offset, len + 24, "code=%d&data=\"%s\"", code, msg);
} else {
ret = snprintf(buffer + offset, len + 24, "code=%d&data=\"msg=%s\"", code, msg);
}
if (ret < 0) {
ESP_LOGE(CO_TAG, "invalid arg");
ret = CO_ERROR_INVALID_ARG;
goto cleanup;
}
ret = co_websocket_send_frame(buffer, ret, WS_OPCODE_TEXT);
cleanup:
free(buffer);
return ret;
}
#if (CO_TEST_MODE == 1)
// use for test
static co_err_t co_websocket_send_echo(void *data, size_t len, int frame_type) {
char *buffer;
int ret;
int offset;
offset = co_websocket_get_res_payload_offset(len);
buffer = malloc(offset + len);
if (buffer == NULL) {
ret = CO_ERROR_NO_MEM;
goto cleanup;
}
memcpy(buffer + offset, data, len);
ret = co_websocket_send_frame(buffer, len, frame_type);
cleanup:
free(buffer);
return ret;
}
#endif // (CO_TEST_MODE == 1)
static void co_websocket_process_binary(uint8_t *data, size_t len) {
co_websocket_process_dap(data, len);
}
static void co_websocket_process_text(uint8_t *data, size_t len) {
}
// send pong response
// TODO: too long ping frame
static void co_websocket_process_ping(co_cb_t *cb, co_socket_cb_t *scb) {
int len;
// control frame max payload length: 125 -> 0 byte extended length
len = 2 + scb->wcb.payload_len + (scb->wcb.MASK ? 4 : 0);
scb->buf[0] = WS_FIN | WS_OPCODE_PONG;
send(scb->fd, scb->buf, len, 0);
}
// close handshake
// TODO: array
static void co_websocket_process_close(co_cb_t *cb, co_socket_cb_t *scb) {
uint8_t buf[4];
uint8_t *p = buf;
*p++ = WS_FIN | WS_OPCODE_CLOSE;
*p++ = 0x02; // 2 byte status code
// normal closure
*p++ = 0x03;
*p = 0xe8;
send(scb->fd, buf, 4, 0);
}
static inline CO_INLINE uint32_t co_rotr32(uint32_t n, unsigned int c) {
const unsigned int mask = (CHAR_BIT * sizeof(n) - 1);
c &= mask;
return (n >> c) | (n << ((-c) & mask));
}
/**
* @brief Quick calculation WebSocket. The process of calculating the mask is one of the performance bottlenecks
* of the entire websocket. The performance between the optimized version and the version without mask is not significant.
*
* We assume: the natural machine word length is 4byte (32bits) and the endianness is little-endian
* For xtensa: single fetch: 4 byte(32bit)
*
* @param data data buffer ptr
* @param mask websocket mask. Little-endian 32bis mask.
* @param len data length
*/
static void co_websocket_fast_mask(uint8_t *data, uint32_t mask, size_t len) {
uint32_t new_mask;
int align_len;
size_t i;
const uint8_t *p_mask = (uint8_t *)&mask;
unsigned long int dst = (long int)data;
if (len >= 8) {
// copy just a few bytes to make dst aligned.
align_len = (-dst) % 4;
len -= align_len;
for (i = 0; i < align_len; i++) {
data[i] ^= p_mask[i];
}
// use the new mask on the aligned address
switch (align_len) {
case 1:
new_mask = co_rotr32(mask, 8U);
break;
case 2:
new_mask = co_rotr32(mask, 16U);
break;
case 3:
new_mask = co_rotr32(mask, 24U);
break;
default: // 0
new_mask = mask;
break;
}
p_mask = (uint8_t *)&new_mask;
dst += align_len;
for (i = 0; i < len / 4; i++) {
*((uint32_t *)dst) ^= new_mask;
dst += 4;
}
len %= 4;
}
// There are just a few bytes to process
for (i = 0; i < len; i++) {
*((uint8_t *)dst) ^= p_mask[i % 4];
dst += 1;
}
}
static inline uint32_t co_websocket_get_new_mask(uint32_t mask, size_t len) {
switch (len & 0b11) {
case 1:
return co_rotr32(mask, 8U);
case 2:
return co_rotr32(mask, 16U);
case 3:
return co_rotr32(mask, 24U);
default:
return mask;
}
}
/**
* @brief Process websocket payload
*
* @param cb corsacOTA control block
* @param scb corsacOTA socket control block
* @return co_err_t
* - CO_OK: Successful processing of all payloads
* - CO_ERROR_IO_PENDING: There are still new frames to be processed
*/
static co_err_t co_websocket_process_payload(co_cb_t *cb, co_socket_cb_t *scb) {
int len, new_len;
uint8_t *data;
uint32_t mask;
data = (uint8_t *)scb->buf + scb->read_len;
// May be possible to read the complete frame and maybe a new frame rate afterwards
len = min(scb->remaining_len - scb->read_len, scb->wcb.payload_len);
// For ping frames, we will directly change their opcode and send.
if (scb->wcb.MASK == 1 && scb->wcb.OPCODE != WS_OPCODE_PING) {
mask = scb->wcb.mask.val;
co_websocket_fast_mask(data, mask, len);
scb->wcb.mask.val = co_websocket_get_new_mask(mask, len);
}
// In the previous processing, we can ensure that each new frame can begin in a place where the Buffer offset is 0.
switch (scb->wcb.OPCODE) {
case WS_OPCODE_TEXT:
#if (CO_TEST_MODE == 1)
co_websocket_send_echo(data, len, WS_OPCODE_TEXT);
break;
#endif
// case 0: This frame should be skip
if (scb->wcb.skip_frame) {
if (len == scb->wcb.payload_len) { // The last part of the data in this frame has been received
scb->wcb.skip_frame = false;
}
break;
}
// case 1: Receive the entire payload
if (len == scb->wcb.payload_len && cb->recv_data_offset == 0) {
co_websocket_process_text(data, len);
break;
}
// case 2: Part of the payload has been received before
if (len > CONFIG_CO_WS_TEXT_BUFFER_SIZE - cb->recv_data_offset) { // overflow
if (len < scb->wcb.payload_len) { // This frame has not yet been received
scb->wcb.skip_frame = true;
}
co_websocket_send_msg_with_code(CO_RES_INVALID_SIZE, "request too long");
cb->recv_data_offset = 0;
break;
}
memcpy(cb->recv_data + cb->recv_data_offset, data, len);
cb->recv_data_offset += len;
if (len == scb->wcb.payload_len) {
co_websocket_process_text(cb->recv_data, len);
cb->recv_data_offset = 0;
}
break;
case WS_OPCODE_BINARY:
#if (CO_TEST_MODE == 1)
co_websocket_send_echo(data, len, WS_OPCODE_BINARY);
break;
#endif
//// TODO: check return val
co_websocket_process_binary(data, len);
break;
case WS_OPCODE_PING:
co_websocket_process_ping(cb, scb);
break;
case WS_OPCODE_PONG:
break;
case WS_OPCODE_CLOSE:
co_websocket_process_close(cb, scb);
return CO_FAIL; // close by server
default:
ESP_LOGE(CO_TAG, "unknow opcode: %d", scb->wcb.OPCODE);
break;
}
new_len = scb->remaining_len - scb->read_len - len;
// case 0: New frames still exist
if (new_len > 0) {
// For simplicity, we make sure that the websocket header is always at the beginning of the buf.
memcpy(scb->buf, data + len, new_len);
scb->read_len = 0;
scb->remaining_len = new_len;
scb->status = CO_SOCKET_WEBSOCKET_HEADER;
scb->wcb.payload_len = 0;
scb->wcb.payload_read_len = 0;
return CO_ERROR_IO_PENDING;
}
// case 1: The payload part is not yet fully read.
else if (scb->wcb.payload_len > len) {
scb->wcb.payload_len -= len;
scb->read_len = 0;
scb->remaining_len = 0;
return CO_OK;
}
// case 2: Exactly one complete frame is read and there is no remaining available data in buf.
else {
scb->read_len = 0;
scb->remaining_len = 0;
scb->status = CO_SOCKET_WEBSOCKET_HEADER;
scb->wcb.payload_len = 0;
scb->wcb.payload_read_len = 0;
return CO_OK;
}
}
static esp_err_t co_websocket_process(co_cb_t *cb, co_socket_cb_t *scb) {
if (cb->websocket != scb) {
return ESP_FAIL;
}
int fd, ret, offset;
fd = scb->fd;
offset = scb->remaining_len;
ret = recv(fd, scb->buf + offset, CONFIG_CO_SOCKET_BUFFER_SIZE - offset, 0);
if (ret <= 0) {
return ESP_FAIL;
}
scb->remaining_len += ret;
do {
// After we process a partial or complete payload,
// we always receive a new payload or header starting from the head of the buf.
if (scb->status != CO_SOCKET_WEBSOCKET_PAYLOAD) {
ret = co_websocket_process_header(cb, scb);
if (ret != CO_OK) {
return CO_FAIL;
}
}
// Perhaps we have already read the header section, and if there are extra bytes left over,
// we continue reading the payload section.
if (scb->status == CO_SOCKET_WEBSOCKET_PAYLOAD) {
ret = co_websocket_process_payload(cb, scb);
if (ret == CO_FAIL) {
return CO_FAIL;
}
}
} while (ret == CO_ERROR_IO_PENDING);
return ESP_OK;
}
/**
* @brief parse HTTP header lines of format:
* \r\nfield_name: value1, value2, ... \r\n
*
* @param header_start
* @param header_end
* @param field_name
* @param value Optional value
* @return const char* The specific value starts, or the beginning of value in field.
*/
static const char *co_http_header_find_field_value(const char *header_start, const char *header_end, const char *field_name, const char *value) {
const char *field_start, *field_end, *next_crlf, *value_start;
int field_name_len;
field_name_len = (int)strlen(field_name);
field_start = header_start;
do {
field_start = strcasestr(field_start + 1, field_name);
field_end = field_start + field_name_len - 1;
if (field_start != NULL && field_start - header_start >= 2 && field_start[-2] == '\r' && field_start[-1] == '\n') { // is start with "\r\n" ?
if (header_end - field_end >= 1 && field_end[1] == ':') { // is end with ':' ?
break;
}
}
} while (field_start != NULL);
if (field_start == NULL) {
return NULL;
}
// find the field terminator
next_crlf = strcasestr(field_start, "\r\n");
if (next_crlf == NULL) {
return NULL; // Malformed HTTP header!
}
// If not looking for a value, then return a pointer to the start of values string
if (value == NULL) {
return field_end + 2; // 2 for ':' ' '(blank)
}
value_start = strcasestr(field_start, value);
if (value_start == NULL) {
return NULL;
}
if (value_start > next_crlf) {
return NULL; // encounter with unanticipated CRLF
}
// The value we found should be properly delineated from the other tokens
if (isalnum((unsigned char)value_start[-1]) || isalnum((unsigned char)value_start[strlen(value)])) {
// "field_name: value1, value2,"
// Consecutive tokens will be considered as errors.
return NULL;
}
return value_start;
}
static void co_http_error_400_response(co_cb_t *cb, co_socket_cb_t *scb) {
const char *error = "HTTP/1.1 400 Bad Request\r\n\r\n";
send(scb->fd, error, strlen(error), 0);
}
#define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
static int co_sha1(const unsigned char *src, size_t src_len, unsigned char *dst) {
return mbedtls_sha1(src, src_len, dst);
}
static int co_base64_encode(unsigned char *dst, size_t dst_len, size_t *written_len, unsigned char *src, size_t src_len) {
return mbedtls_base64_encode(dst, dst_len, written_len, src, src_len);
}
static esp_err_t co_websocket_create_accept_key(char *dst, size_t dst_len, const char *client_key) {
uint8_t sha1buf[20], key_src[60];
memcpy(key_src, client_key, 24);
memcpy(key_src + 24, WS_GUID, 36);
if (co_sha1(key_src, sizeof(key_src), sha1buf) != 0) {
return ESP_FAIL;
}
size_t base64_encode_len;
if (co_base64_encode((unsigned char *)dst, dst_len, &base64_encode_len, sha1buf, sizeof(sha1buf)) != 0) {
return ESP_FAIL;
}
// add terminator
dst[base64_encode_len] = '\0';
return ESP_OK;
}
static esp_err_t co_websocket_handshake_send_key(int fd, const char *client_key) {
char res_header[256], accept_key[29];
int res_header_length;
if (co_websocket_create_accept_key(accept_key, sizeof(accept_key), client_key) != ESP_OK) {
ESP_LOGE(CO_TAG, LOG_FMT("fail to create accept key"));
return ESP_FAIL;
}
snprintf(res_header, sizeof(res_header),
"HTTP/1.1 101 Switching Protocols\r\n"
"Server: corsacOTA server\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n"
"\r\n",
accept_key);
res_header_length = strlen(res_header);
send(fd, res_header, res_header_length, 0);
return ESP_OK;
}
static esp_err_t co_websocket_handshake_process(co_cb_t *cb, co_socket_cb_t *scb) {
if (scb->remaining_len == 0) {
memset(scb->buf, 0, CONFIG_CO_SOCKET_BUFFER_SIZE);
}
int offset = scb->remaining_len;
int fd = scb->fd;
int ret = recv(fd, scb->buf + offset, CONFIG_CO_SOCKET_BUFFER_SIZE - offset, 0);
if (ret <= 0) {
co_http_error_400_response(cb, scb);
return ESP_FAIL;
}
scb->remaining_len += ret;
// Already received the entire http header?
if (scb->remaining_len < 4 || memcmp(scb->buf + scb->remaining_len - 4, "\r\n\r\n", 4) != 0) {
return ESP_OK; // Not yet received
}
const char *header_start = scb->buf, *header_end = scb->buf + scb->remaining_len - 1;
const char *ws_key_start, *ws_key_end;
if (co_http_header_find_field_value(header_start, header_end, "Upgrade", "websocket") == NULL ||
co_http_header_find_field_value(header_start, header_end, "Connection", "Upgrade") == NULL ||
(ws_key_start = co_http_header_find_field_value(header_start, header_end, "Sec-WebSocket-Key", NULL)) == NULL) {
co_http_error_400_response(cb, scb);
return ESP_FAIL;
}
/* example:
Sec-WebSocket-Key: c2REMVVpRXJRQWJ0Q1dKeQ==\r\n
|
ws_key_start
*/
// skip the extra blank
for (; *ws_key_start == ' '; ws_key_start++) {
;
}
// find the end of ws key
for (ws_key_end = ws_key_start; *ws_key_end != '\r' && *ws_key_end != ' '; ws_key_end++) {
;
}
/* example:
Sec-WebSocket-Key: c2REMVVpRXJRQWJ0Q1dKeQ==\r\n
| ||
ws_key_start ws_key_end
*/
if (ws_key_end - ws_key_start != 24) {
co_http_error_400_response(cb, scb);
return ESP_FAIL;
}
if (co_websocket_handshake_send_key(scb->fd, ws_key_start) != ESP_OK) {
co_http_error_400_response(cb, scb);
return ESP_FAIL;
}
ESP_LOGD(CO_TAG, "websocket handshake success");
cb->websocket = scb;
scb->status = CO_SOCKET_WEBSOCKET_HEADER;
scb->remaining_len = 0;
memset(scb->buf, 0, CONFIG_CO_SOCKET_BUFFER_SIZE);
return ESP_OK;
}
static void websocket_buffer_malloc() {
if (ws_process_buffer != NULL)
return;
free_dap_ringbuf();
ws_process_buffer = malloc(1200);
}
static void websocket_buffer_free() {
if (ws_process_buffer != NULL) {
free(ws_process_buffer);
ws_process_buffer = NULL;
}
}
static void co_websocket_process_dap(uint8_t *data, size_t len) {
uint8_t *buf;
int max_offset, res, offset;
max_offset = co_websocket_get_res_payload_offset(1500);
buf = ws_process_buffer + max_offset;
res = DAP_ExecuteCommand(data, buf);
res &= 0xFFFF;
offset = co_websocket_get_res_payload_offset(res);
buf -= offset;
co_websocket_send_frame(buf, res, WS_OPCODE_BINARY);
}
int websocket_worker(int fd, uint8_t *base, uint32_t length) {
co_cb_t cb;
co_socket_cb_t scb;
esp_err_t ret;
memset(&cb, 0, sizeof(co_cb_t));
memset(&scb, 0, sizeof(co_socket_cb_t));
cb.recv_data = NULL; // used in websocket text mode
cb.websocket = &scb;
scb.fd = fd;
scb.status = CO_SOCKET_HANDSHAKE;
scb.buf = (char *)base;
scb.remaining_len = 4; // already read 4 byte
global_cb = &cb;
// handshake
do {
ret = co_websocket_handshake_process(&cb, &scb);
if (ret != ESP_OK)
return ret;
} while (scb.status == CO_SOCKET_HANDSHAKE);
websocket_buffer_malloc();
// websocket data process
do {
ret = co_websocket_process(&cb, &scb);
if (ret != ESP_OK)
goto out;
} while (1);
out:
websocket_buffer_free();
return 0;
}

View File

@ -1,8 +0,0 @@
#ifndef __WEBSOCKET_SERVER_H__
#define __WEBSOCKET_SERVER_H__
#include <stdint.h>
int websocket_worker(int fd, uint8_t *base, uint32_t length);
#endif

View File

@ -12,16 +12,6 @@
#define WIFI_DEFAULT_STA_SSID "example_ssid"
#define WIFI_DEFAULT_STA_PASS "12345678"
#if defined CONFIG_IDF_TARGET_ESP32
#define WIFI_LED_ENABLE 0
#elif defined CONFIG_IDF_TARGET_ESP32C3
#define WIFI_LED_ENABLE 0
#elif defined CONFIG_IDF_TARGET_ESP32S3
#define WIFI_LED_ENABLE 0
#endif
#define WIFI_LED_PIN 9
#define IP4_ADDR_EXPAND(...) IP4_ADDR(__VA_ARGS__)
#endif //WIFI_CONFIGURATION_H_GUARD

View File

@ -14,10 +14,6 @@
#include <lwip/ip4_addr.h>
#include <string.h>
#include <assert.h>
#include <hal/gpio_types.h>
#include <driver/ledc.h>
#include <hal/gpio_hal.h>
#include <soc/ledc_periph.h>
#define TAG __FILENAME__
@ -53,16 +49,10 @@ static void set_sta_cred(const char *ssid, const char *password);
static void disconn_handler(void);
static int set_default_sta_cred(void);
static void wifi_led_init();
static void wifi_led_set_blink();
static void wifi_led_set_on();
void wifi_manager_init(void)
{
esp_err_t err;
uint8_t do_connect = 0;
wifi_led_init();
wifi_led_set_blink();
ap_netif = esp_netif_create_default_wifi_ap();
assert(ap_netif);
@ -133,55 +123,6 @@ void wifi_manager_init(void)
}
}
void wifi_led_init()
{
#if WIFI_LED_ENABLE
ledc_timer_config_t ledc_timer = {
.duty_resolution = LEDC_TIMER_14_BIT, // resolution of PWM duty
.freq_hz = 5, // frequency of PWM signal
.speed_mode = LEDC_LOW_SPEED_MODE, // timer mode
.timer_num = LEDC_TIMER_0, // timer index
.clk_cfg = LEDC_USE_APB_CLK, // Auto select the source clock
};
ledc_timer_config(&ledc_timer);
ledc_channel_config_t ledc_channel = {
.channel = LEDC_CHANNEL_0,
.duty = 0,
.gpio_num = WIFI_LED_PIN,
.speed_mode = LEDC_LOW_SPEED_MODE,
.hpoint = 1,
.timer_sel = LEDC_TIMER_0,
.flags.output_invert = 0
};
ledc_channel_config(&ledc_channel);
ledc_fade_func_install(0);
gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[WIFI_LED_PIN], PIN_FUNC_GPIO);
gpio_set_direction(WIFI_LED_PIN, GPIO_MODE_OUTPUT_OD);
gpio_set_direction(WIFI_LED_PIN, GPIO_FLOATING);
esp_rom_gpio_connect_out_signal(WIFI_LED_PIN, ledc_periph_signal[LEDC_LOW_SPEED_MODE].sig_out0_idx + LEDC_CHANNEL_0,
1, 0);
#endif
}
static void wifi_led_set_blink()
{
#if WIFI_LED_ENABLE
ledc_set_duty_and_update(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1 << 12), 0);
#endif
}
static void wifi_led_set_on()
{
#if WIFI_LED_ENABLE
ledc_set_duty_and_update(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, (1 << 14)-1, 0);
#endif
}
/**
* @brief called by wifi_event_handler on scan done
* */
@ -329,7 +270,6 @@ int wifi_manager_connect(const char *ssid, const char *password)
}
/* connection success: overwrite last connected credential */
wifi_led_set_on();
wifi_credential_t credential;
memcpy(credential.ssid, ssid, 32);
memcpy(credential.password, password, 64);
@ -377,9 +317,6 @@ static void reconnection_task(void *arg)
ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(20000));
if (ctx.conn.event || ctx.auto_reconnect == 0) {
/* reconnection successful or stop reconnect */
if (ctx.conn.event) {
wifi_led_set_on();
}
break;
}
@ -405,7 +342,7 @@ static void disconn_handler(void)
* 2. WI-FI AP or AP device is closed
* 3. this device is ejected by AP
* */
wifi_led_set_blink();
if (ctx.auto_reconnect == 0) {
return;
}