[FL-2630] Wireless UART (#45)

* HTTP: websocket mockup
* PSRAM: 2M, store some global objects there
* Web interface: UART console and refactoring
* Simple-UART: get config
* Network: websocket for web uart
* Web interface: UART history, receive indicator
* Build: artifacts
* Web interface: UART config
* Simple UART: proxy config
* Web interface: cleanup API and fix README
* software uart logs: more precise timings
* software uart logs: enlarge buffer
* uart: network socket
* app: network uart
* Web interface: UART send
* Web interface: update dependencies
* UART Rx: 1Mb stream buffer
* HTTP Server: cleanup websocket fns
* Web interface: smart nbsp in terminal
* Web interface: add keypress to non-interactive elements with on:click
* bundle
* Web interface: fix first nbsp on line
* Web interface: firmware version
* Attempts to fix build: 1
* Attempts to fix build: 2
* Attempts to fix build: 3
* Web interface: mobile friendly uart terminal
* Web interface: mobile friendly input
This commit is contained in:
Sergey Gavrilov 2023-09-26 19:37:47 +03:00 committed by GitHub
parent 41acbe3d97
commit 9447ee9260
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2113 additions and 535 deletions

View File

@ -1,6 +1,6 @@
# Black Magic Probe for ESP32-S2
# Black Magic Probe / DapLink for ESP32-S2
WiFi/USB capable version of the famous Black Magic Probe debugger.
WiFi/USB capable version of the famous BlackMagicProbe (or DapLink) debugger.
# Clone the Repository
@ -32,22 +32,26 @@ Run:
idf.py -p <port> flash
```
## Test with ESP-IDF
Connect to the dev board with:
```shell
idf.py -p <port> monitor
```
You should not see errors in the logs if the firmware is installed and running correctly.
## Web interface development
Web interface is located in `components/svelte-portal` and written in Svelte. To build it, you need to install Node.js and run `npm install` in `components/svelte-portal` directory. Then you can run `npm run dev` to start development server or `npm run build` to build production version.
Typical workflow is to fix the board's IP address in `components/svelte-portal/src/App.svelte` and then run `npm run dev`. After that, you can open `http://localhost:5000` in your browser and see changes in the web interface in real time with live reload.
Typical workflow is to fix the board's IP address in `components/svelte-portal/src/lib/Api.svelte` and then run `npm run dev`. After that, you can open `http://localhost:5000` in your browser and see changes in the web interface in real time with live reload.
If you want to change local ip or port, you need to run `export HOST={ip} PORT={port}` before `npm run dev`.
```shell
export HOST=127.0.0.1 PORT=3000
npm run dev
```
When you're done, you need to run `npm run build`, `idf.py build` and then `idf.py -p <port> flash`. You can then open `http://blackmagic.local` in your browser and see the changes in the web interface.
```shell
npm run build
idf.py build
idf.py -p <port> flash
```
## Schematic

View File

@ -17,6 +17,13 @@ typedef struct {
uart_isr rx_isr;
} uart_context_t;
typedef struct {
uint32_t baud_rate;
uart_stop_bits_t stop_bits;
uart_parity_t parity;
uart_word_length_t data_bits;
} UartInnerConfig;
#define UART_CONTEX_INIT_DEF(uart_num) \
{ \
.hal.dev = UART_LL_GET_HW(uart_num), .uart_index = uart_num, .isr_context = NULL, \
@ -31,6 +38,11 @@ static uart_context_t uart_context[UART_NUM_MAX] = {
#endif
};
static UartInnerConfig uart_config[UART_NUM_MAX] = {
{0},
{0},
};
#define UART_HAL(uart_num) &(uart_context[uart_num].hal)
/***********************************************/
@ -173,17 +185,37 @@ static void simple_uart_isr(void* arg) {
}
void simple_uart_set_baud_rate(uint8_t uart_num, uint32_t baud_rate) {
uart_config[uart_num].baud_rate = baud_rate;
uart_hal_set_baudrate(UART_HAL(uart_num), baud_rate);
}
void simple_uart_set_stop_bits(uint8_t uart_num, uart_stop_bits_t stop_bits) {
uart_config[uart_num].stop_bits = stop_bits;
uart_hal_set_stop_bits(UART_HAL(uart_num), stop_bits);
}
void simple_uart_set_parity(uint8_t uart_num, uart_parity_t parity) {
uart_config[uart_num].parity = parity;
uart_hal_set_parity(UART_HAL(uart_num), parity);
}
void simple_uart_set_data_bits(uint8_t uart_num, uart_word_length_t data_bits) {
uart_config[uart_num].data_bits = data_bits;
uart_hal_set_data_bit_num(UART_HAL(uart_num), data_bits);
}
uint32_t simple_uart_get_baud_rate(uint8_t uart_num) {
return uart_config[uart_num].baud_rate;
}
uart_stop_bits_t simple_uart_get_stop_bits(uint8_t uart_num) {
return uart_config[uart_num].stop_bits;
}
uart_parity_t simple_uart_get_parity(uint8_t uart_num) {
return uart_config[uart_num].parity;
}
uart_word_length_t simple_uart_get_data_bits(uint8_t uart_num) {
return uart_config[uart_num].data_bits;
}

View File

@ -87,4 +87,36 @@ void simple_uart_set_parity(uint8_t uart_num, uart_parity_t parity);
* @param uart_num
* @param data_bits
*/
void simple_uart_set_data_bits(uint8_t uart_num, uart_word_length_t data_bits);
void simple_uart_set_data_bits(uint8_t uart_num, uart_word_length_t data_bits);
/**
* @brief Get the UART baud rate
*
* @param uart_num
* @return uint32_t
*/
uint32_t simple_uart_get_baud_rate(uint8_t uart_num);
/**
* @brief Get the UART stop bits
*
* @param uart_num
* @return uart_stop_bits_t
*/
uart_stop_bits_t simple_uart_get_stop_bits(uint8_t uart_num);
/**
* @brief Get the UART parity
*
* @param uart_num
* @return uart_parity_t
*/
uart_parity_t simple_uart_get_parity(uint8_t uart_num);
/**
* @brief Get the UART data bits
*
* @param uart_num
* @return uart_word_length_t
*/
uart_word_length_t simple_uart_get_data_bits(uint8_t uart_num);

View File

@ -14,7 +14,7 @@ struct SoftUart {
#define wait_cycles(cycles) \
for(uint32_t start = cycle_count_get(); cycle_count_get() - start < cycles;)
static uint32_t cycle_count_get() {
static inline uint32_t __attribute__((always_inline)) cycle_count_get() {
uint32_t ccount;
__asm__ __volatile__("esync; rsr %0,ccount" : "=a"(ccount));
return ccount;

View File

@ -8,7 +8,8 @@
"name": "svelte-app",
"version": "1.0.0",
"dependencies": {
"sirv-cli": "^1.0.0"
"sirv-cli": "^1.0.0",
"stringview": "^3.0.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.0.0",
@ -57,6 +58,64 @@
"node": ">=6.9.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"dependencies": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"dev": true,
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
@ -160,6 +219,18 @@
"@types/node": "*"
}
},
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@ -632,9 +703,9 @@
}
},
"node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@ -926,15 +997,6 @@
"node": ">= 10"
}
},
"node_modules/source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@ -960,6 +1022,11 @@
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"node_modules/stringview": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stringview/-/stringview-3.0.0.tgz",
"integrity": "sha512-eNPN9TLouSN5pMZRtBF8gB7PS+E+rx27LY4Qx4uWzxH18aXHpy6Jeoz4nRVMSTzYGRJ2kSDOac9YQvSprmDTrA=="
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -973,22 +1040,23 @@
}
},
"node_modules/svelte": {
"version": "3.44.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.44.2.tgz",
"integrity": "sha512-jrZhZtmH3ZMweXg1Q15onb8QlWD+a5T5Oca4C1jYvSURp2oD35h4A5TV6t6MEa93K4LlX6BkafZPdQoFjw/ylA==",
"version": "3.59.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
"integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/terser": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"version": "5.20.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz",
"integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==",
"dev": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
},
"bin": {
@ -996,14 +1064,6 @@
},
"engines": {
"node": ">=10"
},
"peerDependencies": {
"acorn": "^8.5.0"
},
"peerDependenciesMeta": {
"acorn": {
"optional": true
}
}
},
"node_modules/tinydate": {
@ -1089,6 +1149,55 @@
"js-tokens": "^4.0.0"
}
},
"@jridgewell/gen-mapping": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
"dev": true,
"requires": {
"@jridgewell/set-array": "^1.0.1",
"@jridgewell/sourcemap-codec": "^1.4.10",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true
},
"@jridgewell/set-array": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
"dev": true
},
"@jridgewell/source-map": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz",
"integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==",
"dev": true,
"requires": {
"@jridgewell/gen-mapping": "^0.3.0",
"@jridgewell/trace-mapping": "^0.3.9"
}
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"@jridgewell/trace-mapping": {
"version": "0.3.19",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
"dev": true,
"requires": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"@polka/url": {
"version": "1.0.0-next.21",
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz",
@ -1173,6 +1282,12 @@
"@types/node": "*"
}
},
"acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"dev": true
},
"ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@ -1542,9 +1657,9 @@
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
@ -1759,12 +1874,6 @@
"tinydate": "^1.0.0"
}
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
},
"source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
@ -1789,6 +1898,11 @@
"integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==",
"dev": true
},
"stringview": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/stringview/-/stringview-3.0.0.tgz",
"integrity": "sha512-eNPN9TLouSN5pMZRtBF8gB7PS+E+rx27LY4Qx4uWzxH18aXHpy6Jeoz4nRVMSTzYGRJ2kSDOac9YQvSprmDTrA=="
},
"supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
@ -1799,19 +1913,20 @@
}
},
"svelte": {
"version": "3.44.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.44.2.tgz",
"integrity": "sha512-jrZhZtmH3ZMweXg1Q15onb8QlWD+a5T5Oca4C1jYvSURp2oD35h4A5TV6t6MEa93K4LlX6BkafZPdQoFjw/ylA==",
"version": "3.59.2",
"resolved": "https://registry.npmjs.org/svelte/-/svelte-3.59.2.tgz",
"integrity": "sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==",
"dev": true
},
"terser": {
"version": "5.10.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.10.0.tgz",
"integrity": "sha512-AMmF99DMfEDiRJfxfY5jj5wNH/bYO09cniSqhfoyxc8sFoYIgkJy86G04UoZU5VjlpnplVu0K6Tx6E9b5+DlHA==",
"version": "5.20.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.20.0.tgz",
"integrity": "sha512-e56ETryaQDyebBwJIWYB2TT6f2EZ0fL0sW/JRXNMN26zZdKi2u/E/5my5lG6jNxym6qsrVXfFRmOdV42zlAgLQ==",
"dev": true,
"requires": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map": "~0.7.2",
"source-map-support": "~0.5.20"
}
},

View File

@ -19,6 +19,7 @@
"svelte": "^3.0.0"
},
"dependencies": {
"sirv-cli": "^1.0.0"
"sirv-cli": "^1.0.0",
"stringview": "^3.0.0"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -39,8 +39,9 @@ export default {
},
plugins: [
replace({
development_mode: !production,
}),
development_mode: !production,
preventAssignment: true,
}),
svelte({
compilerOptions: {
// enable run-time checks when not in production
@ -58,7 +59,8 @@ export default {
// https://github.com/rollup/plugins/tree/master/packages/commonjs
resolve({
browser: true,
dedupe: ['svelte']
dedupe: ['svelte', 'stringview'],
extensions: ['.svelte', '.js', '.mjs']
}),
commonjs(),

View File

@ -1,331 +1,116 @@
<script>
import Button from "./Button.svelte";
import Popup from "./Popup.svelte";
import Input from "./Input.svelte";
import Spinner from "./Spinner.svelte";
import SpinnerBig from "./SpinnerBig.svelte";
import Select from "./Select.svelte";
import ButtonInline from "./ButtonInline.svelte";
let server = "";
if (development_mode) {
server = "http://172.30.1.223";
}
async function api_post(api, data) {
const res = await fetch(api, {
method: "POST",
body: JSON.stringify(data),
});
const json = await res.json();
return json;
}
async function api_get(api) {
const res = await fetch(api, {
method: "GET",
});
const json = await res.json();
return json;
}
let popup_select_net;
let popup_message;
let popup_message_text;
let mode_select;
let usb_mode_select;
let ap_ssid_input;
let ap_pass_input;
let sta_ssid_input;
let sta_pass_input;
let hostname_input;
import WebSocket from "./lib/WebSocket.svelte";
import UartTerminal from "./lib/UartTerminal.svelte";
import TabWiFi from "./tabs/TabWiFi.svelte";
import TabSys from "./tabs/TabSys.svelte";
import TabPs from "./tabs/TabPS.svelte";
import Reload from "./lib/Reload.svelte";
import Indicator from "./lib/Indicator.svelte";
import { onMount } from "svelte";
let current_tab = "WiFi";
if (localStorage.getItem("current_tab") != null) {
current_tab = localStorage.getItem("current_tab");
}
async function save_settings() {
popup_message_text = "";
popup_message.show();
await api_post(server + "/api/v1/wifi/set_credentials", {
wifi_mode: mode_select.get_value(),
usb_mode: usb_mode_select.get_value(),
ap_ssid: ap_ssid_input.get_value(),
ap_pass: ap_pass_input.get_value(),
sta_ssid: sta_ssid_input.get_value(),
sta_pass: sta_pass_input.get_value(),
hostname: hostname_input.get_value(),
}).then((json) => {
if (json.error) {
popup_message_text = json.error;
} else {
popup_message_text = "Saved!";
}
});
}
async function reboot_board() {
api_post(server + "/api/v1/system/reboot", {});
popup_message_text = "Rebooted";
popup_message.show();
}
function change_tab(tab) {
current_tab = tab;
localStorage.setItem("current_tab", current_tab);
}
function print_mac(mac_array) {
let str = "";
for (let index = 0; index < mac_array.length; index++) {
str += mac_array[index].toString(16).padStart(2, "0");
if (index < mac_array.length - 1) {
str += ":";
}
}
return str;
let uart_history_array = [];
function uart_history_array_get() {
return uart_history_array;
}
function print_ip(ip_addr) {
var byteArray = [0, 0, 0, 0];
for (var index = 0; index < byteArray.length; index++) {
var byte = ip_addr & 0xff;
byteArray[index] = byte;
ip_addr = ip_addr >> 8;
}
return byteArray.join(".");
function uart_history_array_put(data) {
uart_history_array.push(data);
}
let uart_indicatior = undefined;
let uart_terminal = undefined;
let web_socket = undefined;
function receive_uart(data) {
uart_indicatior.activate();
uart_history_array_put(data);
if (uart_terminal != undefined) {
uart_terminal.push(data);
}
}
function uart_on_mount() {
let uart_data = uart_history_array_get();
for (let i = 0; i < uart_data.length; i++) {
uart_terminal.push(uart_data[i]);
}
}
function uart_send(data) {
web_socket.send(data);
}
const tabs = ["WiFi", "SYS", "PS", "UART"];
// ugly hack for terminal height on mobile devices
const appHeight = () => {
const doc = document.documentElement;
doc.style.setProperty("--app-height", `${window.innerHeight}px`);
};
onMount(() => {
appHeight();
window.addEventListener("resize", appHeight);
window.addEventListener("orientationchange", function () {
appHeight();
});
});
</script>
<main>
<tabs>
<tab
class:selected={current_tab == "WiFi"}
on:click={() => {
change_tab("WiFi");
}}
>
WiFi
</tab>
<tab
class:selected={current_tab == "SYS"}
on:click={() => {
change_tab("SYS");
}}
>
SYS
</tab>
<tab
class:selected={current_tab == "PS"}
on:click={() => {
change_tab("PS");
}}
>
PS
</tab>
{#each tabs as tab}
<tab
class:selected={current_tab == tab}
on:click={() => {
change_tab(tab);
}}
on:keypress={() => {
change_tab(tab);
}}
>
{tab}
</tab>
{/each}
</tabs>
<tabs-content>
{#if current_tab == "WiFi"}
<tabs-content class:uart-terminal={current_tab == tabs[3]}>
{#if current_tab == tabs[0]}
<tab-content>
<div class="grid">
{#await api_get(server + "/api/v1/wifi/get_credentials")}
<div class="value-name">Mode:</div>
<div class="value"><Spinner /></div>
<div class="value-name splitter">STA</div>
<div class="value mobile-hidden">(join another network)</div>
<div class="value-name">SSID:</div>
<div class="value"><Spinner /></div>
<div class="value-name">Pass:</div>
<div class="value"><Spinner /></div>
<div class="value-name splitter">AP</div>
<div class="value mobile-hidden">(own access point)</div>
<div class="value-name">SSID:</div>
<div class="value"><Spinner /></div>
<div class="value-name">Pass:</div>
<div>class="value"<Spinner /></div>
<div class="value-name">Hostname:</div>
<div class="value"><Spinner /></div>
<div class="value-name">USB mode:</div>
<div class="value"><Spinner /></div>
{:then json}
<div class="value-name">Mode:</div>
<div class="value">
<Select
bind:this={mode_select}
items={[
{ text: "STA (join another network)", value: "STA" },
{ text: "AP (own access point)", value: "AP" },
{ text: "Disabled (do not use WiFi)", value: "Disabled" },
]}
value={json.wifi_mode}
/>
</div>
<div class="value-name splitter">STA</div>
<div class="value mobile-hidden">(join another network)</div>
<div class="value-name">SSID:</div>
<div class="value">
<Input
value={json.sta_ssid}
bind:this={sta_ssid_input}
/><ButtonInline value="+" on:click={popup_select_net.show} />
</div>
<div class="value-name">Pass:</div>
<div class="value">
<Input value={json.sta_pass} bind:this={sta_pass_input} />
</div>
<div class="value-name splitter">AP</div>
<div class="value mobile-hidden">(own access point)</div>
<div class="value-name">SSID:</div>
<div class="value">
<Input value={json.ap_ssid} bind:this={ap_ssid_input} />
</div>
<div class="value-name">Pass:</div>
<div class="value">
<Input value={json.ap_pass} bind:this={ap_pass_input} />
</div>
<div class="value-name">Hostname:</div>
<div class="value">
<Input value={json.hostname} bind:this={hostname_input} />
</div>
<div class="value-name">USB mode:</div>
<div class="value">
<Select
bind:this={usb_mode_select}
items={[
{ text: "BlackMagicProbe", value: "BM" },
{ text: "DapLink", value: "DAP" },
]}
value={json.usb_mode}
/>
</div>
{:catch error}
<error>{error.message}</error>
{/await}
</div>
<div style="margin-top: 10px;">
<Button value="SAVE" on:click={save_settings} />
<Button value="REBOOT" on:click={reboot_board} />
</div>
<TabWiFi />
</tab-content>
{/if}
{#if current_tab == "SYS"}
{:else if current_tab == tabs[1]}
<tab-content>
<div class="grid">
{#await api_get(server + "/api/v1/system/info")}
<div class="value-name">IP:</div>
<div class="value"><Spinner /></div>
{:then json}
<div class="value-name">IP:</div>
<div class="value">{print_ip(json.ip)}</div>
<div class="value-name">Mac:</div>
<div class="value">{print_mac(json.mac)}</div>
<div class="value-name">IDF ver:</div>
<div class="value">{json.idf_version}</div>
<div class="value-name">Model:</div>
<div class="value">
{json.model}.{json.revision}
{json.cores}-core
</div>
<div class="value-name">Min free:</div>
<div class="value">{json.heap.minimum_free_bytes}</div>
<div class="value-name">Free:</div>
<div class="value">{json.heap.total_free_bytes}</div>
<div class="value-name">Alloc:</div>
<div class="value">{json.heap.total_allocated_bytes}</div>
<div class="value-name">Max block:</div>
<div class="value">{json.heap.largest_free_block}</div>
{:catch error}
<error>{error.message}</error>
{/await}
</div>
<TabSys />
</tab-content>
{/if}
{#if current_tab == "PS"}
{:else if current_tab == tabs[2]}
<tab-content>
{#await api_get(server + "/api/v1/system/tasks")}
<span>Name</span>
<span><Spinner /></span>
{:then json}
<task-list>
<span class="mobile-hidden">Name</span>
<span class="mobile-hidden">State</span>
<span class="mobile-hidden">Handle</span>
<span class="mobile-hidden">Stack base</span>
<span class="mobile-hidden">WMRK</span>
{#each json.list.sort(function (a, b) {
return a.number - b.number;
}) as task}
<span>{task.name}</span>
<span>{task.state}</span>
<span>0x{task.handle.toString(16).toUpperCase()}</span>
<span>0x{task.stack_base.toString(16).toUpperCase()}</span>
<span>{task.watermark}</span>
{/each}
</task-list>
{:catch error}
<error>{error.message}</error>
{/await}
<TabPs />
</tab-content>
{:else if current_tab == tabs[3]}
<tab-content class="uart-terminal">
<UartTerminal
bind:this={uart_terminal}
on_mount={uart_on_mount}
send={uart_send}
/>
</tab-content>
{/if}
</tabs-content>
<Popup bind:this={popup_select_net}>
{#await api_get(server + "/api/v1/wifi/list", {})}
<div>Nets: <SpinnerBig /></div>
{:then json}
<div>Nets:</div>
{#each json.net_list as net}
<div>
<ButtonInline
style="normal"
value="[{net.ssid} {net.channel}ch {net.rssi}dBm {net.auth}]"
on:click={() => {
popup_select_net.close();
sta_ssid_input.set_value(net.ssid);
}}
/>
</div>
{/each}
{:catch error}
<error>{error.message}</error>
{/await}
</Popup>
<Popup bind:this={popup_message}>
{#if popup_message_text != ""}
{popup_message_text}
{:else}
<Spinner />
{/if}
</Popup>
<Indicator bind:this={uart_indicatior} />
<WebSocket bind:this={web_socket} receive={receive_uart} />
<Reload />
</main>
<style>
@ -346,36 +131,16 @@
user-select: none;
}
tabs {
border-bottom: 4px dashed #000;
width: 100%;
display: block;
:global(.selectable) {
-moz-user-select: text;
-o-user-select: text;
-khtml-user-select: text;
-webkit-user-select: text;
-ms-user-select: text;
user-select: text;
}
tab {
margin-right: 10px;
padding: 5px 10px;
margin-bottom: 5px;
display: inline-block;
}
tab:hover,
tab.selected:hover {
background: rgb(255, 255, 255);
color: #000000;
}
tab.selected {
background-color: black;
color: white;
}
tabs-content {
display: block;
margin-top: 10px;
}
error {
:global(error) {
padding: 5px 10px;
background-color: rgb(255, 0, 0);
color: black;
@ -406,77 +171,61 @@
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}
.grid {
display: inline-grid;
grid-template-columns: auto auto;
}
.grid > div {
margin-top: 10px;
}
.value-name {
text-align: right;
}
task-list {
display: inline-grid;
grid-template-columns: auto auto auto auto auto;
width: 100%;
}
@media (max-width: 768px) {
task-list {
grid-template-columns: auto auto auto auto;
}
task-list > span:nth-child(5n + 3) {
display: none;
}
}
@media (max-width: 600px) {
task-list {
grid-template-columns: auto auto auto;
}
task-list > span:nth-child(5n + 4) {
display: none;
}
tabs-content.uart-terminal {
height: calc(var(--app-height) - 105px);
}
@media (max-width: 520px) {
.grid {
grid-template-columns: auto;
width: 100%;
:global(.mobile-hidden) {
display: none !important;
}
main {
margin: 0;
}
.mobile-hidden {
display: none;
}
.value-name {
text-align: left;
}
.splitter {
background-color: #000;
width: 100%;
color: #ffa21d;
text-align: center;
}
task-list {
grid-template-columns: auto;
text-align: center;
}
task-list > span:nth-child(5n + 1) {
padding-top: 10px;
}
task-list > span:nth-child(5n + 5) {
border-bottom: 4px dashed #000;
tabs-content.uart-terminal {
height: calc(var(--app-height) - 85px);
}
}
tabs {
border-bottom: 4px dashed #000;
width: 100%;
display: block;
}
tab {
margin-right: 10px;
padding: 5px 10px;
margin-bottom: 5px;
display: inline-block;
}
tab:last-child {
margin-right: 0;
}
tab:hover,
tab.selected:hover {
background: rgb(255, 255, 255);
color: #000000;
}
tab.selected {
background-color: black;
color: white;
}
tabs-content {
display: block;
margin-top: 10px;
}
tab-content {
display: block;
}
tab-content.uart-terminal {
height: 100%;
}
</style>

View File

@ -0,0 +1,29 @@
<script context="module">
let server = "";
if (development_mode) {
server = "http://172.30.1.83";
}
export const api = {
server: server,
dev_mode: development_mode,
post: async function (api, data) {
const res = await fetch(this.server + api, {
method: "POST",
body: JSON.stringify(data),
});
const json = await res.json();
return json;
},
get: async function (api) {
const res = await fetch(this.server + api, {
method: "GET",
});
const json = await res.json();
return json;
},
};
</script>

View File

@ -16,7 +16,6 @@
padding: 0 5px 0 5px;
box-shadow: none;
border-radius: 0;
display: inline-block;
max-width: 100%;
}

View File

@ -0,0 +1,21 @@
<div class="grid">
<slot />
</div>
<style>
.grid {
display: inline-grid;
grid-template-columns: auto auto;
}
:global(.grid > div) {
margin-top: 10px;
}
@media (max-width: 520px) {
.grid {
grid-template-columns: auto;
width: 100%;
}
}
</style>

View File

@ -0,0 +1,35 @@
<script>
let active = false;
let timer = undefined;
export function activate() {
active = true;
if (timer != undefined) {
clearTimeout(timer);
}
timer = setTimeout(() => {
active = false;
}, 100);
}
</script>
<div class="indicatior" class:active>U</div>
<style>
.indicatior {
position: fixed;
top: 0;
right: 0;
background-color: green;
color: white;
padding: 4px;
visibility: hidden;
pointer-events: none;
}
.indicatior.active {
visibility: visible;
}
</style>

View File

@ -1,5 +1,7 @@
<script>
export let value = "";
export let type = "text";
export let input = undefined;
export function set_value(new_value) {
value = new_value;
@ -12,6 +14,9 @@
function text_input() {
this.size = this.value.length > 3 ? this.value.length : 3;
value = this.value;
if (input != undefined) {
input(value);
}
}
</script>
@ -19,9 +24,9 @@
autocorrect="off"
autocapitalize="none"
autocomplete="off"
type="text"
{type}
{value}
size={value.length > 3 ? value.length : 3}
size={(value + "").length > 3 ? (value + "").length : 3}
on:input={text_input}
/>
@ -54,7 +59,7 @@
@media (max-width: 520px) {
input {
max-width: 100%;
width: 100%;
}
}
</style>

View File

@ -14,7 +14,7 @@
<popup-wrapper>
<popup-body>
<popup-content>
<popup-close on:click={close}>X</popup-close>
<popup-close on:click={close} on:keypress={close}>X</popup-close>
<popup-border>
<slot />
</popup-border>

View File

@ -0,0 +1,15 @@
<script>
import { api } from "./Api.svelte";
import Button from "./Button.svelte";
</script>
{#if api.dev_mode}
<div style="position: fixed; bottom: 0; right: 0;">
<Button
value="RELOAD"
on:click={() => {
location.reload();
}}
/>
</div>
{/if}

View File

@ -0,0 +1,330 @@
<script>
import { onMount } from "svelte";
import parseTerminal from "./terminal.js";
import Button from "./Button.svelte";
import Popup from "./Popup.svelte";
import Spinner from "./Spinner.svelte";
import SpinnerBig from "./SpinnerBig.svelte";
import { api } from "../lib/Api.svelte";
import Grid from "./Grid.svelte";
import Value from "./Value.svelte";
import Input from "./Input.svelte";
import StringView from "stringview/StringView";
import Select from "./Select.svelte";
let bytes = new Uint8Array(0);
function cat_arrays(a, b) {
var c = new a.constructor(a.length + b.length);
c.set(a, 0);
c.set(b, a.length);
return c;
}
export const push = (data) => {
bytes = cat_arrays(bytes, data);
process_bytes();
};
export let on_mount = () => {};
export let send = () => {};
let ready = {
lines: [],
last: "",
};
const line_empty_to_br = (line) => {
if (line.trim() == "") {
return "<br>";
} else {
return line;
}
};
const process_bytes = () => {
// convert to DataView
const data_view = new DataView(
bytes.buffer,
bytes.byteOffset,
bytes.byteLength
);
const encoding = "ASCII";
const eol = "\n";
const eol_code = eol.charCodeAt(0);
// find last EOL
const last_eol = bytes.lastIndexOf(eol_code);
if (last_eol != -1) {
// decode bytes from 0 to last_eol
const decoded = StringView.getString(
data_view,
0,
last_eol,
encoding
);
// split by EOL
let lines = decoded.split(eol);
// parse and push lines
lines = lines.map((line) => parseTerminal(line));
ready.lines.push(...lines);
// remove processed bytes
bytes = bytes.subarray(last_eol + 1);
}
// decode last line
if (bytes.length > 0) {
const last_string = StringView.getString(
data_view,
0,
bytes.length,
encoding
);
ready.last = parseTerminal(last_string);
} else {
ready.last = "";
}
};
onMount(() => {
on_mount();
});
const scrollToBottom = (node) => {
const scroll = () =>
node.scroll({
top: node.scrollHeight,
behavior: "instant",
});
scroll();
return { update: scroll };
};
let popup = {
text: "",
self: null,
};
let config = {
popup: null,
bit_rate: null,
stop_bits: null,
parity: null,
data_bits: null,
};
async function config_apply() {
popup.text = "";
popup.self.show();
popup = popup;
config.popup.close();
await api
.post("/api/v1/uart/set_config", {
bit_rate: parseInt(config.bit_rate.get_value()),
stop_bits: parseInt(config.stop_bits.get_value()),
parity: parseInt(config.parity.get_value()),
data_bits: parseInt(config.data_bits.get_value()),
})
.then((json) => {
if (json.error) {
popup.text = json.error;
} else {
popup.text = "Saved!";
}
});
}
let tx = {
popup: null,
data: "",
eol: "\\r\\n",
};
async function uart_send() {
tx.popup.close();
let eol = tx.eol.replaceAll("\\r", "\r").replaceAll("\\n", "\n");
let data = tx.data + eol;
// split data to chunks of 1k bytes
let chunks = [];
while (data.length > 0) {
chunks.push(data.slice(0, 1024));
data = data.slice(1024);
}
for (let chunk of chunks) {
send(chunk);
}
}
</script>
<div class="terminal-wrapper">
<div class="terminal selectable" use:scrollToBottom={ready}>
<div class="line">
{#each ready.lines as line}
{@html line}<br />
{/each}
</div>
{#if ready.last}
<div class="line">
{@html ready.last}<span class="cursor">_</span>
</div>
{/if}
</div>
<div class="config">
<Button value="S" on:click={tx.popup.show} />
<Button value="#" on:click={config.popup.show} />
</div>
<Popup bind:this={config.popup}>
{#await api.get("/api/v1/uart/get_config", {})}
<SpinnerBig />
{:then json}
<div>UART config</div>
<Grid>
<Value name="Rate">
<Input
type="number"
value={json.bit_rate}
bind:this={config.bit_rate}
/>
</Value>
<Value name="Stop">
<Select
bind:this={config.stop_bits}
items={[
{ text: "1", value: "0" },
{ text: "1.5", value: "1" },
{ text: "2", value: "2" },
]}
value={json.stop_bits.toString()}
/>
</Value>
<Value name="Prty">
<Select
bind:this={config.parity}
items={[
{ text: "None", value: "0" },
{ text: "Odd", value: "1" },
{ text: "Even", value: "2" },
]}
value={json.parity.toString()}
/>
</Value>
<Value name="Data">
<Select
bind:this={config.data_bits}
items={[
{ text: "5", value: "5" },
{ text: "6", value: "6" },
{ text: "7", value: "7" },
{ text: "8", value: "8" },
]}
value={json.data_bits.toString()}
/>
</Value>
</Grid>
<div style="margin-top: 10px; text-align: center;">
<Button value="Save" on:click={config_apply} />
</div>
{:catch error}
<error>{error.message}</error>
{/await}
</Popup>
<Popup bind:this={popup.self}>
{#if popup.text != ""}
{popup.text}
{:else}
<Spinner />
{/if}
</Popup>
<Popup bind:this={tx.popup}>
<Grid>
<Value name="Data">
<Input value={tx.data} input={(data) => (tx.data = data)} /><br
/>
</Value>
<Value name="EOL">
<Input value={tx.eol} input={(data) => (tx.eol = data)} />
</Value>
</Grid>
<div style="margin-top: 10px; text-align: center;">
<Button value="Send" on:click={uart_send} />
</div>
</Popup>
</div>
<style>
@keyframes blink {
0% {
opacity: 1;
}
49% {
opacity: 1;
}
50% {
opacity: 0;
}
99% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.cursor {
animation: blink 1s infinite;
}
.line {
display: block;
}
.terminal-wrapper {
position: relative;
height: 100%;
}
.terminal {
height: 100%;
font-size: 18px;
overflow-y: scroll;
overflow-x: clip;
white-space: wrap;
}
.config {
position: absolute;
top: 0;
right: 0;
}
:global(.terminal.bold) {
font-weight: bold;
}
:global(.terminal.underline) {
text-decoration: underline;
}
:global(.terminal.blink) {
animation: blink 1s infinite;
}
:global(.terminal.invisible) {
display: none;
}
:global(.terminal-wrapper select) {
width: 100%;
}
</style>

View File

@ -0,0 +1,36 @@
<script>
export let name = "Name";
export let splitter = false;
export let selectable = false;
</script>
{#if !splitter}
<div class="value-name">{name}:</div>
<div class="value {selectable ? 'selectable' : ''}"><slot /></div>
{:else}
<div class="value-name splitter">{name}</div>
<div class="value mobile-hidden">&nbsp;<slot /></div>
{/if}
<style>
.value {
display: inline-flex;
}
.value-name {
text-align: right;
}
@media (max-width: 520px) {
.value-name {
text-align: left;
}
.splitter {
background-color: #000;
width: 100%;
color: #ffa21d;
text-align: center;
}
}
</style>

View File

@ -0,0 +1,67 @@
<script>
import { api } from "./Api.svelte";
import { onMount, onDestroy } from "svelte";
export let receive = () => {};
export const send = send_data;
function cleanup_server() {
let url = api.server;
if (url == "") {
url = window.location.host;
}
url = url.replaceAll("http://", "");
url = url.replaceAll("https://", "");
return url;
}
let gateway = `ws://${cleanup_server()}/api/v1/uart/websocket`;
let websocket;
function send_data(data) {
websocket.send(data);
}
function on_open(event) {}
function on_close(event) {
setTimeout(init, 1000);
}
function process(array) {
receive(array);
}
function on_message(event) {
let data = event.data;
var fileReader = new FileReader();
fileReader.onload = function (event) {
process(new Uint8Array(event.target.result));
};
if (data instanceof Blob) {
fileReader.readAsArrayBuffer(data);
}
}
function init() {
websocket = new WebSocket(gateway);
websocket.onopen = on_open;
websocket.onclose = on_close;
websocket.onmessage = on_message;
}
function destroy() {
websocket.onclose = function () {};
websocket.close();
}
onMount(() => {
init();
});
onDestroy(() => {
destroy();
});
</script>

View File

@ -0,0 +1,255 @@
const escSeq = {
"7": null,
"8": null,
"[20h": null,
"[?1h": null,
"[?3h": null,
"[?4h": null,
"[?5h": null,
"[?6h": null,
"[?7h": null,
"[?8h": null,
"[?9h": null,
"[20l": null,
"[?1l": null,
"[?2l": null,
"[?3l": null,
"[?4l": null,
"[?5l": null,
"[?6l": null,
"[?7l": null,
"[?8l": null,
"[?9l": null,
"=": null,
">": null,
"(A": null,
")A": null,
"(B": null,
")B": null,
"(0": null,
")0": null,
"(1": null,
")1": null,
"(2": null,
")2": null,
"N": null,
"O": null,
// "[m": function (state) { if (state.spanCount > 0) {state.output +=
// '</span>'; state.spanCount--;} }, "[0m": function (state) { if
// (state.spanCount > 0) {state.output += '</span>'; state.spanCount--;} },
// "[1m": { 'class': 'bold' }, "[2m": { 'class': 'light' }, "[4m": {
// 'class': 'underline' }, "[5m": { 'class': 'blink' }, "[7m": { 'class':
// 'reverse' }, "[8m": { 'class': 'invisible' },
"[;r": null,
"[A": null,
"[B": null,
"[C": null,
"[D": null,
"[H": null,
"[;H": null,
"[f": null,
"[;f": null,
"D": null,
"M": null,
"E": null,
"H": null,
"[g": null,
"[0g": null,
"[3g": null,
"#3": null,
"#4": null,
"#5": null,
"#6": null,
"[K": null,
"[0K": null,
"[1K": null,
"[2K": null,
"[J": null,
"[0J": null,
"[1J": null,
"[2J": null,
"5n": null,
"0n": null,
"3n": null,
"6n": null,
";R": null,
"[c": null,
"[0c": null,
"[?1;0c": null,
"c": null,
"#8": null,
"[2;1y": null,
"[2;2y": null,
"[2;9y": null,
"[2;10y": null,
"[0q": null,
"[1q": null,
"[2q": null,
"[3q": null,
"[4q": null
}
const modeClasses = {
'1': 'bold',
'2': 'light',
'3': 'underline',
'4': 'blink',
'5': 'reverse',
'6': 'invisible'
}
const modeStyles = {
'30': 'color: black',
'31': 'color: red',
'32': 'color: green',
'33': 'color: yellow',
'34': 'color: blue',
'35': 'color: magenta',
'36': 'color: cyan',
'37': 'color: white',
'40': 'background-color: black',
'41': 'background-color: red',
'42': 'background-color: green',
'43': 'background-color: yellow',
'44': 'background-color: blue',
'45': 'background-color: magenta',
'46': 'background-color: cyan',
'47': 'background-color: white'
}
function processModes(escapeTxt, state) {
var modes = escapeTxt.substring(1, escapeTxt.length - 1);
if (modes.length > 0) {
modes = modes.split(';');
for (let i = 0; i < modes.length; i++) {
if (modeClasses[modes[i]]) {
state
.classes
.push(modeClasses[modes[i]]);
} else if (modeStyles[modes[i]]) {
state
.styles
.push(modeStyles[modes[i]]);
} else if (modes[i] === '0') {
if (state.spanCount > 0) {
state.output += '</span>';
state.spanCount--;
}
}
}
} else {
if (state.spanCount > 0) {
state.output += '</span>';
state.spanCount--;
}
}
}
function isLetter(str) {
return str.length === 1 && str.match(/[a-z]/i);
}
function isDigit(str) {
return str.length === 1 && str.match(/[0-9]/i);
}
function processEscape(escapeTxt, state) {
if (escapeTxt.startsWith('[') && escapeTxt.endsWith('m')) {
processModes(escapeTxt, state);
} else {
const entry = escSeq[escapeTxt];
if (entry && entry !== null) {
if (typeof entry === 'object') {
if (entry.class) {
state
.classes
.push(entry.class);
}
if (entry.style) {
state
.styles
.push(entry.stye);
}
} else if (typeof entry === 'function') {
entry(state);
}
}
}
}
export default function parseTerminal(text) {
var escapeTxt = '';
var state = {
output: '',
spanCount: 0,
classes: [],
styles: []
}
for (let i = 0; i < text.length; i++) {
let character = text.charAt(i);
if (character === '\u001b') {
escapeTxt = text.charAt(++i);
if (escapeTxt === '[') {
// process until character
do {
character = text.charAt(++i)
escapeTxt += character;
} while (!isLetter(character) && i < text.length);
} else if (escapeTxt === '#') {
// process until digit
do {
character = text.charAt(++i)
escapeTxt += character;
} while (!isDigit(character) && i < text.length);
} else if (escapeTxt === '(' || escapeTxt === ')') {
// process another char
escapeTxt += text.charAt(++i);
} else {
// that's the escape
}
processEscape(escapeTxt, state);
} else {
if (state.classes.length > 0 || state.styles.length > 0) {
state.output += `<span
class="${state.classes.join(' ')}"
style="${state.styles.join(';')}
">`;
state.classes = [];
state.styles = [];
state.spanCount++;
}
if (character === ' ') {
state.output += '&nbsp;';
} else {
state.output += character;
}
}
}
// replace single &nbsp; enclosed with non &nbsp; characters with spaces
state.output = state
.output
.replace(/&nbsp;([^&]+)&nbsp;/g, ' $1 ');
// return first space to &nbsp;
if (state.output.startsWith(' ')) {
state.output = '&nbsp;' + state.output.substring(1);
}
for (let i = 0; i < state.spanCount; i++) {
state.output += '</span>';
}
return state.output;
}

View File

@ -0,0 +1,76 @@
<script>
import { api } from "../lib/Api.svelte";
import Spinner from "../lib/Spinner.svelte";
</script>
{#await api.get("/api/v1/system/tasks")}
<task-list>
<span><Spinner /></span>
<span><Spinner /></span>
<span><Spinner /></span>
<span><Spinner /></span>
<span><Spinner /></span>
</task-list>
{:then json}
<task-list>
<span class="mobile-hidden">Name</span>
<span class="mobile-hidden">State</span>
<span class="mobile-hidden">Handle</span>
<span class="mobile-hidden">Stack base</span>
<span class="mobile-hidden">WMRK</span>
{#each json.list.sort(function (a, b) {
return a.number - b.number;
}) as task}
<span>{task.name}</span>
<span>{task.state}</span>
<span>{task.handle.toString(16).toUpperCase()}</span>
<span>{task.stack_base.toString(16).toUpperCase()}</span>
<span>{task.watermark}</span>
{/each}
</task-list>
{:catch error}
<error>{error.message}</error>
{/await}
<style>
task-list {
display: inline-grid;
grid-template-columns: auto auto auto auto auto;
width: 100%;
}
@media (max-width: 768px) {
task-list {
grid-template-columns: auto auto auto auto;
}
task-list > span:nth-child(5n + 3) {
display: none;
}
}
@media (max-width: 600px) {
task-list {
grid-template-columns: auto auto auto;
}
task-list > span:nth-child(5n + 4) {
display: none;
}
}
@media (max-width: 520px) {
task-list {
grid-template-columns: auto;
text-align: center;
}
task-list > span:nth-child(5n + 1) {
padding-top: 10px;
}
task-list > span:nth-child(5n + 5) {
border-bottom: 4px dashed #000;
}
}
</style>

View File

@ -0,0 +1,85 @@
<script>
import { api } from "../lib/Api.svelte";
import Grid from "../lib/Grid.svelte";
import Spinner from "../lib/Spinner.svelte";
import Value from "../lib/Value.svelte";
function print_mac(mac_array) {
let str = "";
for (let index = 0; index < mac_array.length; index++) {
str += mac_array[index].toString(16).padStart(2, "0");
if (index < mac_array.length - 1) {
str += ":";
}
}
return str;
}
function print_ip(ip_addr) {
var byteArray = [0, 0, 0, 0];
for (var index = 0; index < byteArray.length; index++) {
var byte = ip_addr & 0xff;
byteArray[index] = byte;
ip_addr = ip_addr >> 8;
}
return byteArray.join(".");
}
</script>
<!--
"firmware_commit": "03b806d",
"firmware_branch": "zlo/2630-logs-over-wifi",
"firmware_branch_num": "157",
"firmware_version": "0.1.1",
"firmware_build_date": "22-09-2023",
-->
<Grid>
{#await api.get("/api/v1/system/info")}
<Value name="IP"><Spinner /></Value>
<Value name="Mac"><Spinner /></Value>
<Value name="IDF ver"><Spinner /></Value>
<Value name="Model"><Spinner /></Value>
<Value name="Heap" splitter={true}>info</Value>
<Value name="Min free"><Spinner /></Value>
<Value name="Free"><Spinner /></Value>
<Value name="Alloc"><Spinner /></Value>
<Value name="Max block"><Spinner /></Value>
<Value name="PSRAM" splitter={true}>info</Value>
<Value name="Min free"><Spinner /></Value>
<Value name="Free"><Spinner /></Value>
<Value name="Alloc"><Spinner /></Value>
<Value name="Max block"><Spinner /></Value>
{:then json}
<Value name="IP" selectable="true">{print_ip(json.ip)}</Value>
<Value name="Mac">{print_mac(json.mac)}</Value>
<Value name="IDF ver">{json.idf_version}</Value>
<Value name="FW commit">
{json.firmware_branch}#{json.firmware_commit}
</Value>
<Value name="FW ver">
{json.firmware_version}/{json.firmware_branch_num}/{json.firmware_build_date}
</Value>
<Value name="Model">
{json.model}.{json.revision}
{json.cores}-core
</Value>
<Value name="Heap" splitter={true}>info</Value>
<Value name="Min free">{json.heap.minimum_free_bytes}</Value>
<Value name="Free">{json.heap.total_free_bytes}</Value>
<Value name="Alloc">{json.heap.total_allocated_bytes}</Value>
<Value name="Max block">{json.heap.largest_free_block}</Value>
<Value name="PSRAM" splitter={true}>info</Value>
<Value name="Min free">{json.psram_heap.minimum_free_bytes}</Value>
<Value name="Free">{json.psram_heap.total_free_bytes}</Value>
<Value name="Alloc">{json.psram_heap.total_allocated_bytes}</Value>
<Value name="Max block">{json.psram_heap.largest_free_block}</Value>
{:catch error}
<error>{error.message}</error>
{/await}
</Grid>

View File

@ -0,0 +1,155 @@
<script>
import { api } from "../lib/Api.svelte";
import Input from "../lib/Input.svelte";
import Spinner from "../lib/Spinner.svelte";
import SpinnerBig from "../lib/SpinnerBig.svelte";
import Button from "../lib/Button.svelte";
import ButtonInline from "../lib/ButtonInline.svelte";
import Select from "../lib/Select.svelte";
import Popup from "../lib/Popup.svelte";
import Value from "../lib/Value.svelte";
import Grid from "../lib/Grid.svelte";
let mode_select;
let usb_mode_select;
let ap_ssid_input;
let ap_pass_input;
let sta_ssid_input;
let sta_pass_input;
let hostname_input;
let popup_select_net;
let popup = {
text: "",
self: null,
};
async function reboot_board() {
api.post("/api/v1/system/reboot", {});
popup.text = "Rebooted";
popup.self.show();
}
async function save_settings() {
popup.text = "";
popup.self.show();
popup = popup;
await api
.post("/api/v1/wifi/set_credentials", {
wifi_mode: mode_select.get_value(),
usb_mode: usb_mode_select.get_value(),
ap_ssid: ap_ssid_input.get_value(),
ap_pass: ap_pass_input.get_value(),
sta_ssid: sta_ssid_input.get_value(),
sta_pass: sta_pass_input.get_value(),
hostname: hostname_input.get_value(),
})
.then((json) => {
if (json.error) {
popup.text = json.error;
} else {
popup.text = "Saved!";
}
});
}
</script>
<Grid>
{#await api.get("/api/v1/wifi/get_credentials")}
<Value name="Mode"><Spinner /></Value>
<Value name="STA" splitter={true}>(join another network)</Value>
<Value name="SSID"><Spinner /></Value>
<Value name="Pass"><Spinner /></Value>
<Value name="AP" splitter={true}>(own access point)</Value>
<Value name="SSID"><Spinner /></Value>
<Value name="Pass"><Spinner /></Value>
<Value name="Hostname"><Spinner /></Value>
<Value name="USB mode"><Spinner /></Value>
{:then json}
<Value name="Mode">
<Select
bind:this={mode_select}
items={[
{ text: "STA (join another network)", value: "STA" },
{ text: "AP (own access point)", value: "AP" },
{ text: "Disabled (do not use WiFi)", value: "Disabled" },
]}
value={json.wifi_mode}
/>
</Value>
<Value name="STA" splitter={true}>(join another network)</Value>
<Value name="SSID">
<Input value={json.sta_ssid} bind:this={sta_ssid_input} />
<ButtonInline value="+" on:click={popup_select_net.show} />
</Value>
<Value name="Pass">
<Input value={json.sta_pass} bind:this={sta_pass_input} />
</Value>
<Value name="AP" splitter={true}>(own access point)</Value>
<Value name="SSID">
<Input value={json.ap_ssid} bind:this={ap_ssid_input} />
</Value>
<Value name="Pass">
<Input value={json.ap_pass} bind:this={ap_pass_input} />
</Value>
<Value name="Hostname">
<Input value={json.hostname} bind:this={hostname_input} />
</Value>
<Value name="USB mode">
<Select
bind:this={usb_mode_select}
items={[
{ text: "BlackMagicProbe", value: "BM" },
{ text: "DapLink", value: "DAP" },
]}
value={json.usb_mode}
/>
</Value>
{:catch error}
<error>{error.message}</error>
{/await}
</Grid>
<div style="margin-top: 10px;">
<Button value="SAVE" on:click={save_settings} />
<Button value="REBOOT" on:click={reboot_board} />
</div>
<Popup bind:this={popup_select_net}>
{#await api.get("/api/v1/wifi/list", {})}
<div>Nets: <SpinnerBig /></div>
{:then json}
<div>Nets:</div>
{#each json.net_list as net}
<div>
<ButtonInline
style="normal"
value="[{net.ssid} {net.channel}ch {net.rssi}dBm {net.auth}]"
on:click={() => {
popup_select_net.close();
sta_ssid_input.set_value(net.ssid);
}}
/>
</div>
{/each}
{:catch error}
<error>{error.message}</error>
{/await}
</Popup>
<Popup bind:this={popup.self}>
{#if popup.text != ""}
{popup.text}
{:else}
<Spinner />
{/if}
</Popup>

View File

@ -11,6 +11,7 @@ set(SOURCES
"network.c"
"network-http.c"
"network-gdb.c"
"network-uart.c"
"cli-uart.c"
"cli/cli.c"
"cli/cli-commands.c"
@ -76,4 +77,5 @@ message(STATUS "FW branch: ${FW_GIT_BRANCH}")
message(STATUS "FW branch num: ${FW_GIT_BRANCH_NUM}")
message(STATUS "FW version: ${FW_GIT_VERSION}")
set_property(SOURCE "cli/cli-commands-device-info.c" APPEND PROPERTY COMPILE_OPTIONS ${INFO_FLAGS})
set_property(SOURCE "cli/cli-commands-device-info.c" APPEND PROPERTY COMPILE_OPTIONS ${INFO_FLAGS})
set_property(SOURCE "network-http.c" APPEND PROPERTY COMPILE_OPTIONS ${INFO_FLAGS})

View File

@ -12,8 +12,8 @@
#include "i2c.h"
#include "network.h"
#include "network-http.h"
#include "network-uart.h"
#include "network-gdb.h"
#include "network-uart.h"
#include "factory-reset-service.h"
#include <gdb-glue.h>
@ -64,8 +64,8 @@ void app_main(void) {
nvs_init();
network_init();
network_http_server_init();
// network_uart_server_init();
network_gdb_server_init();
network_uart_server_init();
usb_init();
cli_uart_init();

View File

@ -46,7 +46,7 @@ void network_gdb_send(uint8_t* buffer, size_t size) {
}
};
void receive_and_send_to_gdb(void) {
static void receive_and_send_to_gdb(void) {
size_t rx_size = SIZE_MAX;
size_t gdb_packet_size = gdb_glue_get_packet_size();
uint8_t* buffer_rx = malloc(gdb_packet_size);

View File

@ -10,6 +10,7 @@
#include "nvs-config.h"
#include "led.h"
#include "helpers.h"
#include "usb-uart.h"
#define TAG "network-http"
#define JSON_ERROR(error_text) "{\"error\": \"" error_text "\"}"
@ -17,6 +18,12 @@
#define WIFI_SCAN_SIZE 20
static httpd_handle_t server = NULL;
typedef struct {
httpd_handle_t hd;
int fd;
} async_resp_arg;
static const char* get_auth_mode(int authmode) {
switch(authmode) {
case WIFI_AUTH_OPEN:
@ -166,16 +173,19 @@ static esp_err_t http_common_get_handler(httpd_req_t* req) {
return ESP_OK;
}
static esp_err_t system_ping_handler(httpd_req_t* req) {
static void httpd_resp_common(httpd_req_t* req) {
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_type(req, "application/json");
}
static esp_err_t system_ping_handler(httpd_req_t* req) {
httpd_resp_common(req);
httpd_resp_sendstr(req, JSON_RESULT("OK"));
return ESP_OK;
}
static esp_err_t system_tasks_handler(httpd_req_t* req) {
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_type(req, "application/json");
httpd_resp_common(req);
uint32_t task_count = uxTaskGetNumberOfTasks();
TaskStatus_t* tasks = malloc(task_count * sizeof(TaskStatus_t));
@ -230,19 +240,25 @@ static esp_err_t system_tasks_handler(httpd_req_t* req) {
}
static esp_err_t system_reboot(httpd_req_t* req) {
httpd_resp_common(req);
httpd_resp_sendstr(req, JSON_RESULT("OK"));
esp_restart();
return ESP_OK;
}
static esp_err_t system_info_get_handler(httpd_req_t* req) {
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_type(req, "application/json");
httpd_resp_common(req);
cJSON* root = cJSON_CreateObject();
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
cJSON_AddStringToObject(root, "idf_version", IDF_VER);
cJSON_AddStringToObject(root, "firmware_commit", FW_GIT_COMMIT);
cJSON_AddStringToObject(root, "firmware_branch", FW_GIT_BRANCH);
cJSON_AddStringToObject(root, "firmware_branch_num", FW_GIT_BRANCH_NUM);
cJSON_AddStringToObject(root, "firmware_version", FW_GIT_VERSION);
cJSON_AddStringToObject(root, "firmware_build_date", FW_BUILD_DATE);
switch(chip_info.model) {
case CHIP_ESP32:
cJSON_AddStringToObject(root, "model", "ESP32");
@ -266,14 +282,25 @@ static esp_err_t system_info_get_handler(httpd_req_t* req) {
cJSON_AddNumberToObject(root, "revision", chip_info.revision);
cJSON_AddNumberToObject(root, "cores", chip_info.cores);
// main heap
multi_heap_info_t info;
heap_caps_get_info(&info, MALLOC_CAP_DEFAULT);
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
cJSON* heap = cJSON_AddObjectToObject(root, "heap");
cJSON_AddNumberToObject(heap, "total_free_bytes", info.total_free_bytes);
cJSON_AddNumberToObject(heap, "total_allocated_bytes", info.total_allocated_bytes);
cJSON_AddNumberToObject(heap, "largest_free_block", info.largest_free_block);
cJSON_AddNumberToObject(heap, "minimum_free_bytes", info.minimum_free_bytes);
// psram heap
heap_caps_get_info(&info, MALLOC_CAP_SPIRAM);
cJSON* psram_heap = cJSON_AddObjectToObject(root, "psram_heap");
cJSON_AddNumberToObject(psram_heap, "total_free_bytes", info.total_free_bytes);
cJSON_AddNumberToObject(psram_heap, "total_allocated_bytes", info.total_allocated_bytes);
cJSON_AddNumberToObject(psram_heap, "largest_free_block", info.largest_free_block);
cJSON_AddNumberToObject(psram_heap, "minimum_free_bytes", info.minimum_free_bytes);
// ip addr
cJSON_AddNumberToObject(root, "ip", network_get_ip());
@ -297,8 +324,7 @@ static esp_err_t system_info_get_handler(httpd_req_t* req) {
}
static esp_err_t wifi_get_credentials_handler(httpd_req_t* req) {
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_set_type(req, "application/json");
httpd_resp_common(req);
cJSON* root = cJSON_CreateObject();
mstring_t* ap_ssid = mstring_alloc();
@ -357,7 +383,8 @@ static esp_err_t wifi_get_credentials_handler(httpd_req_t* req) {
}
static esp_err_t wifi_set_credentials_handler(httpd_req_t* req) {
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_common(req);
int total_length = req->content_len;
int cur_len = 0;
char* buffer = malloc(256);
@ -370,7 +397,6 @@ static esp_err_t wifi_set_credentials_handler(httpd_req_t* req) {
mstring_t* hostname = mstring_alloc();
const char* error_text = JSON_ERROR("unknown error");
int received = 0;
httpd_resp_set_type(req, "application/json");
if(total_length >= 256) {
error_text = JSON_ERROR("request too long");
@ -532,7 +558,8 @@ err_fail:
}
static esp_err_t wifi_list_get_handler(httpd_req_t* req) {
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_common(req);
uint16_t number = WIFI_SCAN_SIZE;
wifi_ap_record_t* ap_info = calloc(WIFI_SCAN_SIZE, sizeof(wifi_ap_record_t));
uint16_t ap_count = 0;
@ -542,7 +569,6 @@ static esp_err_t wifi_list_get_handler(httpd_req_t* req) {
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count));
httpd_resp_set_type(req, "application/json");
cJSON* root = cJSON_CreateObject();
cJSON* array = cJSON_AddArrayToObject(root, "net_list");
@ -571,7 +597,8 @@ static esp_err_t wifi_list_get_handler(httpd_req_t* req) {
}
static esp_err_t gpio_led_set_handler(httpd_req_t* req) {
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
httpd_resp_common(req);
int total_length = req->content_len;
int current_length = 0;
char* buffer = malloc(256);
@ -580,7 +607,6 @@ static esp_err_t gpio_led_set_handler(httpd_req_t* req) {
int32_t led_red = -1;
int32_t led_green = -1;
int32_t led_blue = -1;
httpd_resp_set_type(req, "application/json");
if(total_length >= 256) {
error_text = JSON_ERROR("request too long");
@ -641,6 +667,202 @@ err_fail:
return ESP_FAIL;
}
/*************** UART ***************/
#include <stream_buffer.h>
#define WEBSOCKET_STREAM_BUFFER_SIZE_BYTES 512 * 1024
static uint8_t websocket_stream_storage[WEBSOCKET_STREAM_BUFFER_SIZE_BYTES + 1] EXT_RAM_ATTR;
static StaticStreamBuffer_t websocket_stream_buffer_struct;
static StreamBufferHandle_t websocket_stream = NULL;
void network_http_uart_write_data(uint8_t* data, size_t size) {
if(websocket_stream == NULL) {
return;
}
size_t written = xStreamBufferSend(websocket_stream, data, size, 0);
(void)written;
}
static void websocket_read_task(void* pvParameters) {
const size_t websocket_buffer_size = 4096;
uint8_t* buffer = malloc(websocket_buffer_size);
httpd_ws_frame_t ws_pkt;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
while(true) {
size_t read =
xStreamBufferReceive(websocket_stream, buffer, websocket_buffer_size, portMAX_DELAY);
if(read == 0) continue;
ws_pkt.payload = buffer;
ws_pkt.len = read;
static size_t max_clients = CONFIG_LWIP_MAX_LISTENING_TCP;
size_t fds = max_clients;
int client_fds[max_clients];
esp_err_t ret = httpd_get_client_list(server, &fds, client_fds);
if(ret != ESP_OK) {
return;
}
for(int i = 0; i < fds; i++) {
int client_info = httpd_ws_get_fd_info(server, client_fds[i]);
if(client_info == HTTPD_WS_CLIENT_WEBSOCKET) {
httpd_ws_send_frame_async(server, client_fds[i], &ws_pkt);
}
}
}
}
static esp_err_t uart_get_config_handler(httpd_req_t* req) {
httpd_resp_common(req);
cJSON* root = cJSON_CreateObject();
UsbUartConfig config = usb_uart_get_line_coding();
cJSON_AddNumberToObject(root, "bit_rate", config.bit_rate);
cJSON_AddNumberToObject(root, "stop_bits", config.stop_bits);
cJSON_AddNumberToObject(root, "parity", config.parity);
cJSON_AddNumberToObject(root, "data_bits", config.data_bits);
const char* json_text = cJSON_Print(root);
httpd_resp_sendstr(req, json_text);
free((void*)json_text);
cJSON_Delete(root);
return ESP_OK;
}
static esp_err_t uart_set_config_handler(httpd_req_t* req) {
httpd_resp_common(req);
int total_length = req->content_len;
int current_length = 0;
char* buffer = malloc(256);
const char* error_text = JSON_ERROR("unknown error");
int received = 0;
uint32_t uart_bit_rate = 0;
uint8_t uart_stop_bits = 0;
uint8_t uart_parity = 0;
uint8_t uart_data_bits = 0;
bool error = false;
if(total_length >= 256) {
error_text = JSON_ERROR("request too long");
goto err_fail;
}
while(current_length < total_length) {
received = httpd_req_recv(req, buffer + current_length, total_length);
if(received <= 0) {
error_text = JSON_ERROR("cannot receive request data");
goto err_fail;
}
current_length += received;
}
buffer[total_length] = '\0';
cJSON* root = cJSON_Parse(buffer);
cJSON* element;
element = cJSON_GetObjectItem(root, "bit_rate");
if(element != NULL && element->type == cJSON_Number) {
uart_bit_rate = element->valuedouble;
} else {
error = true;
}
element = cJSON_GetObjectItem(root, "stop_bits");
if(element != NULL && element->type == cJSON_Number) {
uart_stop_bits = element->valuedouble;
} else {
error = true;
}
element = cJSON_GetObjectItem(root, "parity");
if(element != NULL && element->type == cJSON_Number) {
uart_parity = element->valuedouble;
} else {
error = true;
}
element = cJSON_GetObjectItem(root, "data_bits");
if(element != NULL && element->type == cJSON_Number) {
uart_data_bits = element->valuedouble;
} else {
error = true;
}
cJSON_Delete(root);
if(error) {
error_text = JSON_ERROR("expected [bit_rate], [stop_bits], [parity], [data_bits]");
goto err_fail;
}
UsbUartConfig config = {
.bit_rate = uart_bit_rate,
.stop_bits = uart_stop_bits,
.parity = uart_parity,
.data_bits = uart_data_bits,
};
usb_uart_set_line_coding(config);
httpd_resp_sendstr(req, JSON_RESULT("OK"));
free(buffer);
return ESP_OK;
err_fail:
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, error_text);
free(buffer);
return ESP_FAIL;
}
static esp_err_t uart_websocket_handler(httpd_req_t* req) {
if(req->method == HTTP_GET) {
ESP_LOGI(TAG, "Handshake done, the new connection was opened");
return ESP_OK;
}
httpd_ws_frame_t ws_pkt;
uint8_t* buf = NULL;
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
return ret;
}
if(ws_pkt.len) {
buf = calloc(1, ws_pkt.len + 1);
if(buf == NULL) {
ESP_LOGE(TAG, "Failed to calloc memory for buf");
return ESP_ERR_NO_MEM;
}
ws_pkt.payload = buf;
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
if(ret != ESP_OK) {
ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
free(buf);
return ret;
}
usb_uart_write(ws_pkt.payload, ws_pkt.len);
}
if(buf) {
free(buf);
}
return ESP_OK;
}
const httpd_uri_t uri_handlers[] = {
/*************** SYSTEM ***************/
@ -648,56 +870,97 @@ const httpd_uri_t uri_handlers[] = {
{.uri = "/api/v1/system/ping",
.method = HTTP_GET,
.handler = system_ping_handler,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
{.uri = "/api/v1/system/tasks",
.method = HTTP_GET,
.handler = system_tasks_handler,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
{.uri = "/api/v1/system/info",
.method = HTTP_GET,
.handler = system_info_get_handler,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
{.uri = "/api/v1/system/reboot",
.method = HTTP_POST,
.handler = system_reboot,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
/*************** GPIO ***************/
{.uri = "/api/v1/gpio/led",
.method = HTTP_POST,
.handler = gpio_led_set_handler,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
/*************** WIFI ***************/
{.uri = "/api/v1/wifi/list",
.method = HTTP_GET,
.handler = wifi_list_get_handler,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
{.uri = "/api/v1/wifi/set_credentials",
.method = HTTP_POST,
.handler = wifi_set_credentials_handler,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
{.uri = "/api/v1/wifi/get_credentials",
.method = HTTP_GET,
.handler = wifi_get_credentials_handler,
.user_ctx = NULL},
.user_ctx = NULL,
.is_websocket = false},
/*************** UART ***************/
{.uri = "/api/v1/uart/get_config",
.method = HTTP_GET,
.handler = uart_get_config_handler,
.user_ctx = NULL,
.is_websocket = false},
{.uri = "/api/v1/uart/set_config",
.method = HTTP_POST,
.handler = uart_set_config_handler,
.user_ctx = NULL,
.is_websocket = false},
{.uri = "/api/v1/uart/websocket",
.method = HTTP_GET,
.handler = uart_websocket_handler,
.user_ctx = NULL,
.is_websocket = true},
/*************** HTTP ***************/
{.uri = "/*", .method = HTTP_GET, .handler = http_common_get_handler, .user_ctx = NULL},
{.uri = "/*",
.method = HTTP_GET,
.handler = http_common_get_handler,
.user_ctx = NULL,
.is_websocket = false},
};
void network_http_server_init(void) {
ESP_LOGI(TAG, "init rest server");
ESP_LOGI(TAG, "init http server");
{
websocket_stream = xStreamBufferCreateStatic(
WEBSOCKET_STREAM_BUFFER_SIZE_BYTES,
1,
websocket_stream_storage,
&websocket_stream_buffer_struct);
xTaskCreate(websocket_read_task, "websocket_read_task", 4096, NULL, 5, NULL);
}
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.max_uri_handlers = COUNT_OF(uri_handlers);
config.uri_match_fn = httpd_uri_match_wildcard;

View File

@ -11,4 +11,6 @@
/**
* Start HTTP server
*/
void network_http_server_init(void);
void network_http_server_init(void);
void network_http_uart_write_data(uint8_t* data, size_t size);

157
main/network-uart.c Normal file
View File

@ -0,0 +1,157 @@
#include <string.h>
#include <sys/param.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/stream_buffer.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_log.h>
#include <nvs_flash.h>
#include <esp_netif.h>
#include <lwip/err.h>
#include <lwip/sockets.h>
#include <lwip/sys.h>
#include <lwip/netdb.h>
#include "led.h"
#include "usb.h"
#include "delay.h"
#include "network-uart.h"
#include "usb-uart.h"
#define PORT 3456
#define KEEPALIVE_IDLE 5
#define KEEPALIVE_INTERVAL 5
#define KEEPALIVE_COUNT 3
#define TAG "network-uart"
typedef struct {
bool connected;
int socket_id;
} NetworkUART;
static NetworkUART network_uart;
bool network_uart_connected(void) {
return network_uart.connected;
}
void network_uart_send(uint8_t* buffer, size_t size) {
int to_write = size;
while(to_write > 0) {
int written = send(network_uart.socket_id, buffer + (size - to_write), to_write, 0);
to_write -= written;
}
};
static void receive_and_send_to_uart(void) {
size_t rx_size = SIZE_MAX;
const size_t data_size = 1024;
uint8_t* buffer_rx = malloc(data_size);
do {
rx_size = recv(network_uart.socket_id, buffer_rx, data_size, 0);
if(rx_size > 0) {
usb_uart_write(buffer_rx, rx_size);
}
} while(rx_size > 0);
free(buffer_rx);
}
static void network_uart_server_task(void* pvParameters) {
char addr_str[128];
int addr_family = (int)pvParameters;
int ip_protocol = 0;
int keepAlive = 1;
int keepIdle = KEEPALIVE_IDLE;
int keepInterval = KEEPALIVE_INTERVAL;
int keepCount = KEEPALIVE_COUNT;
network_uart.connected = false;
struct sockaddr_storage dest_addr;
if(addr_family == AF_INET) {
struct sockaddr_in* dest_addr_ip4 = (struct sockaddr_in*)&dest_addr;
dest_addr_ip4->sin_addr.s_addr = htonl(INADDR_ANY);
dest_addr_ip4->sin_family = AF_INET;
dest_addr_ip4->sin_port = htons(PORT);
ip_protocol = IPPROTO_IP;
}
int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
if(listen_sock < 0) {
ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
vTaskDelete(NULL);
return;
}
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ESP_LOGI(TAG, "Socket created");
int err = bind(listen_sock, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
if(err != 0) {
ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
ESP_LOGE(TAG, "IPPROTO: %d", addr_family);
goto CLEAN_UP;
}
ESP_LOGI(TAG, "Socket bound, port %d", PORT);
err = listen(listen_sock, 1);
if(err != 0) {
ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno);
goto CLEAN_UP;
}
while(1) {
ESP_LOGI(TAG, "Socket listening");
struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
socklen_t addr_len = sizeof(source_addr);
int sock = accept(listen_sock, (struct sockaddr*)&source_addr, &addr_len);
if(sock < 0) {
ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
break;
}
// Set tcp keepalive option
setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(int));
// setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &keepIdle, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPIDLE, &keepIdle, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPINTVL, &keepInterval, sizeof(int));
setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, &keepCount, sizeof(int));
// Convert ip address to string
if(source_addr.ss_family == PF_INET) {
inet_ntoa_r(
((struct sockaddr_in*)&source_addr)->sin_addr, addr_str, sizeof(addr_str) - 1);
}
ESP_LOGI(TAG, "Socket accepted ip address: %s", addr_str);
network_uart.socket_id = sock;
network_uart.connected = true;
receive_and_send_to_uart();
network_uart.connected = false;
network_uart.socket_id = -1;
shutdown(sock, 0);
close(sock);
}
CLEAN_UP:
close(listen_sock);
vTaskDelete(NULL);
}
void network_uart_server_init(void) {
network_uart.connected = false;
network_uart.socket_id = -1;
esp_wifi_set_ps(WIFI_PS_NONE);
xTaskCreate(network_uart_server_task, "network_uart_server", 4096, (void*)AF_INET, 5, NULL);
}

View File

@ -1,14 +1,20 @@
/**
* @file network-uart.h
* @author Sergey Gavrilov (who.just.the.doctor@gmail.com)
* @version 1.0
* @date 2021-11-23
*
* Uart server API
*/
#pragma once
#include <stdint.h>
/**
* Start UART server
* Start uart server
*/
void network_uart_server_init(void);
void network_uart_server_init(void);
/**
* Checks if someone is connected to the uart port
* @return bool
*/
bool network_uart_connected(void);
/**
* Send data
* @param buffer data
* @param size data size
*/
void network_uart_send(uint8_t* buffer, size_t size);

View File

@ -7,7 +7,7 @@
extern void esp_log_impl_lock(void);
extern void esp_log_impl_unlock(void);
#define LOG_BUFFER_SIZE (128)
#define LOG_BUFFER_SIZE (256)
static SoftUart* log_uart = NULL;
static char log_buffer[LOG_BUFFER_SIZE];

View File

@ -5,16 +5,18 @@
#include <esp_log.h>
#include "usb.h"
#include "usb-uart.h"
#include "network-uart.h"
#define USB_UART_PORT_NUM UART_NUM_0
#define USB_UART_TXD_PIN (43)
#define USB_UART_RXD_PIN (44)
#define USB_UART_BAUD_RATE (115200)
#define USB_UART_BUF_SIZE (128)
#define USB_UART_TX_BUF_SIZE (64)
#define USB_UART_RX_BUF_SIZE (64)
#define USB_UART_BAUD_RATE (230400)
#define USB_UART_RX_BUF_SIZE (1024)
static StreamBufferHandle_t uart_rx_stream;
#define UART_RX_STREAM_BUFFER_SIZE_BYTES 1024 * 1024
static uint8_t uart_rx_stream_storage[UART_RX_STREAM_BUFFER_SIZE_BYTES + 1] EXT_RAM_ATTR;
static StaticStreamBuffer_t uart_rx_stream_buffer_struct;
static StreamBufferHandle_t uart_rx_stream = NULL;
static void usb_uart_rx_isr(void* context);
static void usb_uart_rx_task(void* pvParameters);
@ -24,7 +26,8 @@ static const char* TAG = "usb-uart";
void usb_uart_init() {
ESP_LOGI(TAG, "init");
uart_rx_stream = xStreamBufferCreate(USB_UART_BUF_SIZE * 4, 1);
uart_rx_stream = xStreamBufferCreateStatic(
UART_RX_STREAM_BUFFER_SIZE_BYTES, 1, uart_rx_stream_storage, &uart_rx_stream_buffer_struct);
xTaskCreate(usb_uart_rx_task, "usb_uart_rx", 4096, NULL, 5, NULL);
@ -52,18 +55,14 @@ void usb_uart_set_line_state(bool dtr, bool rts) {
// do nothing, we don't have rts and dtr pins
}
void usb_uart_set_line_coding(
uint32_t bit_rate,
uint8_t stop_bits,
uint8_t parity,
uint8_t data_bits) {
simple_uart_set_baud_rate(USB_UART_PORT_NUM, bit_rate);
void usb_uart_set_line_coding(UsbUartConfig config) {
simple_uart_set_baud_rate(USB_UART_PORT_NUM, config.bit_rate);
// cdc.h
// 0: 1 stop bit
// 1: 1.5 stop bits
// 2: 2 stop bits
switch(stop_bits) {
switch(config.stop_bits) {
case 0:
simple_uart_set_stop_bits(USB_UART_PORT_NUM, UART_STOP_BITS_1);
break;
@ -83,7 +82,7 @@ void usb_uart_set_line_coding(
// 2: Even
// 3: Mark
// 4: Space
switch(parity) {
switch(config.parity) {
case 0:
simple_uart_set_parity(USB_UART_PORT_NUM, UART_PARITY_DISABLE);
break;
@ -99,7 +98,7 @@ void usb_uart_set_line_coding(
// cdc.h
// 5, 6, 7, 8 or 16
switch(parity) {
switch(config.parity) {
case 5:
simple_uart_set_data_bits(USB_UART_PORT_NUM, UART_DATA_5_BITS);
break;
@ -117,9 +116,67 @@ void usb_uart_set_line_coding(
}
}
UsbUartConfig usb_uart_get_line_coding() {
UsbUartConfig config = {
.bit_rate = simple_uart_get_baud_rate(USB_UART_PORT_NUM),
.stop_bits = 0,
.parity = 0,
.data_bits = 0,
};
switch(simple_uart_get_stop_bits(USB_UART_PORT_NUM)) {
case UART_STOP_BITS_1:
config.stop_bits = 0;
break;
case UART_STOP_BITS_1_5:
config.stop_bits = 1;
break;
case UART_STOP_BITS_2:
config.stop_bits = 2;
break;
default:
break;
}
switch(simple_uart_get_parity(USB_UART_PORT_NUM)) {
case UART_PARITY_DISABLE:
config.parity = 0;
break;
case UART_PARITY_ODD:
config.parity = 1;
break;
case UART_PARITY_EVEN:
config.parity = 2;
break;
default:
break;
}
switch(simple_uart_get_data_bits(USB_UART_PORT_NUM)) {
case UART_DATA_5_BITS:
config.data_bits = 5;
break;
case UART_DATA_6_BITS:
config.data_bits = 6;
break;
case UART_DATA_7_BITS:
config.data_bits = 7;
break;
case UART_DATA_8_BITS:
config.data_bits = 8;
break;
default:
break;
}
return config;
}
#include "network-http.h"
static void usb_uart_rx_task(void* pvParameters) {
uint8_t* data = malloc(USB_UART_RX_BUF_SIZE);
while(1) {
uint8_t data[USB_UART_RX_BUF_SIZE];
size_t length =
xStreamBufferReceive(uart_rx_stream, data, USB_UART_RX_BUF_SIZE, portMAX_DELAY);
@ -131,6 +188,10 @@ static void usb_uart_rx_task(void* pvParameters) {
usb_uart_tx_char(data[i], false);
}
}
network_http_uart_write_data(data, length);
if(network_uart_connected()) {
network_uart_send(data, length);
}
}
}
}

View File

@ -14,8 +14,13 @@ void usb_uart_write(const uint8_t* data, size_t data_size);
void usb_uart_set_line_state(bool dtr, bool rts);
void usb_uart_set_line_coding(
uint32_t bit_rate,
uint8_t stop_bits,
uint8_t parity,
uint8_t data_bits);
typedef struct {
uint32_t bit_rate;
uint8_t stop_bits;
uint8_t parity;
uint8_t data_bits;
} UsbUartConfig;
void usb_uart_set_line_coding(UsbUartConfig config);
UsbUartConfig usb_uart_get_line_coding();

View File

@ -75,12 +75,14 @@ static void usb_line_state_cb(bool dtr, bool rts, void* context) {
}
static void usb_set_line_coding_callback(cdc_line_coding_t const* p_line_coding, void* context) {
uint32_t bit_rate = p_line_coding->bit_rate;
uint8_t stop_bits = p_line_coding->stop_bits;
uint8_t parity = p_line_coding->parity;
uint8_t data_bits = p_line_coding->data_bits;
UsbUartConfig config = {
.bit_rate = p_line_coding->bit_rate,
.stop_bits = p_line_coding->stop_bits,
.parity = p_line_coding->parity,
.data_bits = p_line_coding->data_bits,
};
usb_uart_set_line_coding(bit_rate, stop_bits, parity, data_bits);
usb_uart_set_line_coding(config);
}
//--------------------------------------------------------------------+

View File

@ -273,8 +273,7 @@ CONFIG_ESP32S2_INSTRUCTION_CACHE_8KB=y
# CONFIG_ESP32S2_INSTRUCTION_CACHE_16KB is not set
# CONFIG_ESP32S2_INSTRUCTION_CACHE_LINE_16B is not set
CONFIG_ESP32S2_INSTRUCTION_CACHE_LINE_32B=y
CONFIG_ESP32S2_DATA_CACHE_0KB=y
# CONFIG_ESP32S2_DATA_CACHE_8KB is not set
CONFIG_ESP32S2_DATA_CACHE_8KB=y
# CONFIG_ESP32S2_DATA_CACHE_16KB is not set
# CONFIG_ESP32S2_DATA_CACHE_LINE_16B is not set
CONFIG_ESP32S2_DATA_CACHE_LINE_32B=y
@ -282,7 +281,40 @@ CONFIG_ESP32S2_DATA_CACHE_LINE_32B=y
# CONFIG_ESP32S2_DATA_CACHE_WRAP is not set
# end of Cache config
# CONFIG_ESP32S2_SPIRAM_SUPPORT is not set
CONFIG_ESP32S2_SPIRAM_SUPPORT=y
#
# SPI RAM config
#
CONFIG_SPIRAM_TYPE_AUTO=y
# CONFIG_SPIRAM_TYPE_ESPPSRAM16 is not set
# CONFIG_SPIRAM_TYPE_ESPPSRAM32 is not set
# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
CONFIG_SPIRAM_SIZE=-1
#
# PSRAM clock and cs IO for ESP32S2
#
CONFIG_DEFAULT_PSRAM_CLK_IO=30
CONFIG_DEFAULT_PSRAM_CS_IO=26
# end of PSRAM clock and cs IO for ESP32S2
# CONFIG_SPIRAM_FETCH_INSTRUCTIONS is not set
# CONFIG_SPIRAM_RODATA is not set
CONFIG_SPIRAM_SPEED_80M=y
# CONFIG_SPIRAM_SPEED_40M is not set
# CONFIG_SPIRAM_SPEED_26M is not set
# CONFIG_SPIRAM_SPEED_20M is not set
CONFIG_SPIRAM=y
CONFIG_SPIRAM_BOOT_INIT=y
# CONFIG_SPIRAM_USE_MEMMAP is not set
CONFIG_SPIRAM_USE_CAPS_ALLOC=y
# CONFIG_SPIRAM_USE_MALLOC is not set
CONFIG_SPIRAM_MEMTEST=y
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
# end of SPI RAM config
# CONFIG_ESP32S2_TRAX is not set
CONFIG_ESP32S2_TRACEMEM_RESERVE_DRAM=0x0
# CONFIG_ESP32S2_ULP_COPROC_ENABLED is not set
@ -321,6 +353,7 @@ CONFIG_ESP32S2_RTC_CLK_CAL_CYCLES=576
# Common ESP-related
#
CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
CONFIG_ESP_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y
# end of Common ESP-related
#
@ -363,7 +396,7 @@ CONFIG_HTTPD_MAX_URI_LEN=512
CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
CONFIG_HTTPD_PURGE_BUF_LEN=32
# CONFIG_HTTPD_LOG_PURGE_DATA is not set
# CONFIG_HTTPD_WS_SUPPORT is not set
CONFIG_HTTPD_WS_SUPPORT=y
# end of HTTP Server
#
@ -395,9 +428,9 @@ CONFIG_ESP32S2_UNIVERSAL_MAC_ADDRESSES=2
#
# Sleep Config
#
CONFIG_ESP_SLEEP_POWER_DOWN_FLASH=y
CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y
# CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND is not set
# CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND is not set
# CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND is not set
# end of Sleep Config
@ -512,10 +545,12 @@ CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32
# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1
CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32
CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
# CONFIG_ESP32_WIFI_CSI_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED is not set
# CONFIG_ESP32_WIFI_AMSDU_TX_ENABLED is not set
CONFIG_ESP32_WIFI_NVS_ENABLED=y
CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752
CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
@ -570,6 +605,7 @@ CONFIG_FATFS_LFN_NONE=y
CONFIG_FATFS_FS_LOCK=0
CONFIG_FATFS_TIMEOUT_MS=10000
# CONFIG_FATFS_PER_FILE_CACHE is not set
CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
# CONFIG_FATFS_USE_FASTSEEK is not set
# end of FAT Filesystem support
@ -772,6 +808,7 @@ CONFIG_LWIP_TCP_QUEUE_OOSEQ=y
CONFIG_LWIP_TCP_OVERSIZE_MSS=y
# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set
# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set
# CONFIG_LWIP_WND_SCALE is not set
CONFIG_LWIP_TCP_RTO_TIME=1500
# end of TCP
@ -847,6 +884,7 @@ CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y
# mbedTLS
#
CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y
# CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC is not set
# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
@ -1241,7 +1279,6 @@ CONFIG_ADC2_DISABLE_DAC=y
# CONFIG_EVENT_LOOP_PROFILING is not set
CONFIG_POST_EVENTS_FROM_ISR=y
CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
CONFIG_ESP_SYSTEM_PD_FLASH=y
# CONFIG_ESP32C3_LIGHTSLEEP_GPIO_RESET_WORKAROUND is not set
CONFIG_IPC_TASK_STACK_SIZE=1024
CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y