Initial commit
ESP32-S3 firmware acting as BMC for another board, controlling an ATX power supply and providing access to UART. Co-authored with GLM-5
This commit is contained in:
154
.clang-format
Normal file
154
.clang-format
Normal file
@@ -0,0 +1,154 @@
|
||||
# clang-format configuration for ESP32-S3 BMC project
|
||||
# Based on LLVM style with ESP-IDF conventions
|
||||
|
||||
BasedOnStyle: LLVM
|
||||
Language: C
|
||||
|
||||
# Indentation
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: ForIndentation
|
||||
ContinuationIndentWidth: 4
|
||||
IndentCaseLabels: false
|
||||
IndentGotoLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentExternBlock: AfterExternBlock
|
||||
IndentWrappedFunctionNames: false
|
||||
NamespaceIndentation: None
|
||||
|
||||
# Alignment
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignArrayOfStructures: Right
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveBitFields: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignConsecutiveMacros: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: Align
|
||||
AlignTrailingComments: true
|
||||
|
||||
# Line Breaking
|
||||
AllowAllArgumentsOnNextLine: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: Empty
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortEnumsOnASingleLine: true
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
AllowShortIfStatementsOnASingleLine: Never
|
||||
AllowShortLambdasOnASingleLine: All
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: None
|
||||
AlwaysBreakBeforeMultilineStrings: false
|
||||
AlwaysBreakTemplateDeclarations: MultiLine
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeConceptDeclarations: true
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakStringLiterals: true
|
||||
|
||||
# Braces
|
||||
BraceWrapping:
|
||||
AfterCaseLabel: false
|
||||
AfterClass: false
|
||||
AfterControlStatement: Never
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
BeforeLambdaBody: false
|
||||
BeforeWhile: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
SplitEmptyRecord: true
|
||||
SplitEmptyNamespace: true
|
||||
|
||||
# Spacing
|
||||
BitFieldColonSpacing: Both
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterLogicalNot: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceAroundPointerQualifiers: Default
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCaseColon: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceBeforeSquareBrackets: false
|
||||
SpaceInEmptyBlock: false
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
SpacesInAngles: Never
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInConditionalStatement: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInLineCommentPrefix:
|
||||
Minimum: 1
|
||||
Maximum: -1
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
|
||||
# Pointer/Reference Alignment
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
|
||||
# Columns
|
||||
ColumnLimit: 120
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 19
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyIndentedWhitespace: 0
|
||||
PenaltyReturnTypeOnItsOwnLine: 60
|
||||
|
||||
# Comments
|
||||
FixNamespaceComments: true
|
||||
ReflowComments: true
|
||||
|
||||
# Includes
|
||||
IncludeBlocks: Preserve
|
||||
SortIncludes: Never
|
||||
|
||||
# Macros
|
||||
AttributeMacros: []
|
||||
ForEachMacros:
|
||||
- foreach
|
||||
- Q_FOREACH
|
||||
- BOOST_FOREACH
|
||||
IfMacros:
|
||||
- KJ_IF_MAYBE
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
StatementAttributeLikeMacros:
|
||||
- Q_EMIT
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
|
||||
# Other
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
CompactNamespaces: false
|
||||
Cpp11BracedListStyle: true
|
||||
DeriveLineEnding: true
|
||||
EmptyLineAfterAccessModifier: Never
|
||||
EmptyLineBeforeAccessModifier: LogicalBlock
|
||||
InsertBraces: false
|
||||
InsertTrailingCommas: None
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MaxEmptyLinesToKeep: 1
|
||||
PackConstructorInitializers: BinPack
|
||||
QualifierAlignment: Leave
|
||||
SeparateDefinitionBlocks: Leave
|
||||
SortUsingDeclarations: true
|
||||
UseCRLF: false
|
||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
build
|
||||
managed_components
|
||||
sdkconfig
|
||||
sdkconfig.old
|
||||
52
.vscode/settings.json
vendored
Normal file
52
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
// Editor settings
|
||||
"editor.formatOnSave": true,
|
||||
"editor.defaultFormatter": "xaver.clang-format",
|
||||
"editor.tabSize": 4,
|
||||
"editor.insertSpaces": false,
|
||||
|
||||
// C/C++ extension settings
|
||||
"C_Cpp.formatting": "clangFormat",
|
||||
"C_Cpp.format.clang_format_path": "clang-format",
|
||||
"C_Cpp.format.clang_format_fallbackStyle": "LLVM",
|
||||
"C_Cpp.format.clang_format_style": "file",
|
||||
"C_Cpp.errorSquiggles": "enabled",
|
||||
"C_Cpp.intelliSenseEngine": "default",
|
||||
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
|
||||
|
||||
// Files to exclude
|
||||
"files.exclude": {
|
||||
"**/.git": true,
|
||||
"**/build": false,
|
||||
"**/managed_components": true
|
||||
},
|
||||
|
||||
// Files to watch
|
||||
"files.watcherExclude": {
|
||||
"**/.git/**": true,
|
||||
"**/build/**": false,
|
||||
"**/managed_components/**": true
|
||||
},
|
||||
|
||||
// CMake settings
|
||||
"cmake.configureOnOpen": false,
|
||||
"cmake.buildDirectory": "${workspaceFolder}/build",
|
||||
|
||||
// File associations
|
||||
"files.associations": {
|
||||
"*.h": "c",
|
||||
"*.c": "c",
|
||||
"sdkconfig.defaults": "ini",
|
||||
"Kconfig.projbuild": "kconfig"
|
||||
},
|
||||
|
||||
// Trailing whitespace
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
|
||||
// Clang-format specific
|
||||
"clang-format.executable": "clang-format",
|
||||
"clang-format.style": "file",
|
||||
"clang-format.fallbackStyle": "LLVM",
|
||||
"clang-format.assumeFilename": "main.c"
|
||||
}
|
||||
9
CMakeLists.txt
Normal file
9
CMakeLists.txt
Normal file
@@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# ESP-IDF version check
|
||||
set(IDF_TARGET "esp32s3")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp32-bmc)
|
||||
253
README.md
Normal file
253
README.md
Normal file
@@ -0,0 +1,253 @@
|
||||
# ESP32-S3 BMC Firmware
|
||||
|
||||
A Baseboard Management Controller (BMC) firmware for ESP32-S3 that provides remote management capabilities for external boards via Ethernet.
|
||||
|
||||
## Features
|
||||
|
||||
- **Ethernet Connectivity**: W5500 SPI Ethernet module support
|
||||
- **GPIO Control**: 16 GPIO pins for power control, status monitoring, and user functions
|
||||
- **Serial Console**: UART bridge with WebSocket support for remote console access
|
||||
- **Web Interface**: Modern HTML/CSS dashboard for easy management
|
||||
- **REST API**: Full REST API for programmatic control
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
- ESP32-S3 development board
|
||||
- W5500 SPI Ethernet module
|
||||
- Target board to manage
|
||||
|
||||
### Pin Connections
|
||||
|
||||
#### W5500 SPI Ethernet
|
||||
| ESP32-S3 | W5500 |
|
||||
|----------|-------|
|
||||
| GPIO 11 | MOSI |
|
||||
| GPIO 13 | MISO |
|
||||
| GPIO 12 | SCLK |
|
||||
| GPIO 10 | CS |
|
||||
| GPIO 9 | RST |
|
||||
| GPIO 14 | INT |
|
||||
|
||||
#### UART Serial
|
||||
| ESP32-S3 | Target Board |
|
||||
|----------|--------------|
|
||||
| GPIO 43 | TX |
|
||||
| GPIO 44 | RX |
|
||||
|
||||
#### BMC GPIOs
|
||||
| GPIO | Name | Description |
|
||||
|------|------|-------------|
|
||||
| 4 | POWER_ON | Power control output |
|
||||
| 5 | POWER_OFF | Power control output |
|
||||
| 6 | RESET | Reset control (active-low) |
|
||||
| 7 | STATUS_LED | Status LED |
|
||||
| 15 | PWR_GOOD | Power good input |
|
||||
| 16 | TEMP_ALERT | Temperature alert input |
|
||||
| 17 | FAN_CTRL | Fan control PWM |
|
||||
| 18-25 | USER_1-8 | User configurable GPIOs |
|
||||
| 35-38 | USER_5-8 | Input-only GPIOs |
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- ESP-IDF v5.0 or later
|
||||
- Python 3.8+
|
||||
- CMake 3.16+
|
||||
|
||||
### Setup ESP-IDF
|
||||
|
||||
```bash
|
||||
# Install ESP-IDF (if not already installed)
|
||||
git clone --recursive https://github.com/espressif/esp-idf.git
|
||||
cd esp-idf
|
||||
./install.sh esp32s3
|
||||
source export.sh
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
```bash
|
||||
# Navigate to project directory
|
||||
cd esp32-bmc
|
||||
|
||||
# Configure the project (optional)
|
||||
idf.py menuconfig
|
||||
|
||||
# Build
|
||||
idf.py build
|
||||
|
||||
# Flash to ESP32-S3
|
||||
idf.py -p /dev/ttyUSB0 flash
|
||||
|
||||
# Monitor serial output
|
||||
idf.py -p /dev/ttyUSB0 monitor
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Network Settings
|
||||
|
||||
By default, the firmware uses DHCP. To configure static IP:
|
||||
|
||||
1. Run `idf.py menuconfig`
|
||||
2. Navigate to "BMC Configuration"
|
||||
3. Disable "Use DHCP" and set static IP, netmask, and gateway
|
||||
|
||||
Or use the REST API to change network settings at runtime.
|
||||
|
||||
### GPIO Configuration
|
||||
|
||||
GPIO pins can be configured in `main/config.h`. Modify the `bmc_gpio_defaults` array to change pin assignments, names, and default modes.
|
||||
|
||||
## REST API
|
||||
|
||||
### GPIO Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/gpio` | Get all GPIO states |
|
||||
| POST | `/api/gpio/set?pin=X` | Set GPIO value (body: `{"value": 0/1}`) |
|
||||
| PUT | `/api/gpio/config?pin=X` | Configure GPIO mode (body: `{"mode": "input/output"}`) |
|
||||
|
||||
### Power Control Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/power/status` | Get power status |
|
||||
| POST | `/api/power/on` | Turn board power on |
|
||||
| POST | `/api/power/off` | Turn board power off |
|
||||
| POST | `/api/power/reset` | Reset the board |
|
||||
|
||||
### Serial Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/serial/config` | Get serial configuration |
|
||||
| PUT | `/api/serial/config` | Set serial configuration |
|
||||
| POST | `/api/serial/send` | Send data to serial |
|
||||
| GET | `/api/serial/ws` | WebSocket for serial console |
|
||||
|
||||
### System Endpoints
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/system/info` | Get system information |
|
||||
| GET | `/api/system/status` | Get BMC status |
|
||||
|
||||
## Web Interface
|
||||
|
||||
Access the web interface by navigating to `http://<ip-address>/` in your browser.
|
||||
|
||||
Features:
|
||||
- Power control panel with on/off/reset buttons
|
||||
- Real-time GPIO monitoring
|
||||
- Serial console with WebSocket support
|
||||
- System information display
|
||||
|
||||
## Example Usage
|
||||
|
||||
### Power On
|
||||
|
||||
```bash
|
||||
curl -X POST http://192.168.1.100/api/power/on
|
||||
```
|
||||
|
||||
### Set GPIO
|
||||
|
||||
```bash
|
||||
curl -X POST "http://192.168.1.100/api/gpio/set?pin=18" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"value": 1}'
|
||||
```
|
||||
|
||||
### Get System Info
|
||||
|
||||
```bash
|
||||
curl http://192.168.1.100/api/system/info
|
||||
```
|
||||
|
||||
### Serial Console via WebSocket
|
||||
|
||||
```javascript
|
||||
const ws = new WebSocket('ws://192.168.1.100/api/serial/ws');
|
||||
ws.onmessage = (event) => console.log(event.data);
|
||||
ws.send('command\n');
|
||||
```
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
esp32-bmc/
|
||||
├── CMakeLists.txt # Project CMake configuration
|
||||
├── sdkconfig.defaults # Default SDK configuration
|
||||
├── .clang-format # Code formatter configuration
|
||||
├── main/
|
||||
│ ├── CMakeLists.txt # Main component CMake
|
||||
│ ├── main.c # Application entry point
|
||||
│ ├── config.h # Pin definitions and settings
|
||||
│ ├── gpio_controller.c/h # GPIO management
|
||||
│ ├── uart_handler.c/h # UART serial communication
|
||||
│ ├── ethernet_manager.c/h # W5500 Ethernet setup
|
||||
│ ├── web_server.c/h # HTTP server and routes
|
||||
│ └── html/
|
||||
│ └── index.html # Web interface
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Code Quality Tools
|
||||
|
||||
This project uses clang-format for code formatting.
|
||||
|
||||
### clang-format
|
||||
|
||||
Format all C source files:
|
||||
|
||||
```bash
|
||||
# Format all files in-place
|
||||
find main -name "*.c" -o -name "*.h" | xargs clang-format -i
|
||||
|
||||
# Format a specific file
|
||||
clang-format -i main/web_server.c
|
||||
|
||||
# Check formatting without modifying (dry run)
|
||||
clang-format --dry-run --Werror main/web_server.c
|
||||
```
|
||||
|
||||
### VS Code Integration
|
||||
|
||||
The project includes VS Code settings for automatic formatting on save. Install the following extensions:
|
||||
- **C/C++** (ms-vscode.cpptools) - IntelliSense and debugging
|
||||
- **clang-format** (xaver.clang-format) - Code formatting
|
||||
- **ESP-IDF** (espressif.esp-idf-extension) - ESP-IDF development
|
||||
|
||||
With these extensions installed, code will be automatically formatted when you save files.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Ethernet Not Connecting
|
||||
|
||||
1. Check W5500 module connections
|
||||
2. Verify SPI pins are correct
|
||||
3. Check Ethernet cable and link lights
|
||||
4. Monitor serial output for error messages
|
||||
|
||||
### GPIO Not Working
|
||||
|
||||
1. Verify pin is not input-only (GPIO 35-38 on ESP32-S3)
|
||||
2. Check pin is not already used by Ethernet or UART
|
||||
3. Ensure proper GPIO mode is set
|
||||
|
||||
### Serial Console Issues
|
||||
|
||||
1. Verify UART TX/RX connections
|
||||
2. Check baud rate matches target board
|
||||
3. Ensure WebSocket connection is established
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! Please submit pull requests or open issues for any improvements.
|
||||
31
dependencies.lock
Normal file
31
dependencies.lock
Normal file
@@ -0,0 +1,31 @@
|
||||
dependencies:
|
||||
espressif/cjson:
|
||||
component_hash: 002c6d1872ee4c97d333938ebe107a29841cc847f9de89e676714bd2844057ea
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.7.19~1
|
||||
espressif/w5500:
|
||||
component_hash: e6e47022a1f509d1d1197652a554aea84a8cd4d99161061e187ecf4b78a7f6ca
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=6.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.1
|
||||
idf:
|
||||
source:
|
||||
type: idf
|
||||
version: 6.1.0
|
||||
direct_dependencies:
|
||||
- espressif/cjson
|
||||
- espressif/w5500
|
||||
manifest_hash: bce4dc0e6c6fb0ff282ed34e9e61e176299db42d266e76e5b467d7ae762f489a
|
||||
target: esp32s3
|
||||
version: 2.0.0
|
||||
11
main/CMakeLists.txt
Normal file
11
main/CMakeLists.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
"gpio_controller.c"
|
||||
"uart_handler.c"
|
||||
"ethernet_manager.c"
|
||||
"web_server.c"
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES esp_driver_gpio esp_driver_uart esp_driver_spi
|
||||
esp_http_server esp_eth esp_netif lwip spi_flash nvs_flash log)
|
||||
|
||||
# Embed HTML file
|
||||
target_add_binary_data(${COMPONENT_TARGET} "html/index.html" TEXT)
|
||||
122
main/config.h
Normal file
122
main/config.h
Normal file
@@ -0,0 +1,122 @@
|
||||
#ifndef BMC_CONFIG_H
|
||||
#define BMC_CONFIG_H
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/uart.h"
|
||||
|
||||
// ============================================================================
|
||||
// Ethernet Configuration (W5500 SPI)
|
||||
// ============================================================================
|
||||
#define ETH_SPI_HOST SPI2_HOST
|
||||
#define ETH_SPI_MISO_GPIO GPIO_NUM_12
|
||||
#define ETH_SPI_MOSI_GPIO GPIO_NUM_11
|
||||
#define ETH_SPI_SCLK_GPIO GPIO_NUM_13
|
||||
#define ETH_SPI_CS_GPIO GPIO_NUM_14
|
||||
#define ETH_SPI_INT_GPIO GPIO_NUM_10
|
||||
#define ETH_SPI_RST_GPIO GPIO_NUM_9
|
||||
|
||||
// MAC address (can be customized)
|
||||
#define ETH_MAC_ADDR {0x02, 0x00, 0x00, 0x12, 0x34, 0x56}
|
||||
|
||||
// Network configuration (used if DHCP fails or is disabled)
|
||||
#define BMC_USE_DHCP 1
|
||||
#define BMC_STATIC_IP "192.168.1.100"
|
||||
#define BMC_NETMASK "255.255.255.0"
|
||||
#define BMC_GATEWAY "192.168.1.1"
|
||||
|
||||
// ============================================================================
|
||||
// UART Serial Configuration
|
||||
// ============================================================================
|
||||
#define BMC_UART_NUM UART_NUM_1
|
||||
#define BMC_UART_TX_GPIO GPIO_NUM_43
|
||||
#define BMC_UART_RX_GPIO GPIO_NUM_44
|
||||
#define BMC_UART_BAUD_RATE 115200
|
||||
#define BMC_UART_BUF_SIZE 2048
|
||||
#define BMC_UART_TASK_STACK 4096
|
||||
#define BMC_UART_TASK_PRIO 5
|
||||
|
||||
// ============================================================================
|
||||
// HTTP Server Configuration
|
||||
// ============================================================================
|
||||
#define BMC_HTTP_PORT 80
|
||||
#define BMC_HTTP_MAX_CONN 4
|
||||
#define BMC_HTTP_STACK_SIZE 8192
|
||||
|
||||
// ============================================================================
|
||||
// GPIO Configuration
|
||||
// ============================================================================
|
||||
|
||||
// Number of user-configurable GPIOs
|
||||
#define BMC_GPIO_COUNT 16
|
||||
|
||||
// GPIO pin definitions with names and default modes
|
||||
typedef struct {
|
||||
gpio_num_t pin;
|
||||
const char *name;
|
||||
gpio_mode_t mode;
|
||||
int default_value;
|
||||
bool inverted; // Active-low logic
|
||||
bool is_input_only; // Some pins are input-only
|
||||
} bmc_gpio_def_t;
|
||||
|
||||
// Predefined GPIO assignments
|
||||
#define BMC_GPIO_POWER_ON GPIO_NUM_4
|
||||
#define BMC_GPIO_POWER_OFF GPIO_NUM_5
|
||||
#define BMC_GPIO_RESET GPIO_NUM_6
|
||||
#define BMC_GPIO_STATUS_LED GPIO_NUM_7
|
||||
#define BMC_GPIO_PWR_GOOD GPIO_NUM_15
|
||||
#define BMC_GPIO_TEMP_ALERT GPIO_NUM_16
|
||||
#define BMC_GPIO_FAN_CTRL GPIO_NUM_17
|
||||
#define BMC_GPIO_USER_1 GPIO_NUM_18
|
||||
#define BMC_GPIO_USER_2 GPIO_NUM_21
|
||||
#define BMC_GPIO_USER_3 GPIO_NUM_35
|
||||
#define BMC_GPIO_USER_4 GPIO_NUM_36
|
||||
#define BMC_GPIO_USER_5 GPIO_NUM_37
|
||||
#define BMC_GPIO_USER_6 GPIO_NUM_38
|
||||
#define BMC_GPIO_USER_7 GPIO_NUM_47
|
||||
#define BMC_GPIO_USER_8 GPIO_NUM_48
|
||||
|
||||
// Default GPIO configuration table
|
||||
static const bmc_gpio_def_t bmc_gpio_defaults[BMC_GPIO_COUNT] = {
|
||||
// Power control outputs
|
||||
{ BMC_GPIO_POWER_ON, "POWER_ON", GPIO_MODE_OUTPUT, 0, false, false},
|
||||
{ BMC_GPIO_POWER_OFF, "POWER_OFF", GPIO_MODE_OUTPUT, 0, false, false},
|
||||
{ BMC_GPIO_RESET, "RESET", GPIO_MODE_OUTPUT, 0, true, false}, // Active-low reset
|
||||
{BMC_GPIO_STATUS_LED, "STATUS_LED", GPIO_MODE_OUTPUT, 0, false, false},
|
||||
|
||||
// Status inputs
|
||||
{ BMC_GPIO_PWR_GOOD, "PWR_GOOD", GPIO_MODE_INPUT, 0, false, false},
|
||||
{BMC_GPIO_TEMP_ALERT, "TEMP_ALERT", GPIO_MODE_INPUT, 0, true, false}, // Active-low alert
|
||||
|
||||
// PWM output
|
||||
{ BMC_GPIO_FAN_CTRL, "FAN_CTRL", GPIO_MODE_OUTPUT, 0, false, false},
|
||||
|
||||
// User-configurable GPIOs
|
||||
{ BMC_GPIO_USER_1, "USER_1", GPIO_MODE_INPUT, 0, false, false},
|
||||
{ BMC_GPIO_USER_2, "USER_2", GPIO_MODE_INPUT, 0, false, false},
|
||||
{ BMC_GPIO_USER_3, "USER_3", GPIO_MODE_INPUT, 0, false, false},
|
||||
{ BMC_GPIO_USER_4, "USER_4", GPIO_MODE_INPUT, 0, false, false},
|
||||
{ BMC_GPIO_USER_5, "USER_5", GPIO_MODE_INPUT, 0, false, true}, // Input-only on ESP32-S3
|
||||
{ BMC_GPIO_USER_6, "USER_6", GPIO_MODE_INPUT, 0, false, true}, // Input-only on ESP32-S3
|
||||
{ BMC_GPIO_USER_7, "USER_7", GPIO_MODE_INPUT, 0, false, true}, // Input-only on ESP32-S3
|
||||
{ BMC_GPIO_USER_8, "USER_8", GPIO_MODE_INPUT, 0, false, true}, // Input-only on ESP32-S3
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Power Control Timing
|
||||
// ============================================================================
|
||||
#define BMC_POWER_ON_PULSE_MS 100
|
||||
#define BMC_POWER_OFF_PULSE_MS 5000
|
||||
#define BMC_RESET_PULSE_MS 100
|
||||
#define BMC_POWER_GOOD_DELAY_MS 2000
|
||||
|
||||
// ============================================================================
|
||||
// Logging Tags
|
||||
// ============================================================================
|
||||
#define TAG_BMC "BMC"
|
||||
#define TAG_GPIO "GPIO"
|
||||
#define TAG_UART "UART"
|
||||
#define TAG_ETH "ETH"
|
||||
#define TAG_HTTP "HTTP"
|
||||
|
||||
#endif // BMC_CONFIG_H
|
||||
362
main/ethernet_manager.c
Normal file
362
main/ethernet_manager.c
Normal file
@@ -0,0 +1,362 @@
|
||||
#include "ethernet_manager.h"
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_eth_driver.h"
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_eth_phy.h"
|
||||
#include "esp_netif.h"
|
||||
#include "driver/spi_master.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "lwip/ip_addr.h"
|
||||
#include "esp_eth_phy_w5500.h"
|
||||
#include "esp_eth_mac_w5500.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = TAG_ETH;
|
||||
|
||||
// Ethernet handles
|
||||
static esp_eth_handle_t eth_handle = NULL;
|
||||
static esp_eth_mac_t *eth_mac = NULL;
|
||||
static esp_eth_phy_t *eth_phy = NULL;
|
||||
static esp_netif_t *eth_netif = NULL;
|
||||
|
||||
// State tracking
|
||||
static bool eth_initialized = false;
|
||||
static bool eth_connected = false;
|
||||
static bool eth_has_ip = false;
|
||||
static SemaphoreHandle_t eth_mutex = NULL;
|
||||
|
||||
// Event callback
|
||||
static eth_event_callback_t event_callback = NULL;
|
||||
|
||||
// Current configuration
|
||||
static bmc_eth_config_t current_config = {
|
||||
.use_dhcp = true,
|
||||
.static_ip = BMC_STATIC_IP,
|
||||
.netmask = BMC_NETMASK,
|
||||
.gateway = BMC_GATEWAY,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Event Handler
|
||||
// ============================================================================
|
||||
|
||||
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) {
|
||||
switch (event_id) {
|
||||
case ETHERNET_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet link up");
|
||||
eth_connected = true;
|
||||
if (event_callback) {
|
||||
event_callback(ETH_EVENT_CONNECTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case ETHERNET_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet link down");
|
||||
eth_connected = false;
|
||||
eth_has_ip = false;
|
||||
if (event_callback) {
|
||||
event_callback(ETH_EVENT_DISCONNECTED);
|
||||
}
|
||||
break;
|
||||
|
||||
case ETHERNET_EVENT_START:
|
||||
ESP_LOGI(TAG, "Ethernet started");
|
||||
break;
|
||||
|
||||
case ETHERNET_EVENT_STOP:
|
||||
ESP_LOGI(TAG, "Ethernet stopped");
|
||||
break;
|
||||
|
||||
case IP_EVENT_ETH_GOT_IP: {
|
||||
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
|
||||
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
|
||||
ESP_LOGI(TAG, "Netmask: " IPSTR, IP2STR(&event->ip_info.netmask));
|
||||
ESP_LOGI(TAG, "Gateway: " IPSTR, IP2STR(&event->ip_info.gw));
|
||||
eth_has_ip = true;
|
||||
if (event_callback) {
|
||||
event_callback(ETH_EVENT_GOT_IP);
|
||||
}
|
||||
} break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// SPI Configuration for W5500
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t init_spi_for_ethernet(void) {
|
||||
spi_bus_config_t buscfg = {
|
||||
.miso_io_num = ETH_SPI_MISO_GPIO,
|
||||
.mosi_io_num = ETH_SPI_MOSI_GPIO,
|
||||
.sclk_io_num = ETH_SPI_SCLK_GPIO,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.max_transfer_sz = 4096,
|
||||
};
|
||||
|
||||
esp_err_t ret = spi_bus_initialize(ETH_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
|
||||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "Failed to initialize SPI bus: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SPI bus initialized for W5500");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Public Functions
|
||||
// ============================================================================
|
||||
|
||||
esp_err_t ethernet_manager_init(void) {
|
||||
if (eth_initialized) {
|
||||
ESP_LOGW(TAG, "Ethernet manager already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing Ethernet manager (W5500 SPI)");
|
||||
|
||||
// Create mutex
|
||||
eth_mutex = xSemaphoreCreateMutex();
|
||||
if (!eth_mutex) {
|
||||
ESP_LOGE(TAG, "Failed to create mutex");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Initialize TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
|
||||
// Create default event loop if not already created
|
||||
esp_err_t loop_ret = esp_event_loop_create_default();
|
||||
if (loop_ret != ESP_OK && loop_ret != ESP_ERR_INVALID_STATE) {
|
||||
ESP_LOGE(TAG, "Failed to create event loop: %s", esp_err_to_name(loop_ret));
|
||||
return loop_ret;
|
||||
}
|
||||
|
||||
// Create default ETH netif
|
||||
esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
eth_netif = esp_netif_new(&netif_cfg);
|
||||
|
||||
// Register event handlers
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, ð_event_handler, NULL));
|
||||
|
||||
// Initialize SPI bus
|
||||
esp_err_t ret = init_spi_for_ethernet();
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Configure W5500 MAC address
|
||||
uint8_t mac_addr[6] = ETH_MAC_ADDR;
|
||||
|
||||
// Create SPI device configuration for W5500
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.mode = 0,
|
||||
.clock_speed_hz = 36 * 1000 * 1000, // 36 MHz
|
||||
.queue_size = 20,
|
||||
.spics_io_num = ETH_SPI_CS_GPIO,
|
||||
};
|
||||
|
||||
// Create W5500 MAC instance using SPI (new API uses spi_host and device config pointer)
|
||||
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(ETH_SPI_HOST, &devcfg);
|
||||
w5500_config.int_gpio_num = ETH_SPI_INT_GPIO;
|
||||
|
||||
eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
|
||||
mac_config.rx_task_stack_size = 4096;
|
||||
mac_config.rx_task_prio = 15;
|
||||
|
||||
eth_mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
if (!eth_mac) {
|
||||
ESP_LOGE(TAG, "Failed to create W5500 MAC");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Create W5500 PHY instance
|
||||
eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
|
||||
phy_config.phy_addr = 1; // W5500 uses address 1
|
||||
phy_config.reset_gpio_num = ETH_SPI_RST_GPIO;
|
||||
phy_config.reset_timeout_ms = 1000;
|
||||
|
||||
eth_phy = esp_eth_phy_new_w5500(&phy_config);
|
||||
if (!eth_phy) {
|
||||
ESP_LOGE(TAG, "Failed to create W5500 PHY");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Create Ethernet driver
|
||||
esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(eth_mac, eth_phy);
|
||||
ret = esp_eth_driver_install(ð_config, ð_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to install Ethernet driver: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Set MAC address
|
||||
ret = esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, mac_addr);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to set MAC address: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
// Attach Ethernet driver to TCP/IP stack
|
||||
ret = esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to attach Ethernet to netif: %s", esp_err_to_name(ret));
|
||||
esp_eth_driver_uninstall(eth_handle);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Start Ethernet driver
|
||||
ret = esp_eth_start(eth_handle);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start Ethernet: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
eth_initialized = true;
|
||||
ESP_LOGI(TAG, "Ethernet manager initialized successfully");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool ethernet_is_connected(void) {
|
||||
return eth_connected && eth_has_ip;
|
||||
}
|
||||
|
||||
esp_err_t ethernet_get_ip(char *ip_str) {
|
||||
if (!eth_initialized || !ip_str) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t ip_info;
|
||||
esp_err_t ret = esp_netif_get_ip_info(eth_netif, &ip_info);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
snprintf(ip_str, 16, IPSTR, IP2STR(&ip_info.ip));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t ethernet_get_network_info(char *ip, char *netmask, char *gateway) {
|
||||
if (!eth_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
esp_netif_ip_info_t ip_info;
|
||||
esp_err_t ret = esp_netif_get_ip_info(eth_netif, &ip_info);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
if (ip)
|
||||
snprintf(ip, 16, IPSTR, IP2STR(&ip_info.ip));
|
||||
if (netmask)
|
||||
snprintf(netmask, 16, IPSTR, IP2STR(&ip_info.netmask));
|
||||
if (gateway)
|
||||
snprintf(gateway, 16, IPSTR, IP2STR(&ip_info.gw));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t ethernet_get_mac(char *mac_str) {
|
||||
if (!eth_initialized || !mac_str) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
uint8_t mac[6];
|
||||
esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
snprintf(mac_str, 18, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t ethernet_set_static_ip(const bmc_eth_config_t *config) {
|
||||
if (!eth_initialized || !config) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(eth_mutex, portMAX_DELAY);
|
||||
|
||||
// Stop DHCP
|
||||
esp_netif_dhcpc_stop(eth_netif);
|
||||
|
||||
// Set static IP
|
||||
esp_netif_ip_info_t ip_info;
|
||||
ip_info.ip.addr = ipaddr_addr(config->static_ip);
|
||||
ip_info.netmask.addr = ipaddr_addr(config->netmask);
|
||||
ip_info.gw.addr = ipaddr_addr(config->gateway);
|
||||
|
||||
esp_err_t ret = esp_netif_set_ip_info(eth_netif, &ip_info);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
memcpy(¤t_config, config, sizeof(bmc_eth_config_t));
|
||||
current_config.use_dhcp = false;
|
||||
ESP_LOGI(TAG, "Static IP configured: %s", config->static_ip);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set static IP: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
xSemaphoreGive(eth_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t ethernet_enable_dhcp(void) {
|
||||
if (!eth_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(eth_mutex, portMAX_DELAY);
|
||||
|
||||
esp_err_t ret = esp_netif_dhcpc_start(eth_netif);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
current_config.use_dhcp = true;
|
||||
ESP_LOGI(TAG, "DHCP enabled");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to enable DHCP: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
xSemaphoreGive(eth_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ethernet_get_link_speed(void) {
|
||||
if (!eth_initialized || !eth_connected) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
eth_speed_t speed;
|
||||
esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_G_SPEED, &speed);
|
||||
|
||||
if (ret == ESP_OK) {
|
||||
return (speed == ETH_SPEED_100M) ? 100 : 10;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool ethernet_is_full_duplex(void) {
|
||||
if (!eth_initialized || !eth_connected) {
|
||||
return false;
|
||||
}
|
||||
|
||||
eth_duplex_t duplex;
|
||||
esp_err_t ret = esp_eth_ioctl(eth_handle, ETH_CMD_G_DUPLEX_MODE, &duplex);
|
||||
|
||||
return (ret == ESP_OK && duplex == ETH_DUPLEX_FULL);
|
||||
}
|
||||
|
||||
esp_err_t ethernet_register_event_callback(eth_event_callback_t callback) {
|
||||
event_callback = callback;
|
||||
return ESP_OK;
|
||||
}
|
||||
117
main/ethernet_manager.h
Normal file
117
main/ethernet_manager.h
Normal file
@@ -0,0 +1,117 @@
|
||||
#ifndef ETHERNET_MANAGER_H
|
||||
#define ETHERNET_MANAGER_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_eth.h"
|
||||
#include "esp_netif.h"
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
typedef struct {
|
||||
bool use_dhcp;
|
||||
char static_ip[16];
|
||||
char netmask[16];
|
||||
char gateway[16];
|
||||
char dns_primary[16];
|
||||
char dns_secondary[16];
|
||||
} bmc_eth_config_t;
|
||||
|
||||
// ============================================================================
|
||||
// Function Declarations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Initialize the Ethernet manager
|
||||
*
|
||||
* Sets up W5500 SPI Ethernet and network stack.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t ethernet_manager_init(void);
|
||||
|
||||
/**
|
||||
* @brief Check if Ethernet is connected
|
||||
*
|
||||
* @return true if connected, false otherwise
|
||||
*/
|
||||
bool ethernet_is_connected(void);
|
||||
|
||||
/**
|
||||
* @brief Get the current IP address
|
||||
*
|
||||
* @param ip_str Buffer to store IP address string (min 16 bytes)
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t ethernet_get_ip(char *ip_str);
|
||||
|
||||
/**
|
||||
* @brief Get network information
|
||||
*
|
||||
* @param ip IP address buffer (min 16 bytes)
|
||||
* @param netmask Netmask buffer (min 16 bytes)
|
||||
* @param gateway Gateway buffer (min 16 bytes)
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t ethernet_get_network_info(char *ip, char *netmask, char *gateway);
|
||||
|
||||
/**
|
||||
* @brief Get MAC address
|
||||
*
|
||||
* @param mac_str Buffer to store MAC address string (min 18 bytes)
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t ethernet_get_mac(char *mac_str);
|
||||
|
||||
/**
|
||||
* @brief Set static IP configuration
|
||||
*
|
||||
* @param config Network configuration
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t ethernet_set_static_ip(const bmc_eth_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Enable DHCP
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t ethernet_enable_dhcp(void);
|
||||
|
||||
/**
|
||||
* @brief Get Ethernet link speed
|
||||
*
|
||||
* @return Speed in Mbps, or 0 if not connected
|
||||
*/
|
||||
int ethernet_get_link_speed(void);
|
||||
|
||||
/**
|
||||
* @brief Get Ethernet duplex mode
|
||||
*
|
||||
* @return true for full duplex, false for half duplex
|
||||
*/
|
||||
bool ethernet_is_full_duplex(void);
|
||||
|
||||
// ============================================================================
|
||||
// Event Callbacks
|
||||
// ============================================================================
|
||||
|
||||
typedef enum {
|
||||
ETH_EVENT_CONNECTED,
|
||||
ETH_EVENT_DISCONNECTED,
|
||||
ETH_EVENT_GOT_IP,
|
||||
ETH_EVENT_LOST_IP,
|
||||
} eth_event_type_t;
|
||||
|
||||
typedef void (*eth_event_callback_t)(eth_event_type_t event);
|
||||
|
||||
/**
|
||||
* @brief Register callback for Ethernet events
|
||||
*
|
||||
* @param callback Function to call on events
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t ethernet_register_event_callback(eth_event_callback_t callback);
|
||||
|
||||
#endif // ETHERNET_MANAGER_H
|
||||
278
main/gpio_controller.c
Normal file
278
main/gpio_controller.c
Normal file
@@ -0,0 +1,278 @@
|
||||
#include "gpio_controller.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = TAG_GPIO;
|
||||
|
||||
// Runtime GPIO state storage
|
||||
static bmc_gpio_state_t gpio_states[BMC_GPIO_COUNT];
|
||||
static bool gpio_initialized = false;
|
||||
|
||||
esp_err_t gpio_controller_init(void) {
|
||||
ESP_LOGI(TAG, "Initializing GPIO controller");
|
||||
|
||||
// Initialize all GPIOs from default configuration
|
||||
for (int i = 0; i < BMC_GPIO_COUNT; i++) {
|
||||
const bmc_gpio_def_t *def = &bmc_gpio_defaults[i];
|
||||
|
||||
// Copy configuration to runtime state
|
||||
gpio_states[i].pin = def->pin;
|
||||
gpio_states[i].name = def->name;
|
||||
gpio_states[i].mode = def->mode;
|
||||
gpio_states[i].inverted = def->inverted;
|
||||
gpio_states[i].is_input_only = def->is_input_only;
|
||||
|
||||
// Skip configuration for input-only pins when mode is output
|
||||
if (def->is_input_only && def->mode == GPIO_MODE_OUTPUT) {
|
||||
gpio_states[i].mode = GPIO_MODE_INPUT;
|
||||
ESP_LOGW(TAG, "Pin %d (%s) is input-only, forcing input mode", def->pin, def->name);
|
||||
}
|
||||
|
||||
// Configure GPIO
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << def->pin),
|
||||
.mode = gpio_states[i].mode,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
|
||||
// Enable pull-up for inputs by default
|
||||
if (gpio_states[i].mode == GPIO_MODE_INPUT) {
|
||||
io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||
}
|
||||
|
||||
esp_err_t ret = gpio_config(&io_conf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure GPIO %d: %s", def->pin, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Set default value for outputs
|
||||
if (gpio_states[i].mode == GPIO_MODE_OUTPUT) {
|
||||
int value = def->default_value;
|
||||
if (def->inverted) {
|
||||
value = !value;
|
||||
}
|
||||
gpio_set_level(def->pin, value);
|
||||
gpio_states[i].value = value;
|
||||
} else {
|
||||
// Read current input value
|
||||
gpio_states[i].value = gpio_get_level(def->pin);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "GPIO %d (%s) initialized: mode=%d, value=%d, inverted=%d", def->pin, def->name,
|
||||
gpio_states[i].mode, gpio_states[i].value, gpio_states[i].inverted);
|
||||
}
|
||||
|
||||
gpio_initialized = true;
|
||||
ESP_LOGI(TAG, "GPIO controller initialized with %d pins", BMC_GPIO_COUNT);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int gpio_find_index(gpio_num_t pin) {
|
||||
for (int i = 0; i < BMC_GPIO_COUNT; i++) {
|
||||
if (gpio_states[i].pin == pin) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *gpio_get_name(gpio_num_t pin) {
|
||||
int idx = gpio_find_index(pin);
|
||||
if (idx >= 0) {
|
||||
return gpio_states[idx].name;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
esp_err_t gpio_get_all_states(bmc_gpio_state_t *states) {
|
||||
if (!gpio_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// Update input values
|
||||
for (int i = 0; i < BMC_GPIO_COUNT; i++) {
|
||||
if (gpio_states[i].mode == GPIO_MODE_INPUT || gpio_states[i].mode == GPIO_MODE_INPUT_OUTPUT) {
|
||||
gpio_states[i].value = gpio_get_level(gpio_states[i].pin);
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(states, gpio_states, sizeof(gpio_states));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t gpio_get_state(gpio_num_t pin, bmc_gpio_state_t *state) {
|
||||
int idx = gpio_find_index(pin);
|
||||
if (idx < 0) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Update input value
|
||||
if (gpio_states[idx].mode == GPIO_MODE_INPUT || gpio_states[idx].mode == GPIO_MODE_INPUT_OUTPUT) {
|
||||
gpio_states[idx].value = gpio_get_level(pin);
|
||||
}
|
||||
|
||||
memcpy(state, &gpio_states[idx], sizeof(bmc_gpio_state_t));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int gpio_read(gpio_num_t pin) {
|
||||
int idx = gpio_find_index(pin);
|
||||
if (idx < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int level = gpio_get_level(pin);
|
||||
|
||||
// Apply inverted logic for display
|
||||
if (gpio_states[idx].inverted) {
|
||||
level = !level;
|
||||
}
|
||||
|
||||
gpio_states[idx].value = level;
|
||||
return level;
|
||||
}
|
||||
|
||||
esp_err_t gpio_write(gpio_num_t pin, int value) {
|
||||
int idx = gpio_find_index(pin);
|
||||
if (idx < 0) {
|
||||
ESP_LOGW(TAG, "GPIO %d not found in configuration", pin);
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
if (gpio_states[idx].is_input_only) {
|
||||
ESP_LOGW(TAG, "GPIO %d (%s) is input-only", pin, gpio_states[idx].name);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
// Ensure GPIO is configured as output
|
||||
if (gpio_states[idx].mode != GPIO_MODE_OUTPUT && gpio_states[idx].mode != GPIO_MODE_INPUT_OUTPUT) {
|
||||
ESP_LOGW(TAG, "GPIO %d (%s) is not configured as output", pin, gpio_states[idx].name);
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
// Apply inverted logic
|
||||
int actual_value = value;
|
||||
if (gpio_states[idx].inverted) {
|
||||
actual_value = !value;
|
||||
}
|
||||
|
||||
esp_err_t ret = gpio_set_level(pin, actual_value);
|
||||
if (ret == ESP_OK) {
|
||||
gpio_states[idx].value = value;
|
||||
ESP_LOGD(TAG, "GPIO %d (%s) set to %d (actual: %d)", pin, gpio_states[idx].name, value, actual_value);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set GPIO %d: %s", pin, esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t gpio_configure(gpio_num_t pin, gpio_mode_t mode) {
|
||||
int idx = gpio_find_index(pin);
|
||||
if (idx < 0) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
// Check if trying to set input-only pin as output
|
||||
if (gpio_states[idx].is_input_only && mode == GPIO_MODE_OUTPUT) {
|
||||
ESP_LOGW(TAG, "GPIO %d (%s) is input-only, cannot set as output", pin, gpio_states[idx].name);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
gpio_config_t io_conf = {
|
||||
.pin_bit_mask = (1ULL << pin),
|
||||
.mode = mode,
|
||||
.pull_up_en = (mode == GPIO_MODE_INPUT) ? GPIO_PULLUP_ENABLE : GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
|
||||
esp_err_t ret = gpio_config(&io_conf);
|
||||
if (ret == ESP_OK) {
|
||||
gpio_states[idx].mode = mode;
|
||||
ESP_LOGI(TAG, "GPIO %d (%s) mode changed to %d", pin, gpio_states[idx].name, mode);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t gpio_toggle(gpio_num_t pin) {
|
||||
int idx = gpio_find_index(pin);
|
||||
if (idx < 0) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
int current = gpio_states[idx].value;
|
||||
return gpio_write(pin, !current);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Power Control Functions
|
||||
// ============================================================================
|
||||
|
||||
esp_err_t gpio_power_on(void) {
|
||||
ESP_LOGI(TAG, "Power ON sequence initiated");
|
||||
|
||||
// Pulse the POWER_ON signal
|
||||
gpio_write(BMC_GPIO_POWER_ON, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(BMC_POWER_ON_PULSE_MS));
|
||||
gpio_write(BMC_GPIO_POWER_ON, 0);
|
||||
|
||||
// Wait for power good signal
|
||||
vTaskDelay(pdMS_TO_TICKS(BMC_POWER_GOOD_DELAY_MS));
|
||||
|
||||
if (gpio_is_power_good()) {
|
||||
ESP_LOGI(TAG, "Power ON successful - power good detected");
|
||||
gpio_set_status_led(true);
|
||||
return ESP_OK;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Power ON completed - no power good signal");
|
||||
return ESP_OK; // Still return OK, just no confirmation
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t gpio_power_off(void) {
|
||||
ESP_LOGI(TAG, "Power OFF sequence initiated");
|
||||
|
||||
// Pulse the POWER_OFF signal
|
||||
gpio_write(BMC_GPIO_POWER_OFF, 1);
|
||||
vTaskDelay(pdMS_TO_TICKS(BMC_POWER_OFF_PULSE_MS));
|
||||
gpio_write(BMC_GPIO_POWER_OFF, 0);
|
||||
|
||||
gpio_set_status_led(false);
|
||||
ESP_LOGI(TAG, "Power OFF completed");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t gpio_reset(void) {
|
||||
ESP_LOGI(TAG, "Reset sequence initiated");
|
||||
|
||||
// Pulse the RESET signal (active-low)
|
||||
gpio_write(BMC_GPIO_RESET, 0); // Assert reset (active-low)
|
||||
vTaskDelay(pdMS_TO_TICKS(BMC_RESET_PULSE_MS));
|
||||
gpio_write(BMC_GPIO_RESET, 1); // De-assert reset
|
||||
|
||||
ESP_LOGI(TAG, "Reset completed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool gpio_is_power_good(void) {
|
||||
int idx = gpio_find_index(BMC_GPIO_PWR_GOOD);
|
||||
if (idx < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int level = gpio_get_level(BMC_GPIO_PWR_GOOD);
|
||||
return (level == 1);
|
||||
}
|
||||
|
||||
esp_err_t gpio_set_status_led(bool on) {
|
||||
return gpio_write(BMC_GPIO_STATUS_LED, on ? 1 : 0);
|
||||
}
|
||||
148
main/gpio_controller.h
Normal file
148
main/gpio_controller.h
Normal file
@@ -0,0 +1,148 @@
|
||||
#ifndef GPIO_CONTROLLER_H
|
||||
#define GPIO_CONTROLLER_H
|
||||
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_err.h"
|
||||
#include "config.h"
|
||||
|
||||
// ============================================================================
|
||||
// GPIO State Structure
|
||||
// ============================================================================
|
||||
typedef struct {
|
||||
gpio_num_t pin;
|
||||
const char *name;
|
||||
gpio_mode_t mode;
|
||||
int value;
|
||||
bool inverted;
|
||||
bool is_input_only;
|
||||
} bmc_gpio_state_t;
|
||||
|
||||
// ============================================================================
|
||||
// Function Declarations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Initialize the GPIO controller
|
||||
*
|
||||
* Sets up all GPIOs according to the default configuration.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_controller_init(void);
|
||||
|
||||
/**
|
||||
* @brief Get the current state of all GPIOs
|
||||
*
|
||||
* @param states Array to store GPIO states (must be BMC_GPIO_COUNT size)
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_get_all_states(bmc_gpio_state_t *states);
|
||||
|
||||
/**
|
||||
* @brief Get the state of a specific GPIO
|
||||
*
|
||||
* @param pin GPIO pin number
|
||||
* @param state Pointer to store the GPIO state
|
||||
* @return ESP_OK on success, ESP_ERR_NOT_FOUND if pin not in config
|
||||
*/
|
||||
esp_err_t gpio_get_state(gpio_num_t pin, bmc_gpio_state_t *state);
|
||||
|
||||
/**
|
||||
* @brief Read the value of a GPIO
|
||||
*
|
||||
* @param pin GPIO pin number
|
||||
* @return GPIO level (0 or 1), -1 on error
|
||||
*/
|
||||
int gpio_read(gpio_num_t pin);
|
||||
|
||||
/**
|
||||
* @brief Write a value to a GPIO
|
||||
*
|
||||
* Handles inverted logic automatically.
|
||||
*
|
||||
* @param pin GPIO pin number
|
||||
* @param value Value to write (0 or 1)
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_write(gpio_num_t pin, int value);
|
||||
|
||||
/**
|
||||
* @brief Configure a GPIO mode
|
||||
*
|
||||
* @param pin GPIO pin number
|
||||
* @param mode GPIO mode (INPUT, OUTPUT, etc.)
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_configure(gpio_num_t pin, gpio_mode_t mode);
|
||||
|
||||
/**
|
||||
* @brief Toggle a GPIO output
|
||||
*
|
||||
* @param pin GPIO pin number
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_toggle(gpio_num_t pin);
|
||||
|
||||
/**
|
||||
* @brief Find GPIO index by pin number
|
||||
*
|
||||
* @param pin GPIO pin number
|
||||
* @return Index in the GPIO array, or -1 if not found
|
||||
*/
|
||||
int gpio_find_index(gpio_num_t pin);
|
||||
|
||||
/**
|
||||
* @brief Get GPIO name by pin number
|
||||
*
|
||||
* @param pin GPIO pin number
|
||||
* @return GPIO name string, or NULL if not found
|
||||
*/
|
||||
const char *gpio_get_name(gpio_num_t pin);
|
||||
|
||||
// ============================================================================
|
||||
// Power Control Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Turn board power on
|
||||
*
|
||||
* Pulses the POWER_ON GPIO.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_power_on(void);
|
||||
|
||||
/**
|
||||
* @brief Turn board power off
|
||||
*
|
||||
* Pulses the POWER_OFF GPIO.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_power_off(void);
|
||||
|
||||
/**
|
||||
* @brief Reset the board
|
||||
*
|
||||
* Pulses the RESET GPIO (active-low).
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_reset(void);
|
||||
|
||||
/**
|
||||
* @brief Check if power is good
|
||||
*
|
||||
* @return true if power good signal is high, false otherwise
|
||||
*/
|
||||
bool gpio_is_power_good(void);
|
||||
|
||||
/**
|
||||
* @brief Set status LED state
|
||||
*
|
||||
* @param on true to turn on, false to turn off
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t gpio_set_status_led(bool on);
|
||||
|
||||
#endif // GPIO_CONTROLLER_H
|
||||
825
main/html/index.html
Normal file
825
main/html/index.html
Normal file
@@ -0,0 +1,825 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>VisionFive2 BMC Dashboard</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
color: #eee;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 15px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2.5em;
|
||||
margin-bottom: 10px;
|
||||
background: linear-gradient(90deg, #00d4ff, #00ff88);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 30px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.status-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #ff4444;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-dot.connected {
|
||||
background: #00ff88;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 15px;
|
||||
padding: 20px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
font-size: 1.3em;
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.card h2 .icon {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
/* Power Control */
|
||||
.power-buttons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 12px 24px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 1em;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.btn-power-on {
|
||||
background: linear-gradient(135deg, #00c853, #00e676);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.btn-power-off {
|
||||
background: linear-gradient(135deg, #ff1744, #ff5252);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background: linear-gradient(135deg, #ff9100, #ffab40);
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #fff;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.power-status {
|
||||
margin-top: 15px;
|
||||
padding: 15px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.power-indicator {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
background: #ff4444;
|
||||
}
|
||||
|
||||
.power-indicator.on {
|
||||
background: #00ff88;
|
||||
box-shadow: 0 0 20px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
/* GPIO Monitor */
|
||||
.gpio-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.gpio-item {
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.gpio-name {
|
||||
font-size: 0.8em;
|
||||
color: #888;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.gpio-pin {
|
||||
font-size: 0.9em;
|
||||
color: #00d4ff;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.gpio-value {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.gpio-led {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 50%;
|
||||
background: #333;
|
||||
border: 2px solid #555;
|
||||
}
|
||||
|
||||
.gpio-led.high {
|
||||
background: #00ff88;
|
||||
border-color: #00ff88;
|
||||
box-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
|
||||
}
|
||||
|
||||
.gpio-toggle {
|
||||
padding: 4px 8px;
|
||||
font-size: 0.8em;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.gpio-toggle:hover {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* Serial Console */
|
||||
.console-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
.console-output {
|
||||
flex: 1;
|
||||
background: #0a0a0a;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.console-output:focus {
|
||||
outline: none;
|
||||
border-color: #00d4ff;
|
||||
}
|
||||
|
||||
.console-output pre {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.console-output .input-line {
|
||||
color: #00ff88;
|
||||
}
|
||||
|
||||
.console-output .cursor {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 1em;
|
||||
background: #00ff88;
|
||||
animation: blink 1s step-end infinite;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
.console-controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.console-controls select {
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* System Info */
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
padding: 10px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 0.8em;
|
||||
color: #888;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 1em;
|
||||
color: #00d4ff;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
|
||||
/* Toast Notifications */
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.toast {
|
||||
padding: 15px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 10px;
|
||||
animation: slideIn 0.3s ease;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.toast.success {
|
||||
background: rgba(0, 255, 136, 0.2);
|
||||
border: 1px solid #00ff88;
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
background: rgba(255, 68, 68, 0.2);
|
||||
border: 1px solid #ff4444;
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from {
|
||||
transform: translateX(100%);
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
transform: translateX(0);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 600px) {
|
||||
h1 {
|
||||
font-size: 1.8em;
|
||||
}
|
||||
|
||||
.status-bar {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.power-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🖥️ VisionFive2 BMC Dashboard</h1>
|
||||
<div class="status-bar">
|
||||
<div class="status-item">
|
||||
<div class="status-dot" id="eth-status"></div>
|
||||
<span>Ethernet: <span id="eth-text">Disconnected</span></span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>IP: <span id="ip-address">-</span></span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span>UART: <span id="uart-baud">115200</span> baud</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="grid">
|
||||
<!-- Power Control Card -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">⚡</span> Power Control</h2>
|
||||
<div class="power-buttons">
|
||||
<button class="btn btn-power-on" onclick="powerOn()">Power On</button>
|
||||
<button class="btn btn-power-off" onclick="powerOff()">Power Off</button>
|
||||
<button class="btn btn-reset" onclick="reset()">Reset</button>
|
||||
</div>
|
||||
<div class="power-status">
|
||||
<div class="power-indicator" id="power-indicator"></div>
|
||||
<span>Power Status: <strong id="power-status">Unknown</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- System Info Card -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">ℹ️</span> System Info</h2>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<div class="info-label">MAC Address</div>
|
||||
<div class="info-value" id="mac-address">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Netmask</div>
|
||||
<div class="info-value" id="netmask">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Gateway</div>
|
||||
<div class="info-value" id="gateway">-</div>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<div class="info-label">Link Speed</div>
|
||||
<div class="info-value" id="link-speed">-</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<!-- GPIO Monitor Card -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">🔌</span> GPIO Monitor</h2>
|
||||
<div class="gpio-grid" id="gpio-grid">
|
||||
<!-- GPIO items will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Serial Console Card -->
|
||||
<div class="card">
|
||||
<h2><span class="icon">💻</span> Serial Console</h2>
|
||||
<div class="console-container">
|
||||
<div class="console-output" id="console-output" tabindex="0">
|
||||
<pre>Connecting to serial console...</pre>
|
||||
</div>
|
||||
<div class="console-controls">
|
||||
<label>Baud Rate:</label>
|
||||
<select id="baud-select" onchange="setBaudRate()">
|
||||
<option value="9600">9600</option>
|
||||
<option value="19200">19200</option>
|
||||
<option value="38400">38400</option>
|
||||
<option value="57600">57600</option>
|
||||
<option value="115200" selected>115200</option>
|
||||
<option value="230400">230400</option>
|
||||
<option value="460800">460800</option>
|
||||
<option value="921600">921600</option>
|
||||
</select>
|
||||
<button class="btn btn-secondary" onclick="clearConsole()">Clear</button>
|
||||
<button class="btn btn-secondary" onclick="connectWebSocket()">Reconnect</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<script>
|
||||
// WebSocket connection
|
||||
let ws = null;
|
||||
let wsConnected = false;
|
||||
|
||||
// API base URL
|
||||
const API_BASE = '';
|
||||
|
||||
// Toast notification
|
||||
function showToast(message, type = 'success') {
|
||||
const container = document.getElementById('toast-container');
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `toast ${type}`;
|
||||
toast.textContent = message;
|
||||
container.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.remove();
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// API helper
|
||||
async function apiCall(endpoint, method = 'GET', data = null) {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE}${endpoint}`, options);
|
||||
const result = await response.json();
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Unknown error');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
showToast(error.message, 'error');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Power control functions
|
||||
async function powerOn() {
|
||||
try {
|
||||
await apiCall('/api/power/on', 'POST');
|
||||
showToast('Power on sequence initiated');
|
||||
updatePowerStatus();
|
||||
} catch (error) {
|
||||
console.error('Power on failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function powerOff() {
|
||||
try {
|
||||
await apiCall('/api/power/off', 'POST');
|
||||
showToast('Power off sequence initiated');
|
||||
updatePowerStatus();
|
||||
} catch (error) {
|
||||
console.error('Power off failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
try {
|
||||
await apiCall('/api/power/reset', 'POST');
|
||||
showToast('Reset sequence initiated');
|
||||
} catch (error) {
|
||||
console.error('Reset failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function updatePowerStatus() {
|
||||
try {
|
||||
const result = await apiCall('/api/power/status');
|
||||
const indicator = document.getElementById('power-indicator');
|
||||
const status = document.getElementById('power-status');
|
||||
|
||||
if (result.data.power_good) {
|
||||
indicator.classList.add('on');
|
||||
status.textContent = 'ON';
|
||||
} else {
|
||||
indicator.classList.remove('on');
|
||||
status.textContent = 'OFF';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get power status:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// GPIO functions
|
||||
async function updateGPIOStates() {
|
||||
try {
|
||||
const result = await apiCall('/api/gpio');
|
||||
const grid = document.getElementById('gpio-grid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
result.gpios.forEach(gpio => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'gpio-item';
|
||||
|
||||
const isOutput = gpio.mode === 'output';
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="gpio-name">${gpio.name}</div>
|
||||
<div class="gpio-pin">GPIO ${gpio.pin}</div>
|
||||
<div class="gpio-value">
|
||||
<div class="gpio-led ${gpio.value ? 'high' : ''}"></div>
|
||||
<span>${gpio.value}</span>
|
||||
${isOutput ? `<button class="gpio-toggle" onclick="toggleGPIO(${gpio.pin})">Toggle</button>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
grid.appendChild(item);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get GPIO states:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleGPIO(pin) {
|
||||
try {
|
||||
// Get current state
|
||||
const result = await apiCall('/api/gpio');
|
||||
const gpio = result.gpios.find(g => g.pin === pin);
|
||||
|
||||
if (gpio) {
|
||||
const newValue = gpio.value ? 0 : 1;
|
||||
await apiCall(`/api/gpio/set?pin=${pin}`, 'POST', { value: newValue });
|
||||
updateGPIOStates();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to toggle GPIO:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Serial console functions
|
||||
let inputBuffer = '';
|
||||
let cursorVisible = true;
|
||||
|
||||
function connectWebSocket() {
|
||||
const protocol = window.location.protocol === 'https' ? 'wss' : 'ws';
|
||||
const wsUrl = `${protocol}://${window.location.host}/api/serial/ws`;
|
||||
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = () => {
|
||||
wsConnected = true;
|
||||
clearConsole();
|
||||
appendConsole('Connected to serial console\n');
|
||||
updateCursor();
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
appendConsole(event.data);
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
wsConnected = false;
|
||||
appendConsole('\nDisconnected from serial console\n');
|
||||
|
||||
// Try to reconnect after 3 seconds
|
||||
setTimeout(connectWebSocket, 3000);
|
||||
};
|
||||
|
||||
ws.onerror = (error) => {
|
||||
console.error('WebSocket error:', error);
|
||||
appendConsole('WebSocket error\n');
|
||||
};
|
||||
}
|
||||
|
||||
// Strip ANSI escape sequences from text
|
||||
function stripAnsi(text) {
|
||||
// Remove ANSI escape sequences (cursor movement, colors, etc.)
|
||||
return text.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '')
|
||||
.replace(/\x1b\][^\x07]*\x07/g, '')
|
||||
.replace(/\x1b[()][AB012]/g, '')
|
||||
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
|
||||
}
|
||||
|
||||
function appendConsole(data) {
|
||||
const output = document.getElementById('console-output');
|
||||
const pre = output.querySelector('pre') || document.createElement('pre');
|
||||
|
||||
if (!output.contains(pre)) {
|
||||
output.innerHTML = '';
|
||||
output.appendChild(pre);
|
||||
}
|
||||
|
||||
// Remove cursor before appending, then re-add
|
||||
const cursor = pre.querySelector('.cursor');
|
||||
if (cursor) cursor.remove();
|
||||
|
||||
// Strip ANSI escape sequences for cleaner display
|
||||
const cleanData = stripAnsi(data);
|
||||
pre.textContent += cleanData;
|
||||
updateCursor();
|
||||
output.scrollTop = output.scrollHeight;
|
||||
}
|
||||
|
||||
function updateCursor() {
|
||||
const output = document.getElementById('console-output');
|
||||
const pre = output.querySelector('pre');
|
||||
if (!pre) return;
|
||||
|
||||
// Remove existing cursor
|
||||
const existingCursor = pre.querySelector('.cursor');
|
||||
if (existingCursor) existingCursor.remove();
|
||||
|
||||
// Add cursor at end
|
||||
if (wsConnected) {
|
||||
const cursor = document.createElement('span');
|
||||
cursor.className = 'cursor';
|
||||
pre.appendChild(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
function sendChar(char) {
|
||||
if (wsConnected) {
|
||||
ws.send(char);
|
||||
}
|
||||
}
|
||||
|
||||
function sendBuffer() {
|
||||
if (inputBuffer && wsConnected) {
|
||||
ws.send(inputBuffer + '\n');
|
||||
inputBuffer = '';
|
||||
updateCursor();
|
||||
}
|
||||
}
|
||||
|
||||
function handleConsoleKeyDown(event) {
|
||||
if (!wsConnected) return;
|
||||
|
||||
const output = document.getElementById('console-output');
|
||||
|
||||
// Handle special keys
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
sendChar('\n');
|
||||
inputBuffer = '';
|
||||
} else if (event.key === 'Backspace') {
|
||||
event.preventDefault();
|
||||
if (inputBuffer.length > 0) {
|
||||
inputBuffer = inputBuffer.slice(0, -1);
|
||||
sendChar('\b'); // Send backspace to remote
|
||||
}
|
||||
} else if (event.key === 'Tab') {
|
||||
event.preventDefault();
|
||||
sendChar('\t');
|
||||
inputBuffer += '\t';
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
sendChar('\x1b');
|
||||
} else if (event.ctrlKey && event.key.length === 1) {
|
||||
// Handle Ctrl+C, Ctrl+D, etc.
|
||||
event.preventDefault();
|
||||
const ctrlChar = String.fromCharCode(event.key.charCodeAt(0) - 96);
|
||||
sendChar(ctrlChar);
|
||||
} else if (event.key.length === 1 && !event.ctrlKey && !event.altKey && !event.metaKey) {
|
||||
// Regular character
|
||||
event.preventDefault();
|
||||
sendChar(event.key);
|
||||
inputBuffer += event.key;
|
||||
}
|
||||
}
|
||||
|
||||
function clearConsole() {
|
||||
const output = document.getElementById('console-output');
|
||||
output.innerHTML = '<pre></pre>';
|
||||
inputBuffer = '';
|
||||
updateCursor();
|
||||
}
|
||||
|
||||
// Initialize console keyboard handling
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const output = document.getElementById('console-output');
|
||||
output.addEventListener('keydown', handleConsoleKeyDown);
|
||||
|
||||
// Focus console on click
|
||||
output.addEventListener('click', () => {
|
||||
output.focus();
|
||||
});
|
||||
});
|
||||
|
||||
async function setBaudRate() {
|
||||
const baud = document.getElementById('baud-select').value;
|
||||
try {
|
||||
await apiCall('/api/serial/config', 'PUT', { baud_rate: parseInt(baud) });
|
||||
document.getElementById('uart-baud').textContent = baud;
|
||||
showToast(`Baud rate set to ${baud}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to set baud rate:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// System info functions
|
||||
async function updateSystemInfo() {
|
||||
try {
|
||||
const result = await apiCall('/api/system/info');
|
||||
|
||||
document.getElementById('ip-address').textContent = result.data.ip || '-';
|
||||
document.getElementById('mac-address').textContent = result.data.mac || '-';
|
||||
document.getElementById('netmask').textContent = result.data.netmask || '-';
|
||||
document.getElementById('gateway').textContent = result.data.gateway || '-';
|
||||
document.getElementById('link-speed').textContent = result.data.link_speed ? `${result.data.link_speed} Mbps` : '-';
|
||||
|
||||
// Update Ethernet status
|
||||
const ethStatus = document.getElementById('eth-status');
|
||||
const ethText = document.getElementById('eth-text');
|
||||
|
||||
if (result.data.ethernet_connected) {
|
||||
ethStatus.classList.add('connected');
|
||||
ethText.textContent = 'Connected';
|
||||
} else {
|
||||
ethStatus.classList.remove('connected');
|
||||
ethText.textContent = 'Disconnected';
|
||||
}
|
||||
|
||||
document.getElementById('uart-baud').textContent = result.data.uart_baud_rate;
|
||||
} catch (error) {
|
||||
console.error('Failed to get system info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
updateSystemInfo();
|
||||
updateGPIOStates();
|
||||
updatePowerStatus();
|
||||
connectWebSocket();
|
||||
|
||||
// Periodic updates
|
||||
setInterval(updateSystemInfo, 5000);
|
||||
setInterval(updateGPIOStates, 2000);
|
||||
setInterval(updatePowerStatus, 2000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
3
main/idf_component.yml
Normal file
3
main/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
espressif/cjson: ^1.7.19~1
|
||||
espressif/w5500: ^1.0.1
|
||||
129
main/main.c
Normal file
129
main/main.c
Normal file
@@ -0,0 +1,129 @@
|
||||
#include <stdio.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_event.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "config.h"
|
||||
#include "gpio_controller.h"
|
||||
#include "uart_handler.h"
|
||||
#include "ethernet_manager.h"
|
||||
#include "web_server.h"
|
||||
|
||||
static const char *TAG = TAG_BMC;
|
||||
|
||||
// Ethernet event callback
|
||||
static void eth_event_callback(eth_event_type_t event) {
|
||||
switch (event) {
|
||||
case ETH_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "Ethernet connected");
|
||||
gpio_set_status_led(true);
|
||||
break;
|
||||
|
||||
case ETH_EVENT_DISCONNECTED:
|
||||
ESP_LOGW(TAG, "Ethernet disconnected");
|
||||
gpio_set_status_led(false);
|
||||
break;
|
||||
|
||||
case ETH_EVENT_GOT_IP: {
|
||||
char ip[16];
|
||||
if (ethernet_get_ip(ip) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Got IP: %s", ip);
|
||||
}
|
||||
} break;
|
||||
|
||||
case ETH_EVENT_LOST_IP:
|
||||
ESP_LOGW(TAG, "Lost IP address");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void) {
|
||||
ESP_LOGI(TAG, "========================================");
|
||||
ESP_LOGI(TAG, "ESP32-S3 BMC Firmware Starting...");
|
||||
ESP_LOGI(TAG, "========================================");
|
||||
|
||||
// Initialize GPIO controller
|
||||
ESP_LOGI(TAG, "Initializing GPIO controller...");
|
||||
esp_err_t ret = gpio_controller_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize GPIO controller: %s", esp_err_to_name(ret));
|
||||
// Continue anyway - some GPIOs might still work
|
||||
}
|
||||
|
||||
// Set status LED to indicate initialization
|
||||
gpio_set_status_led(false);
|
||||
|
||||
// Initialize UART handler
|
||||
ESP_LOGI(TAG, "Initializing UART handler...");
|
||||
ret = uart_handler_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize UART handler: %s", esp_err_to_name(ret));
|
||||
// Continue anyway - serial console is optional
|
||||
}
|
||||
|
||||
// Initialize Ethernet manager
|
||||
ESP_LOGI(TAG, "Initializing Ethernet manager...");
|
||||
ethernet_register_event_callback(eth_event_callback);
|
||||
ret = ethernet_manager_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize Ethernet: %s", esp_err_to_name(ret));
|
||||
// Cannot continue without network
|
||||
return;
|
||||
}
|
||||
|
||||
// Wait for Ethernet connection (with timeout)
|
||||
ESP_LOGI(TAG, "Waiting for Ethernet connection...");
|
||||
int timeout = 30; // 30 seconds timeout
|
||||
while (!ethernet_is_connected() && timeout > 0) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
timeout--;
|
||||
ESP_LOGD(TAG, "Waiting for Ethernet... (%d seconds remaining)", timeout);
|
||||
}
|
||||
|
||||
if (!ethernet_is_connected()) {
|
||||
ESP_LOGW(TAG, "Ethernet not connected after timeout, starting server anyway...");
|
||||
}
|
||||
|
||||
// Start web server
|
||||
ESP_LOGI(TAG, "Starting web server...");
|
||||
ret = web_server_start();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start web server: %s", esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
// Print network information
|
||||
char ip[16], netmask[16], gateway[16], mac[18];
|
||||
if (ethernet_get_network_info(ip, netmask, gateway) == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Network Configuration:");
|
||||
ESP_LOGI(TAG, " IP Address: %s", ip);
|
||||
ESP_LOGI(TAG, " Netmask: %s", netmask);
|
||||
ESP_LOGI(TAG, " Gateway: %s", gateway);
|
||||
}
|
||||
|
||||
if (ethernet_get_mac(mac) == ESP_OK) {
|
||||
ESP_LOGI(TAG, " MAC Address: %s", mac);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "========================================");
|
||||
ESP_LOGI(TAG, "BMC Ready!");
|
||||
ESP_LOGI(TAG, "Web interface: http://%s/", ip);
|
||||
ESP_LOGI(TAG, "========================================");
|
||||
|
||||
// Main loop - monitor system health
|
||||
while (1) {
|
||||
// Update status LED based on connection state
|
||||
if (ethernet_is_connected()) {
|
||||
gpio_set_status_led(true);
|
||||
} else {
|
||||
// Blink LED if not connected
|
||||
gpio_set_status_led(false);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
gpio_set_status_led(true);
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
gpio_set_status_led(false);
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
410
main/uart_handler.c
Normal file
410
main/uart_handler.c
Normal file
@@ -0,0 +1,410 @@
|
||||
#include "uart_handler.h"
|
||||
#include "config.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include <string.h>
|
||||
|
||||
static const char *TAG = TAG_UART;
|
||||
|
||||
// Runtime UART configuration
|
||||
static bmc_uart_config_t current_config;
|
||||
static bool uart_initialized = false;
|
||||
static TaskHandle_t uart_rx_task_handle = NULL;
|
||||
static SemaphoreHandle_t uart_mutex = NULL;
|
||||
|
||||
// Callback for WebSocket bridge
|
||||
static uart_rx_callback_t rx_callback = NULL;
|
||||
static SemaphoreHandle_t callback_mutex = NULL;
|
||||
|
||||
// Ring buffer for received data
|
||||
#define UART_RING_BUF_SIZE 4096
|
||||
static uint8_t *ring_buffer = NULL;
|
||||
static volatile size_t ring_head = 0;
|
||||
static volatile size_t ring_tail = 0;
|
||||
|
||||
// ============================================================================
|
||||
// Ring Buffer Functions
|
||||
// ============================================================================
|
||||
|
||||
static size_t ring_buffer_available(void) {
|
||||
if (ring_head >= ring_tail) {
|
||||
return ring_head - ring_tail;
|
||||
} else {
|
||||
return UART_RING_BUF_SIZE - ring_tail + ring_head;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t ring_buffer_read(uint8_t *buf, size_t len) {
|
||||
size_t available = ring_buffer_available();
|
||||
size_t to_read = (len < available) ? len : available;
|
||||
|
||||
for (size_t i = 0; i < to_read; i++) {
|
||||
buf[i] = ring_buffer[ring_tail];
|
||||
ring_tail = (ring_tail + 1) % UART_RING_BUF_SIZE;
|
||||
}
|
||||
|
||||
return to_read;
|
||||
}
|
||||
|
||||
static size_t ring_buffer_write(const uint8_t *buf, size_t len) {
|
||||
size_t written = 0;
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
size_t next_head = (ring_head + 1) % UART_RING_BUF_SIZE;
|
||||
if (next_head == ring_tail) {
|
||||
// Buffer full, overwrite oldest data
|
||||
ring_tail = (ring_tail + 1) % UART_RING_BUF_SIZE;
|
||||
}
|
||||
ring_buffer[ring_head] = buf[i];
|
||||
ring_head = next_head;
|
||||
written++;
|
||||
}
|
||||
|
||||
return written;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// UART Receive Task
|
||||
// ============================================================================
|
||||
|
||||
static void uart_rx_task(void *arg) {
|
||||
uint8_t *temp_buf = malloc(256);
|
||||
if (!temp_buf) {
|
||||
ESP_LOGE(TAG, "Failed to allocate UART RX buffer");
|
||||
vTaskDelete(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "UART RX task started, waiting for data on UART%d", BMC_UART_NUM);
|
||||
|
||||
while (uart_initialized) {
|
||||
// Check for data from UART with longer timeout
|
||||
int len = uart_read_bytes(BMC_UART_NUM, temp_buf, 256, pdMS_TO_TICKS(100));
|
||||
|
||||
if (len > 0) {
|
||||
// Store in ring buffer
|
||||
ring_buffer_write(temp_buf, len);
|
||||
|
||||
// Call callback if registered (outside mutex to avoid deadlock)
|
||||
uart_rx_callback_t cb = NULL;
|
||||
xSemaphoreTake(callback_mutex, pdMS_TO_TICKS(100));
|
||||
cb = rx_callback;
|
||||
xSemaphoreGive(callback_mutex);
|
||||
|
||||
if (cb) {
|
||||
cb(temp_buf, len);
|
||||
}
|
||||
} else if (len < 0) {
|
||||
ESP_LOGW(TAG, "UART read error: %d", len);
|
||||
}
|
||||
}
|
||||
|
||||
free(temp_buf);
|
||||
ESP_LOGI(TAG, "UART RX task stopped");
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Public Functions
|
||||
// ============================================================================
|
||||
|
||||
esp_err_t uart_handler_init(void) {
|
||||
if (uart_initialized) {
|
||||
ESP_LOGW(TAG, "UART handler already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Initializing UART handler");
|
||||
|
||||
// Allocate ring buffer
|
||||
ring_buffer = calloc(UART_RING_BUF_SIZE, 1);
|
||||
if (!ring_buffer) {
|
||||
ESP_LOGE(TAG, "Failed to allocate ring buffer");
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ring_head = 0;
|
||||
ring_tail = 0;
|
||||
|
||||
// Create mutex for UART access
|
||||
uart_mutex = xSemaphoreCreateMutex();
|
||||
if (!uart_mutex) {
|
||||
ESP_LOGE(TAG, "Failed to create UART mutex");
|
||||
free(ring_buffer);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Create mutex for callback
|
||||
callback_mutex = xSemaphoreCreateMutex();
|
||||
if (!callback_mutex) {
|
||||
ESP_LOGE(TAG, "Failed to create callback mutex");
|
||||
vSemaphoreDelete(uart_mutex);
|
||||
free(ring_buffer);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
// Set default configuration
|
||||
current_config.port = BMC_UART_NUM;
|
||||
current_config.baud_rate = BMC_UART_BAUD_RATE;
|
||||
current_config.data_bits = UART_DATA_8_BITS;
|
||||
current_config.parity = UART_PARITY_DISABLE;
|
||||
current_config.stop_bits = UART_STOP_BITS_1;
|
||||
current_config.flow_ctrl = UART_HW_FLOWCTRL_DISABLE;
|
||||
|
||||
// Configure UART
|
||||
uart_config_t uart_cfg = {
|
||||
.baud_rate = current_config.baud_rate,
|
||||
.data_bits = current_config.data_bits,
|
||||
.parity = current_config.parity,
|
||||
.stop_bits = current_config.stop_bits,
|
||||
.flow_ctrl = current_config.flow_ctrl,
|
||||
};
|
||||
|
||||
esp_err_t ret = uart_param_config(BMC_UART_NUM, &uart_cfg);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to configure UART: %s", esp_err_to_name(ret));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Set UART pins
|
||||
ret = uart_set_pin(BMC_UART_NUM, BMC_UART_TX_GPIO, BMC_UART_RX_GPIO, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to set UART pins: %s", esp_err_to_name(ret));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
// Install UART driver
|
||||
ret = uart_driver_install(BMC_UART_NUM, BMC_UART_BUF_SIZE * 2, 0, 0, NULL, 0);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to install UART driver: %s", esp_err_to_name(ret));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
uart_initialized = true;
|
||||
|
||||
// Start receive task
|
||||
BaseType_t task_ret =
|
||||
xTaskCreate(uart_rx_task, "uart_rx", BMC_UART_TASK_STACK, NULL, BMC_UART_TASK_PRIO, &uart_rx_task_handle);
|
||||
|
||||
if (task_ret != pdPASS) {
|
||||
ESP_LOGE(TAG, "Failed to create UART RX task");
|
||||
uart_driver_delete(BMC_UART_NUM);
|
||||
uart_initialized = false;
|
||||
ret = ESP_FAIL;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "UART initialized: port=%d, baud=%d, tx=%d, rx=%d", BMC_UART_NUM, current_config.baud_rate,
|
||||
BMC_UART_TX_GPIO, BMC_UART_RX_GPIO);
|
||||
|
||||
return ESP_OK;
|
||||
|
||||
cleanup:
|
||||
vSemaphoreDelete(callback_mutex);
|
||||
vSemaphoreDelete(uart_mutex);
|
||||
free(ring_buffer);
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t uart_handler_deinit(void) {
|
||||
if (!uart_initialized) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Deinitializing UART handler");
|
||||
|
||||
uart_initialized = false;
|
||||
|
||||
// Wait for task to finish
|
||||
if (uart_rx_task_handle) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
uart_rx_task_handle = NULL;
|
||||
}
|
||||
|
||||
// Delete UART driver
|
||||
uart_driver_delete(BMC_UART_NUM);
|
||||
|
||||
// Clean up resources
|
||||
if (callback_mutex) {
|
||||
vSemaphoreDelete(callback_mutex);
|
||||
callback_mutex = NULL;
|
||||
}
|
||||
if (uart_mutex) {
|
||||
vSemaphoreDelete(uart_mutex);
|
||||
uart_mutex = NULL;
|
||||
}
|
||||
if (ring_buffer) {
|
||||
free(ring_buffer);
|
||||
ring_buffer = NULL;
|
||||
}
|
||||
|
||||
rx_callback = NULL;
|
||||
|
||||
ESP_LOGI(TAG, "UART handler deinitialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t uart_get_config(bmc_uart_config_t *config) {
|
||||
if (!uart_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (config) {
|
||||
memcpy(config, ¤t_config, sizeof(bmc_uart_config_t));
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t uart_set_config(const bmc_uart_config_t *config) {
|
||||
if (!uart_initialized || !config) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(uart_mutex, portMAX_DELAY);
|
||||
|
||||
// Apply new configuration
|
||||
uart_config_t uart_cfg = {
|
||||
.baud_rate = config->baud_rate,
|
||||
.data_bits = config->data_bits,
|
||||
.parity = config->parity,
|
||||
.stop_bits = config->stop_bits,
|
||||
.flow_ctrl = config->flow_ctrl,
|
||||
};
|
||||
|
||||
esp_err_t ret = uart_param_config(BMC_UART_NUM, &uart_cfg);
|
||||
if (ret == ESP_OK) {
|
||||
memcpy(¤t_config, config, sizeof(bmc_uart_config_t));
|
||||
ESP_LOGI(TAG, "UART configuration updated: baud=%d", config->baud_rate);
|
||||
}
|
||||
|
||||
xSemaphoreGive(uart_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int uart_read_data(uint8_t *buf, size_t len, uint32_t timeout_ms) {
|
||||
if (!uart_initialized || !buf || len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
xSemaphoreTake(uart_mutex, pdMS_TO_TICKS(timeout_ms));
|
||||
size_t available = ring_buffer_available();
|
||||
|
||||
if (available == 0) {
|
||||
xSemaphoreGive(uart_mutex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t to_read = (len < available) ? len : available;
|
||||
size_t read = ring_buffer_read(buf, to_read);
|
||||
xSemaphoreGive(uart_mutex);
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
int uart_write_data(const uint8_t *buf, size_t len) {
|
||||
if (!uart_initialized || !buf || len == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
xSemaphoreTake(uart_mutex, portMAX_DELAY);
|
||||
int written = uart_write_bytes(BMC_UART_NUM, buf, len);
|
||||
xSemaphoreGive(uart_mutex);
|
||||
|
||||
if (written < 0) {
|
||||
ESP_LOGE(TAG, "Failed to write to UART");
|
||||
return -1;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Wrote %d bytes to UART", written);
|
||||
return written;
|
||||
}
|
||||
|
||||
int uart_data_available(void) {
|
||||
if (!uart_initialized) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ring_buffer_available();
|
||||
}
|
||||
|
||||
esp_err_t uart_flush_buffers(void) {
|
||||
if (!uart_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(uart_mutex, portMAX_DELAY);
|
||||
|
||||
// Flush hardware buffers
|
||||
uart_flush_input(BMC_UART_NUM);
|
||||
|
||||
// Clear ring buffer
|
||||
ring_head = 0;
|
||||
ring_tail = 0;
|
||||
|
||||
xSemaphoreGive(uart_mutex);
|
||||
|
||||
ESP_LOGI(TAG, "UART buffers flushed");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t uart_set_baud_rate(int baud_rate) {
|
||||
if (!uart_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(uart_mutex, portMAX_DELAY);
|
||||
|
||||
esp_err_t ret = uart_set_baudrate(BMC_UART_NUM, baud_rate);
|
||||
if (ret == ESP_OK) {
|
||||
current_config.baud_rate = baud_rate;
|
||||
ESP_LOGI(TAG, "UART baud rate changed to %d", baud_rate);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set baud rate: %s", esp_err_to_name(ret));
|
||||
}
|
||||
|
||||
xSemaphoreGive(uart_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int uart_get_baud_rate(void) {
|
||||
return current_config.baud_rate;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WebSocket Bridge Functions
|
||||
// ============================================================================
|
||||
|
||||
esp_err_t uart_register_rx_callback(uart_rx_callback_t callback) {
|
||||
if (!uart_initialized) {
|
||||
ESP_LOGW(TAG, "UART not initialized, cannot register callback");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
if (!callback_mutex) {
|
||||
ESP_LOGE(TAG, "callback_mutex is NULL!");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(callback_mutex, portMAX_DELAY);
|
||||
rx_callback = callback;
|
||||
xSemaphoreGive(callback_mutex);
|
||||
|
||||
ESP_LOGI(TAG, "UART RX callback registered successfully, rx_callback=%p", rx_callback);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t uart_unregister_rx_callback(void) {
|
||||
if (!uart_initialized) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
xSemaphoreTake(callback_mutex, portMAX_DELAY);
|
||||
rx_callback = NULL;
|
||||
xSemaphoreGive(callback_mutex);
|
||||
|
||||
ESP_LOGI(TAG, "UART RX callback unregistered");
|
||||
return ESP_OK;
|
||||
}
|
||||
138
main/uart_handler.h
Normal file
138
main/uart_handler.h
Normal file
@@ -0,0 +1,138 @@
|
||||
#ifndef UART_HANDLER_H
|
||||
#define UART_HANDLER_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "driver/uart.h"
|
||||
|
||||
// ============================================================================
|
||||
// Configuration
|
||||
// ============================================================================
|
||||
|
||||
// Default UART settings (can be changed at runtime)
|
||||
typedef struct {
|
||||
uart_port_t port;
|
||||
int baud_rate;
|
||||
uart_word_length_t data_bits;
|
||||
uart_parity_t parity;
|
||||
uart_stop_bits_t stop_bits;
|
||||
uart_hw_flowcontrol_t flow_ctrl;
|
||||
} bmc_uart_config_t;
|
||||
|
||||
// ============================================================================
|
||||
// Function Declarations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Initialize the UART handler
|
||||
*
|
||||
* Sets up UART with default configuration and starts the receive task.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_handler_init(void);
|
||||
|
||||
/**
|
||||
* @brief Deinitialize the UART handler
|
||||
*
|
||||
* Stops the receive task and releases UART resources.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_handler_deinit(void);
|
||||
|
||||
/**
|
||||
* @brief Get current UART configuration
|
||||
*
|
||||
* @param config Pointer to store the configuration
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_get_config(bmc_uart_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Set UART configuration
|
||||
*
|
||||
* @param config New configuration to apply
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_set_config(const bmc_uart_config_t *config);
|
||||
|
||||
/**
|
||||
* @brief Read data from UART buffer
|
||||
*
|
||||
* Non-blocking read from the internal ring buffer.
|
||||
*
|
||||
* @param buf Buffer to store received data
|
||||
* @param len Maximum bytes to read
|
||||
* @param timeout_ms Timeout in milliseconds
|
||||
* @return Number of bytes read, or -1 on error
|
||||
*/
|
||||
int uart_read_data(uint8_t *buf, size_t len, uint32_t timeout_ms);
|
||||
|
||||
/**
|
||||
* @brief Write data to UART
|
||||
*
|
||||
* @param buf Data to send
|
||||
* @param len Number of bytes to send
|
||||
* @return Number of bytes written, or -1 on error
|
||||
*/
|
||||
int uart_write_data(const uint8_t *buf, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Check if data is available in receive buffer
|
||||
*
|
||||
* @return Number of bytes available, or -1 on error
|
||||
*/
|
||||
int uart_data_available(void);
|
||||
|
||||
/**
|
||||
* @brief Flush UART buffers
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_flush_buffers(void);
|
||||
|
||||
/**
|
||||
* @brief Set baud rate
|
||||
*
|
||||
* @param baud_rate New baud rate
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_set_baud_rate(int baud_rate);
|
||||
|
||||
/**
|
||||
* @brief Get current baud rate
|
||||
*
|
||||
* @return Current baud rate
|
||||
*/
|
||||
int uart_get_baud_rate(void);
|
||||
|
||||
// ============================================================================
|
||||
// WebSocket Bridge Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Callback type for UART data received
|
||||
*
|
||||
* @param data Received data
|
||||
* @param len Length of data
|
||||
*/
|
||||
typedef void (*uart_rx_callback_t)(const uint8_t *data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Register a callback for received UART data
|
||||
*
|
||||
* Used for WebSocket bridge to forward data to connected clients.
|
||||
*
|
||||
* @param callback Function to call when data is received
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_register_rx_callback(uart_rx_callback_t callback);
|
||||
|
||||
/**
|
||||
* @brief Unregister the UART receive callback
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t uart_unregister_rx_callback(void);
|
||||
|
||||
#endif // UART_HANDLER_H
|
||||
745
main/web_server.c
Normal file
745
main/web_server.c
Normal file
@@ -0,0 +1,745 @@
|
||||
#include "web_server.h"
|
||||
#include "config.h"
|
||||
#include "gpio_controller.h"
|
||||
#include "uart_handler.h"
|
||||
#include "ethernet_manager.h"
|
||||
#include "esp_log.h"
|
||||
#include "cJSON.h"
|
||||
#include <string.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
static const char *TAG = TAG_HTTP;
|
||||
|
||||
// Server handle
|
||||
static httpd_handle_t server = NULL;
|
||||
static bool server_running = false;
|
||||
|
||||
// ============================================================================
|
||||
// HTML Content (Embedded)
|
||||
// ============================================================================
|
||||
|
||||
extern const uint8_t index_html_start[] asm("_binary_index_html_start");
|
||||
extern const uint8_t index_html_end[] asm("_binary_index_html_end");
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t set_content_type_json(httpd_req_t *req) {
|
||||
return httpd_resp_set_type(req, "application/json");
|
||||
}
|
||||
|
||||
static esp_err_t send_json_response(httpd_req_t *req, const char *json_str) {
|
||||
set_content_type_json(req);
|
||||
return httpd_resp_sendstr(req, json_str);
|
||||
}
|
||||
|
||||
static esp_err_t send_json_error(httpd_req_t *req, const char *message) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "success", false);
|
||||
cJSON_AddStringToObject(root, "error", message);
|
||||
|
||||
char *json_str = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
esp_err_t ret = send_json_response(req, json_str);
|
||||
free(json_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t send_json_success(httpd_req_t *req, cJSON *data) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "success", true);
|
||||
if (data) {
|
||||
cJSON_AddItemToObject(root, "data", data);
|
||||
}
|
||||
|
||||
char *json_str = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
esp_err_t ret = send_json_response(req, json_str);
|
||||
free(json_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Static File Handlers
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t index_handler(httpd_req_t *req) {
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, (const char *)index_html_start, index_html_end - index_html_start);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GPIO API Handlers
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t api_gpio_get_all_handler(httpd_req_t *req) {
|
||||
bmc_gpio_state_t states[BMC_GPIO_COUNT];
|
||||
esp_err_t ret = gpio_get_all_states(states);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Failed to get GPIO states");
|
||||
}
|
||||
|
||||
cJSON *gpios = cJSON_CreateArray();
|
||||
for (int i = 0; i < BMC_GPIO_COUNT; i++) {
|
||||
cJSON *gpio = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(gpio, "pin", states[i].pin);
|
||||
cJSON_AddStringToObject(gpio, "name", states[i].name);
|
||||
cJSON_AddStringToObject(gpio, "mode",
|
||||
states[i].mode == GPIO_MODE_INPUT ? "input"
|
||||
: states[i].mode == GPIO_MODE_OUTPUT ? "output"
|
||||
: "inout");
|
||||
cJSON_AddNumberToObject(gpio, "value", states[i].value);
|
||||
cJSON_AddBoolToObject(gpio, "inverted", states[i].inverted);
|
||||
cJSON_AddItemToArray(gpios, gpio);
|
||||
}
|
||||
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(root, "success", true);
|
||||
cJSON_AddItemToObject(root, "gpios", gpios);
|
||||
|
||||
char *json_str = cJSON_PrintUnformatted(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
esp_err_t resp_ret = send_json_response(req, json_str);
|
||||
free(json_str);
|
||||
return resp_ret;
|
||||
}
|
||||
|
||||
// static esp_err_t api_gpio_get_handler(httpd_req_t *req)
|
||||
// {
|
||||
// char pin_str[8];
|
||||
// if (httpd_req_get_url_query_str(req, pin_str, sizeof(pin_str)) != ESP_OK) {
|
||||
// return send_json_error(req, "Missing pin parameter");
|
||||
// }
|
||||
|
||||
// int pin = atoi(pin_str);
|
||||
// bmc_gpio_state_t state;
|
||||
|
||||
// esp_err_t ret = gpio_get_state((gpio_num_t)pin, &state);
|
||||
// if (ret == ESP_ERR_NOT_FOUND) {
|
||||
// return send_json_error(req, "GPIO not found");
|
||||
// }
|
||||
|
||||
// cJSON *gpio = cJSON_CreateObject();
|
||||
// cJSON_AddNumberToObject(gpio, "pin", state.pin);
|
||||
// cJSON_AddStringToObject(gpio, "name", state.name);
|
||||
// cJSON_AddStringToObject(gpio, "mode",
|
||||
// state.mode == GPIO_MODE_INPUT ? "input" :
|
||||
// state.mode == GPIO_MODE_OUTPUT ? "output" : "inout");
|
||||
// cJSON_AddNumberToObject(gpio, "value", state.value);
|
||||
// cJSON_AddBoolToObject(gpio, "inverted", state.inverted);
|
||||
|
||||
// return send_json_success(req, gpio);
|
||||
// }
|
||||
|
||||
static esp_err_t api_gpio_set_handler(httpd_req_t *req) {
|
||||
// Get pin from query string
|
||||
char query[64];
|
||||
if (httpd_req_get_url_query_str(req, query, sizeof(query)) != ESP_OK) {
|
||||
return send_json_error(req, "Missing query parameters");
|
||||
}
|
||||
|
||||
char pin_str[8];
|
||||
if (httpd_query_key_value(query, "pin", pin_str, sizeof(pin_str)) != ESP_OK) {
|
||||
return send_json_error(req, "Missing pin parameter");
|
||||
}
|
||||
int pin = atoi(pin_str);
|
||||
|
||||
// Parse JSON body
|
||||
char buf[128];
|
||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
if (ret <= 0) {
|
||||
return send_json_error(req, "No data received");
|
||||
}
|
||||
buf[ret] = '\0';
|
||||
|
||||
cJSON *json = cJSON_Parse(buf);
|
||||
if (!json) {
|
||||
return send_json_error(req, "Invalid JSON");
|
||||
}
|
||||
|
||||
cJSON *value_item = cJSON_GetObjectItem(json, "value");
|
||||
if (!value_item || !cJSON_IsNumber(value_item)) {
|
||||
cJSON_Delete(json);
|
||||
return send_json_error(req, "Missing or invalid 'value' field");
|
||||
}
|
||||
|
||||
int value = value_item->valueint;
|
||||
cJSON_Delete(json);
|
||||
|
||||
// Set GPIO
|
||||
ret = gpio_write((gpio_num_t)pin, value);
|
||||
if (ret == ESP_ERR_NOT_FOUND) {
|
||||
return send_json_error(req, "GPIO not found");
|
||||
} else if (ret == ESP_ERR_NOT_SUPPORTED) {
|
||||
return send_json_error(req, "GPIO is input-only");
|
||||
} else if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Failed to set GPIO");
|
||||
}
|
||||
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(response, "pin", pin);
|
||||
cJSON_AddNumberToObject(response, "value", value);
|
||||
|
||||
return send_json_success(req, response);
|
||||
}
|
||||
|
||||
static esp_err_t api_gpio_config_handler(httpd_req_t *req) {
|
||||
// Get pin from query string
|
||||
char query[64];
|
||||
if (httpd_req_get_url_query_str(req, query, sizeof(query)) != ESP_OK) {
|
||||
return send_json_error(req, "Missing query parameters");
|
||||
}
|
||||
|
||||
char pin_str[8];
|
||||
if (httpd_query_key_value(query, "pin", pin_str, sizeof(pin_str)) != ESP_OK) {
|
||||
return send_json_error(req, "Missing pin parameter");
|
||||
}
|
||||
int pin = atoi(pin_str);
|
||||
|
||||
// Parse JSON body
|
||||
char buf[128];
|
||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
if (ret <= 0) {
|
||||
return send_json_error(req, "No data received");
|
||||
}
|
||||
buf[ret] = '\0';
|
||||
|
||||
cJSON *json = cJSON_Parse(buf);
|
||||
if (!json) {
|
||||
return send_json_error(req, "Invalid JSON");
|
||||
}
|
||||
|
||||
cJSON *mode_item = cJSON_GetObjectItem(json, "mode");
|
||||
if (!mode_item || !cJSON_IsString(mode_item)) {
|
||||
cJSON_Delete(json);
|
||||
return send_json_error(req, "Missing or invalid 'mode' field");
|
||||
}
|
||||
|
||||
gpio_mode_t mode;
|
||||
const char *mode_str = mode_item->valuestring;
|
||||
if (strcmp(mode_str, "input") == 0) {
|
||||
mode = GPIO_MODE_INPUT;
|
||||
} else if (strcmp(mode_str, "output") == 0) {
|
||||
mode = GPIO_MODE_OUTPUT;
|
||||
} else {
|
||||
cJSON_Delete(json);
|
||||
return send_json_error(req, "Invalid mode. Use 'input' or 'output'");
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
|
||||
// Configure GPIO
|
||||
ret = gpio_configure((gpio_num_t)pin, mode);
|
||||
if (ret == ESP_ERR_NOT_FOUND) {
|
||||
return send_json_error(req, "GPIO not found");
|
||||
} else if (ret == ESP_ERR_NOT_SUPPORTED) {
|
||||
return send_json_error(req, "GPIO is input-only, cannot set as output");
|
||||
} else if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Failed to configure GPIO");
|
||||
}
|
||||
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(response, "pin", pin);
|
||||
cJSON_AddStringToObject(response, "mode", mode_str);
|
||||
|
||||
return send_json_success(req, response);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Power Control API Handlers
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t api_power_status_handler(httpd_req_t *req) {
|
||||
cJSON *status = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(status, "power_good", gpio_is_power_good());
|
||||
|
||||
// Get power control GPIO states
|
||||
int power_on_idx = gpio_find_index(BMC_GPIO_POWER_ON);
|
||||
int power_off_idx = gpio_find_index(BMC_GPIO_POWER_OFF);
|
||||
|
||||
if (power_on_idx >= 0) {
|
||||
bmc_gpio_state_t state;
|
||||
gpio_get_state(BMC_GPIO_POWER_ON, &state);
|
||||
cJSON_AddNumberToObject(status, "power_on_state", state.value);
|
||||
}
|
||||
|
||||
if (power_off_idx >= 0) {
|
||||
bmc_gpio_state_t state;
|
||||
gpio_get_state(BMC_GPIO_POWER_OFF, &state);
|
||||
cJSON_AddNumberToObject(status, "power_off_state", state.value);
|
||||
}
|
||||
|
||||
return send_json_success(req, status);
|
||||
}
|
||||
|
||||
static esp_err_t api_power_on_handler(httpd_req_t *req) {
|
||||
esp_err_t ret = gpio_power_on();
|
||||
if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Power on failed");
|
||||
}
|
||||
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "message", "Power on sequence initiated");
|
||||
cJSON_AddBoolToObject(response, "power_good", gpio_is_power_good());
|
||||
|
||||
return send_json_success(req, response);
|
||||
}
|
||||
|
||||
static esp_err_t api_power_off_handler(httpd_req_t *req) {
|
||||
esp_err_t ret = gpio_power_off();
|
||||
if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Power off failed");
|
||||
}
|
||||
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "message", "Power off sequence initiated");
|
||||
|
||||
return send_json_success(req, response);
|
||||
}
|
||||
|
||||
static esp_err_t api_power_reset_handler(httpd_req_t *req) {
|
||||
esp_err_t ret = gpio_reset();
|
||||
if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Reset failed");
|
||||
}
|
||||
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(response, "message", "Reset sequence initiated");
|
||||
|
||||
return send_json_success(req, response);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Serial API Handlers
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t api_serial_config_get_handler(httpd_req_t *req) {
|
||||
bmc_uart_config_t config;
|
||||
esp_err_t ret = uart_get_config(&config);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Failed to get serial config");
|
||||
}
|
||||
|
||||
cJSON *config_json = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(config_json, "baud_rate", config.baud_rate);
|
||||
cJSON_AddNumberToObject(config_json, "data_bits", config.data_bits);
|
||||
cJSON_AddNumberToObject(config_json, "parity", config.parity);
|
||||
cJSON_AddNumberToObject(config_json, "stop_bits", config.stop_bits);
|
||||
|
||||
return send_json_success(req, config_json);
|
||||
}
|
||||
|
||||
static esp_err_t api_serial_config_set_handler(httpd_req_t *req) {
|
||||
char buf[256];
|
||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
if (ret <= 0) {
|
||||
return send_json_error(req, "No data received");
|
||||
}
|
||||
buf[ret] = '\0';
|
||||
|
||||
cJSON *json = cJSON_Parse(buf);
|
||||
if (!json) {
|
||||
return send_json_error(req, "Invalid JSON");
|
||||
}
|
||||
|
||||
bmc_uart_config_t config;
|
||||
uart_get_config(&config); // Get current config
|
||||
|
||||
cJSON *baud = cJSON_GetObjectItem(json, "baud_rate");
|
||||
if (baud && cJSON_IsNumber(baud)) {
|
||||
config.baud_rate = baud->valueint;
|
||||
}
|
||||
|
||||
cJSON_Delete(json);
|
||||
|
||||
ret = uart_set_baud_rate(config.baud_rate);
|
||||
if (ret != ESP_OK) {
|
||||
return send_json_error(req, "Failed to set baud rate");
|
||||
}
|
||||
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(response, "baud_rate", config.baud_rate);
|
||||
|
||||
return send_json_success(req, response);
|
||||
}
|
||||
|
||||
static esp_err_t api_serial_send_handler(httpd_req_t *req) {
|
||||
char buf[1024];
|
||||
int ret = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
if (ret <= 0) {
|
||||
return send_json_error(req, "No data received");
|
||||
}
|
||||
|
||||
int written = uart_write_data((uint8_t *)buf, ret);
|
||||
if (written < 0) {
|
||||
return send_json_error(req, "Failed to send data");
|
||||
}
|
||||
|
||||
cJSON *response = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(response, "bytes_sent", written);
|
||||
|
||||
return send_json_success(req, response);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// WebSocket Handler for Serial Console
|
||||
// ============================================================================
|
||||
|
||||
static void uart_to_ws_callback(const uint8_t *data, size_t len) {
|
||||
// Always broadcast - the function will dynamically check for connected clients
|
||||
web_server_ws_broadcast(data, len);
|
||||
}
|
||||
|
||||
static esp_err_t ws_serial_handler(httpd_req_t *req) {
|
||||
// For WebSocket handlers with is_websocket=true, ESP-IDF handles the handshake
|
||||
// internally. This handler is only called for frame processing.
|
||||
// Client tracking is done dynamically in the broadcast function.
|
||||
|
||||
// Handle WebSocket frames
|
||||
httpd_ws_frame_t ws_pkt;
|
||||
memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
|
||||
|
||||
// First call gets the frame info
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
|
||||
if (ret != ESP_OK) {
|
||||
// This is normal when client disconnects - don't log as error
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Handle different frame types
|
||||
switch (ws_pkt.type) {
|
||||
case HTTPD_WS_TYPE_CLOSE:
|
||||
// Send close frame in response
|
||||
ws_pkt.len = 0;
|
||||
ws_pkt.payload = NULL;
|
||||
httpd_ws_send_frame(req, &ws_pkt);
|
||||
return ESP_OK;
|
||||
|
||||
case HTTPD_WS_TYPE_PING:
|
||||
// Respond with pong
|
||||
ws_pkt.type = HTTPD_WS_TYPE_PONG;
|
||||
return httpd_ws_send_frame(req, &ws_pkt);
|
||||
|
||||
case HTTPD_WS_TYPE_TEXT:
|
||||
case HTTPD_WS_TYPE_BINARY:
|
||||
// Handle text/binary data (send to UART)
|
||||
if (ws_pkt.len > 0) {
|
||||
uint8_t *buf = malloc(ws_pkt.len + 1);
|
||||
if (buf) {
|
||||
ws_pkt.payload = buf;
|
||||
ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
|
||||
if (ret == ESP_OK) {
|
||||
uart_write_data(ws_pkt.payload, ws_pkt.len);
|
||||
}
|
||||
free(buf);
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
|
||||
default:
|
||||
ESP_LOGI(TAG_HTTP, "WS frame of unknown type received, ignored");
|
||||
return ESP_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// System API Handlers
|
||||
// ============================================================================
|
||||
|
||||
static esp_err_t api_system_info_handler(httpd_req_t *req) {
|
||||
cJSON *info = cJSON_CreateObject();
|
||||
|
||||
// Network info
|
||||
char ip[16], netmask[16], gateway[16], mac[18];
|
||||
if (ethernet_get_network_info(ip, netmask, gateway) == ESP_OK) {
|
||||
cJSON_AddStringToObject(info, "ip", ip);
|
||||
cJSON_AddStringToObject(info, "netmask", netmask);
|
||||
cJSON_AddStringToObject(info, "gateway", gateway);
|
||||
}
|
||||
|
||||
if (ethernet_get_mac(mac) == ESP_OK) {
|
||||
cJSON_AddStringToObject(info, "mac", mac);
|
||||
}
|
||||
|
||||
cJSON_AddBoolToObject(info, "ethernet_connected", ethernet_is_connected());
|
||||
cJSON_AddNumberToObject(info, "link_speed", ethernet_get_link_speed());
|
||||
cJSON_AddBoolToObject(info, "full_duplex", ethernet_is_full_duplex());
|
||||
|
||||
// UART info
|
||||
cJSON_AddNumberToObject(info, "uart_baud_rate", uart_get_baud_rate());
|
||||
|
||||
return send_json_success(req, info);
|
||||
}
|
||||
|
||||
static esp_err_t api_system_status_handler(httpd_req_t *req) {
|
||||
cJSON *status = cJSON_CreateObject();
|
||||
|
||||
// Power status
|
||||
cJSON *power = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(power, "power_good", gpio_is_power_good());
|
||||
cJSON_AddItemToObject(status, "power", power);
|
||||
|
||||
// Network status
|
||||
cJSON *network = cJSON_CreateObject();
|
||||
cJSON_AddBoolToObject(network, "connected", ethernet_is_connected());
|
||||
char ip[16];
|
||||
if (ethernet_get_ip(ip) == ESP_OK) {
|
||||
cJSON_AddStringToObject(network, "ip", ip);
|
||||
}
|
||||
cJSON_AddItemToObject(status, "network", network);
|
||||
|
||||
// Serial status
|
||||
cJSON *serial = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(serial, "baud_rate", uart_get_baud_rate());
|
||||
cJSON_AddNumberToObject(serial, "rx_available", uart_data_available());
|
||||
cJSON_AddItemToObject(status, "serial", serial);
|
||||
|
||||
return send_json_success(req, status);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// URI Registration
|
||||
// ============================================================================
|
||||
|
||||
static const httpd_uri_t uri_index = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = index_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_gpio = {
|
||||
.uri = "/api/gpio",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_gpio_get_all_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_gpio_set = {
|
||||
.uri = "/api/gpio/set",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_gpio_set_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_gpio_config = {
|
||||
.uri = "/api/gpio/config",
|
||||
.method = HTTP_PUT,
|
||||
.handler = api_gpio_config_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_status = {
|
||||
.uri = "/api/power/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_power_status_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_on = {
|
||||
.uri = "/api/power/on",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_power_on_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_off = {
|
||||
.uri = "/api/power/off",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_power_off_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_power_reset = {
|
||||
.uri = "/api/power/reset",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_power_reset_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_serial_config_get = {
|
||||
.uri = "/api/serial/config",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_serial_config_get_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_serial_config_set = {
|
||||
.uri = "/api/serial/config",
|
||||
.method = HTTP_PUT,
|
||||
.handler = api_serial_config_set_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_serial_send = {
|
||||
.uri = "/api/serial/send",
|
||||
.method = HTTP_POST,
|
||||
.handler = api_serial_send_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_ws_serial = {
|
||||
.uri = "/api/serial/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = ws_serial_handler,
|
||||
.is_websocket = true,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_system_info = {
|
||||
.uri = "/api/system/info",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_system_info_handler,
|
||||
};
|
||||
|
||||
static const httpd_uri_t uri_api_system_status = {
|
||||
.uri = "/api/system/status",
|
||||
.method = HTTP_GET,
|
||||
.handler = api_system_status_handler,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Public Functions
|
||||
// ============================================================================
|
||||
|
||||
esp_err_t web_server_start(void) {
|
||||
if (server_running) {
|
||||
ESP_LOGW(TAG, "Web server already running");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting web server on port %d", BMC_HTTP_PORT);
|
||||
|
||||
// Configure server
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.server_port = BMC_HTTP_PORT;
|
||||
config.max_uri_handlers = 20;
|
||||
config.stack_size = BMC_HTTP_STACK_SIZE;
|
||||
config.max_open_sockets = BMC_HTTP_MAX_CONN;
|
||||
config.lru_purge_enable = false; // Don't close connections when limit reached
|
||||
config.keep_alive_enable = true; // Enable keep-alive for better connection handling
|
||||
config.keep_alive_idle = 5; // Seconds before keep-alive starts
|
||||
config.keep_alive_interval = 5; // Seconds between keep-alive probes
|
||||
config.keep_alive_count = 3; // Number of failed probes before closing
|
||||
|
||||
// Start server
|
||||
esp_err_t ret = httpd_start(&server, &config);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start web server: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Register URI handlers
|
||||
httpd_register_uri_handler(server, &uri_index);
|
||||
httpd_register_uri_handler(server, &uri_api_gpio);
|
||||
httpd_register_uri_handler(server, &uri_api_gpio_set);
|
||||
httpd_register_uri_handler(server, &uri_api_gpio_config);
|
||||
httpd_register_uri_handler(server, &uri_api_power_status);
|
||||
httpd_register_uri_handler(server, &uri_api_power_on);
|
||||
httpd_register_uri_handler(server, &uri_api_power_off);
|
||||
httpd_register_uri_handler(server, &uri_api_power_reset);
|
||||
httpd_register_uri_handler(server, &uri_api_serial_config_get);
|
||||
httpd_register_uri_handler(server, &uri_api_serial_config_set);
|
||||
httpd_register_uri_handler(server, &uri_api_serial_send);
|
||||
httpd_register_uri_handler(server, &uri_ws_serial);
|
||||
httpd_register_uri_handler(server, &uri_api_system_info);
|
||||
httpd_register_uri_handler(server, &uri_api_system_status);
|
||||
|
||||
// Register UART callback for WebSocket broadcast at startup
|
||||
esp_err_t cb_ret = uart_register_rx_callback(uart_to_ws_callback);
|
||||
if (cb_ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "UART RX callback registered for WebSocket broadcast");
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to register UART RX callback: %s", esp_err_to_name(cb_ret));
|
||||
}
|
||||
|
||||
server_running = true;
|
||||
ESP_LOGI(TAG, "Web server started successfully");
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t web_server_stop(void) {
|
||||
if (!server_running) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Stopping web server");
|
||||
|
||||
httpd_stop(server);
|
||||
server = NULL;
|
||||
server_running = false;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
bool web_server_is_running(void) {
|
||||
return server_running;
|
||||
}
|
||||
|
||||
httpd_handle_t web_server_get_handle(void) {
|
||||
return server;
|
||||
}
|
||||
|
||||
esp_err_t web_server_ws_broadcast(const uint8_t *data, size_t len) {
|
||||
if (!server_running || !data || len == 0) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
httpd_ws_frame_t ws_pkt = {
|
||||
.payload = (uint8_t *)data,
|
||||
.len = len,
|
||||
.type = HTTPD_WS_TYPE_TEXT,
|
||||
.final = true,
|
||||
};
|
||||
|
||||
// Use a fixed-size array to avoid stack issues
|
||||
#define MAX_WS_BROADCAST_CLIENTS 4
|
||||
int client_fds[MAX_WS_BROADCAST_CLIENTS];
|
||||
size_t clients = MAX_WS_BROADCAST_CLIENTS;
|
||||
|
||||
// Get client list - httpd has its own internal locking
|
||||
esp_err_t ret = httpd_get_client_list(server, &clients, client_fds);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get client list: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Count and send to all WebSocket clients
|
||||
int ws_count = 0;
|
||||
for (size_t i = 0; i < clients && i < MAX_WS_BROADCAST_CLIENTS; i++) {
|
||||
int fd = client_fds[i];
|
||||
httpd_ws_client_info_t info = httpd_ws_get_fd_info(server, fd);
|
||||
if (info == HTTPD_WS_CLIENT_WEBSOCKET) {
|
||||
ws_count++;
|
||||
esp_err_t err = httpd_ws_send_frame_async(server, fd, &ws_pkt);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGD(TAG, "WS send to fd %d failed: %s", fd, esp_err_to_name(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Log if we have multiple clients
|
||||
if (ws_count > 1) {
|
||||
ESP_LOGD(TAG, "Broadcast to %d WS clients", ws_count);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int web_server_ws_client_count(void) {
|
||||
// Dynamically count WebSocket clients
|
||||
if (!server_running) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define MAX_WS_COUNT_CLIENTS 4
|
||||
int client_fds[MAX_WS_COUNT_CLIENTS];
|
||||
size_t clients = MAX_WS_COUNT_CLIENTS;
|
||||
int count = 0;
|
||||
|
||||
if (httpd_get_client_list(server, &clients, client_fds) == ESP_OK) {
|
||||
for (size_t i = 0; i < clients && i < MAX_WS_COUNT_CLIENTS; i++) {
|
||||
if (httpd_ws_get_fd_info(server, client_fds[i]) == HTTPD_WS_CLIENT_WEBSOCKET) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
61
main/web_server.h
Normal file
61
main/web_server.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#ifndef WEB_SERVER_H
|
||||
#define WEB_SERVER_H
|
||||
|
||||
#include "esp_err.h"
|
||||
#include "esp_http_server.h"
|
||||
|
||||
// ============================================================================
|
||||
// Function Declarations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Initialize and start the web server
|
||||
*
|
||||
* Starts the HTTP server and registers all URI handlers.
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t web_server_start(void);
|
||||
|
||||
/**
|
||||
* @brief Stop the web server
|
||||
*
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t web_server_stop(void);
|
||||
|
||||
/**
|
||||
* @brief Check if web server is running
|
||||
*
|
||||
* @return true if running, false otherwise
|
||||
*/
|
||||
bool web_server_is_running(void);
|
||||
|
||||
/**
|
||||
* @brief Get the server handle
|
||||
*
|
||||
* @return httpd_handle_t or NULL if not running
|
||||
*/
|
||||
httpd_handle_t web_server_get_handle(void);
|
||||
|
||||
// ============================================================================
|
||||
// WebSocket Functions
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Broadcast data to all connected WebSocket clients
|
||||
*
|
||||
* @param data Data to send
|
||||
* @param len Length of data
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t web_server_ws_broadcast(const uint8_t *data, size_t len);
|
||||
|
||||
/**
|
||||
* @brief Get number of connected WebSocket clients
|
||||
*
|
||||
* @return Number of connected clients
|
||||
*/
|
||||
int web_server_ws_client_count(void);
|
||||
|
||||
#endif // WEB_SERVER_H
|
||||
348
plans/esp32-bmc-plan.md
Normal file
348
plans/esp32-bmc-plan.md
Normal file
@@ -0,0 +1,348 @@
|
||||
# ESP32-S3 BMC Firmware Plan
|
||||
|
||||
## Overview
|
||||
|
||||
This project creates a Baseboard Management Controller (BMC) firmware for ESP32-S3 that provides remote management capabilities for an external board via Ethernet.
|
||||
|
||||
## Framework Decision: ESP-IDF
|
||||
|
||||
**Selected Framework:** ESP-IDF (Espressif IoT Development Framework)
|
||||
|
||||
**Rationale:**
|
||||
- Native W5500 SPI Ethernet driver support
|
||||
- Excellent HTTP server performance with `esp_http_server`
|
||||
- FreeRTOS enables efficient multitasking for concurrent operations
|
||||
- Production-ready with long-term support
|
||||
- Good balance between development speed and performance
|
||||
|
||||
---
|
||||
|
||||
## System Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph ESP32-S3
|
||||
subgraph Software
|
||||
WEB[HTTP Web Server]
|
||||
API[REST API Handler]
|
||||
GPIO[GPIO Controller]
|
||||
UART[UART Serial Module]
|
||||
ETH[Ethernet Manager - W5500]
|
||||
NET[Network Stack - LwIP]
|
||||
end
|
||||
|
||||
subgraph Hardware
|
||||
SPI[SPI Bus]
|
||||
GP_IO[GPIO Pins 8-16]
|
||||
UART_HW[UART Hardware]
|
||||
end
|
||||
end
|
||||
|
||||
subgraph External
|
||||
CLIENT[Web Browser Client]
|
||||
BOARD[Target Board]
|
||||
W5500[W5500 Ethernet Module]
|
||||
end
|
||||
|
||||
CLIENT -->|HTTP| WEB
|
||||
WEB --> API
|
||||
API --> GPIO
|
||||
API --> UART
|
||||
GPIO --> GP_IO --> BOARD
|
||||
UART --> UART_HW --> BOARD
|
||||
ETH --> SPI --> W5500
|
||||
NET --> ETH
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
esp32-bmc/
|
||||
├── CMakeLists.txt
|
||||
├── sdkconfig.defaults
|
||||
├── main/
|
||||
│ ├── CMakeLists.txt
|
||||
│ ├── main.c # Application entry point
|
||||
│ ├── gpio_controller.c # GPIO management
|
||||
│ ├── gpio_controller.h
|
||||
│ ├── uart_handler.c # UART serial communication
|
||||
│ ├── uart_handler.h
|
||||
│ ├── ethernet_manager.c # W5500 Ethernet setup
|
||||
│ ├── ethernet_manager.h
|
||||
│ ├── web_server.c # HTTP server and routes
|
||||
│ ├── web_server.h
|
||||
│ └── html/
|
||||
│ └── index.html # Embedded web interface
|
||||
├── components/
|
||||
│ └── w5500/ # W5500 driver component if needed
|
||||
├── include/
|
||||
│ └── config.h # Pin definitions and settings
|
||||
└── docs/
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hardware Configuration
|
||||
|
||||
### Pin Assignments (ESP32-S3)
|
||||
|
||||
| Function | GPIO Pin | Description |
|
||||
|----------|----------|-------------|
|
||||
| **SPI for W5500** | | |
|
||||
| MOSI | GPIO 11 | SPI MOSI |
|
||||
| MISO | GPIO 13 | SPI MISO |
|
||||
| SCLK | GPIO 12 | SPI Clock |
|
||||
| CS | GPIO 10 | Chip Select |
|
||||
| RST | GPIO 9 | W5500 Reset |
|
||||
| INT | GPIO 14 | W5500 Interrupt |
|
||||
| **UART Serial** | | |
|
||||
| TX | GPIO 43 | UART0 TX to target board |
|
||||
| RX | GPIO 44 | UART0 RX from target board |
|
||||
| **BMC GPIOs** | | |
|
||||
| POWER_ON | GPIO 4 | Power control output |
|
||||
| POWER_OFF | GPIO 5 | Power control output |
|
||||
| RESET | GPIO 6 | Reset control output |
|
||||
| STATUS_LED | GPIO 7 | Status LED output |
|
||||
| PWR_GOOD | GPIO 15 | Power good input |
|
||||
| TEMP_ALERT | GPIO 16 | Temperature alert input |
|
||||
| FAN_CTRL | GPIO 17 | Fan control PWM |
|
||||
| USER_GPIO_1-8 | GPIO 18-25 | User configurable GPIOs |
|
||||
|
||||
---
|
||||
|
||||
## REST API Endpoints
|
||||
|
||||
### GPIO Control
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/gpio` | Get all GPIO states |
|
||||
| GET | `/api/gpio/{pin}` | Get specific GPIO state |
|
||||
| POST | `/api/gpio/{pin}` | Set GPIO state |
|
||||
| PUT | `/api/gpio/{pin}/config` | Configure GPIO mode |
|
||||
|
||||
**Example Request/Response:**
|
||||
|
||||
```json
|
||||
// GET /api/gpio
|
||||
{
|
||||
"gpios": [
|
||||
{"pin": 4, "name": "POWER_ON", "mode": "output", "value": 1},
|
||||
{"pin": 5, "name": "POWER_OFF", "mode": "output", "value": 0},
|
||||
{"pin": 15, "name": "PWR_GOOD", "mode": "input", "value": 1}
|
||||
]
|
||||
}
|
||||
|
||||
// POST /api/gpio/4
|
||||
// Request body:
|
||||
{"value": 1}
|
||||
// Response:
|
||||
{"success": true, "pin": 4, "value": 1}
|
||||
```
|
||||
|
||||
### Power Control
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/api/power/on` | Turn board power on |
|
||||
| POST | `/api/power/off` | Turn board power off |
|
||||
| POST | `/api/power/reset` | Reset the board |
|
||||
| GET | `/api/power/status` | Get power status |
|
||||
|
||||
### Serial Console
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/serial` | WebSocket for serial console |
|
||||
| POST | `/api/serial/send` | Send data to serial |
|
||||
| GET | `/api/serial/config` | Get serial configuration |
|
||||
| PUT | `/api/serial/config` | Configure serial settings |
|
||||
|
||||
### System
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/system/info` | Get system information |
|
||||
| GET | `/api/system/status` | Get BMC status |
|
||||
|
||||
---
|
||||
|
||||
## Web Interface
|
||||
|
||||
A simple HTML/CSS dashboard will be embedded in the firmware providing:
|
||||
|
||||
1. **Power Control Panel** - Buttons for power on/off/reset with status indicators
|
||||
2. **GPIO Monitor** - Real-time display of GPIO states
|
||||
3. **Serial Console** - Web-based terminal for serial communication
|
||||
4. **System Info** - Display BMC status and network information
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Web Interface
|
||||
PWR[Power Control Panel]
|
||||
GPIO[GPIO Monitor]
|
||||
SER[Serial Console]
|
||||
SYS[System Info]
|
||||
end
|
||||
|
||||
PWR -->|REST API| API[REST Endpoints]
|
||||
GPIO -->|REST API| API
|
||||
SER -->|WebSocket| WS[Serial Bridge]
|
||||
SYS -->|REST API| API
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### 1. Ethernet Manager (W5500)
|
||||
|
||||
The W5500 will be configured using SPI interface:
|
||||
|
||||
```c
|
||||
// Key configuration steps
|
||||
1. Initialize SPI driver
|
||||
2. Configure W5500 MAC address
|
||||
3. Set up DHCP client or static IP
|
||||
4. Handle network events (connect/disconnect)
|
||||
5. Register network callbacks
|
||||
```
|
||||
|
||||
### 2. GPIO Controller
|
||||
|
||||
```c
|
||||
// GPIO configuration structure
|
||||
typedef struct {
|
||||
gpio_num_t pin;
|
||||
const char* name;
|
||||
gpio_mode_t mode;
|
||||
int default_value;
|
||||
bool inverted; // For active-low signals
|
||||
} bmc_gpio_config_t;
|
||||
|
||||
// Functions
|
||||
void gpio_controller_init(void);
|
||||
int gpio_read(gpio_num_t pin);
|
||||
esp_err_t gpio_write(gpio_num_t pin, int value);
|
||||
esp_err_t gpio_configure(gpio_num_t pin, gpio_mode_t mode);
|
||||
```
|
||||
|
||||
### 3. UART Handler
|
||||
|
||||
```c
|
||||
// UART configuration
|
||||
#define UART_NUM UART_NUM_1
|
||||
#define UART_BAUD_RATE 115200
|
||||
#define UART_BUF_SIZE 2048
|
||||
|
||||
// Functions
|
||||
void uart_handler_init(void);
|
||||
int uart_read_bytes(uint8_t* buf, size_t len);
|
||||
int uart_write_bytes(const uint8_t* buf, size_t len);
|
||||
```
|
||||
|
||||
### 4. Web Server
|
||||
|
||||
Using `esp_http_server` component:
|
||||
|
||||
```c
|
||||
// Server setup
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
|
||||
// Register URI handlers
|
||||
httpd_register_uri_handler(server, &gpio_get_uri);
|
||||
httpd_register_uri_handler(server, &gpio_post_uri);
|
||||
httpd_register_uri_handler(server, &power_on_uri);
|
||||
httpd_register_uri_handler(server, &serial_ws_uri);
|
||||
```
|
||||
|
||||
### 5. Serial WebSocket Bridge
|
||||
|
||||
The serial endpoint will use WebSocket for bidirectional communication:
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Browser
|
||||
participant WebSocket
|
||||
participant UART Buffer
|
||||
participant Target Board
|
||||
|
||||
Browser->>WebSocket: Connect to /api/serial
|
||||
WebSocket->>UART Buffer: Start reading task
|
||||
|
||||
loop Serial Data
|
||||
Target Board->>UART Buffer: Serial data
|
||||
UART Buffer->>WebSocket: Data available
|
||||
WebSocket->>Browser: Send data frame
|
||||
end
|
||||
|
||||
Browser->>WebSocket: Send command
|
||||
WebSocket->>Target Board: Write to UART
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Configuration Options
|
||||
|
||||
The firmware will support configuration via `sdkconfig` and a runtime config file:
|
||||
|
||||
| Option | Default | Description |
|
||||
|--------|---------|-------------|
|
||||
| BMC_ETHERNET_DHCP | true | Use DHCP for IP |
|
||||
| BMC_STATIC_IP | 192.168.1.100 | Static IP if DHCP disabled |
|
||||
| BMC_NETMASK | 255.255.255.0 | Network mask |
|
||||
| BMC_GATEWAY | 192.168.1.1 | Default gateway |
|
||||
| BMC_HTTP_PORT | 80 | Web server port |
|
||||
| BMC_UART_BAUD | 115200 | Serial baud rate |
|
||||
| BMC_GPIO_COUNT | 16 | Number of GPIOs to manage |
|
||||
|
||||
---
|
||||
|
||||
## Development Phases
|
||||
|
||||
### Phase 1: Core Infrastructure
|
||||
- Initialize ESP-IDF project
|
||||
- Set up W5500 Ethernet connectivity
|
||||
- Basic GPIO read/write functionality
|
||||
|
||||
### Phase 2: Web Server
|
||||
- Implement HTTP server
|
||||
- Create REST API endpoints for GPIO
|
||||
- Add power control endpoints
|
||||
|
||||
### Phase 3: Serial Console
|
||||
- Implement UART handler
|
||||
- Create WebSocket bridge
|
||||
- Add serial configuration endpoints
|
||||
|
||||
### Phase 4: Web Interface
|
||||
- Design and implement HTML/CSS dashboard
|
||||
- Embed files in firmware
|
||||
- Add JavaScript for dynamic updates
|
||||
|
||||
### Phase 5: Polish and Testing
|
||||
- Error handling and recovery
|
||||
- Documentation
|
||||
- Testing on hardware
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
|
||||
- ESP-IDF v5.0+
|
||||
- esp_http_server component
|
||||
- driver component (SPI, GPIO, UART)
|
||||
- lwIP stack
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Initialize the ESP-IDF project structure
|
||||
2. Configure W5500 SPI Ethernet
|
||||
3. Implement GPIO controller module
|
||||
4. Build and test incrementally
|
||||
35
sdkconfig.defaults
Normal file
35
sdkconfig.defaults
Normal file
@@ -0,0 +1,35 @@
|
||||
# ESP32-S3 BMC Configuration
|
||||
|
||||
# Target chip
|
||||
CONFIG_IDF_TARGET="esp32s3"
|
||||
|
||||
# Enable FreeRTOS
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
# HTTP Server configuration
|
||||
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
|
||||
CONFIG_HTTPD_MAX_URI_LEN=512
|
||||
CONFIG_HTTPD_PURGE_BUF_LEN=256
|
||||
CONFIG_HTTPD_WS_SUPPORT=y
|
||||
|
||||
# LWIP configuration
|
||||
CONFIG_LWIP_LOCAL_HOSTNAME="vf2-bmc"
|
||||
CONFIG_LWIP_TCP_MSS=1436
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5744
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=5744
|
||||
CONFIG_LWIP_MAX_SOCKETS=7
|
||||
CONFIG_LWIP_SO_REUSE=y
|
||||
CONFIG_LWIP_SO_RCVBUF=y
|
||||
|
||||
# Enable pthread
|
||||
CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5
|
||||
|
||||
# Main task stack size
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
|
||||
|
||||
# Log level
|
||||
CONFIG_LOG_DEFAULT_LEVEL_INFO=y
|
||||
|
||||
# USB Serial JTAG console output (ESP32-S3)
|
||||
CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y
|
||||
CONFIG_ESP_CONSOLE_SECONDARY_NONE=y
|
||||
Reference in New Issue
Block a user