Add current limit configuration for VIN, MAIN, and USB
- Integrated current limit settings in `nconfig`. - Enabled API support for managing current limits. - Added UI components for configuring current limits. - Implemented backend logic for handling validations and updates. Signed-off-by: YoungSoo Shin <shinys000114@gmail.com>
This commit is contained in:
31
docs/API.md
31
docs/API.md
@@ -114,6 +114,9 @@ Retrieves the complete current network and system configuration.
|
|||||||
"mode": "apsta",
|
"mode": "apsta",
|
||||||
"net_type": "static",
|
"net_type": "static",
|
||||||
"baudrate": "115200",
|
"baudrate": "115200",
|
||||||
|
"vin_current_limit": 8.0,
|
||||||
|
"main_current_limit": 7.0,
|
||||||
|
"usb_current_limit": 5.0,
|
||||||
"ip": {
|
"ip": {
|
||||||
"ip": "192.168.1.100",
|
"ip": "192.168.1.100",
|
||||||
"gateway": "192.168.1.1",
|
"gateway": "192.168.1.1",
|
||||||
@@ -130,6 +133,9 @@ Retrieves the complete current network and system configuration.
|
|||||||
- `mode` (string): The current Wi-Fi mode (`"sta"` or `"apsta"`).
|
- `mode` (string): The current Wi-Fi mode (`"sta"` or `"apsta"`).
|
||||||
- `net_type` (string): The network type (`"dhcp"` or `"static"`).
|
- `net_type` (string): The network type (`"dhcp"` or `"static"`).
|
||||||
- `baudrate` (string): The current UART baud rate.
|
- `baudrate` (string): The current UART baud rate.
|
||||||
|
- `vin_current_limit` (number): The current limit for VIN in Amps. `0` means disabled.
|
||||||
|
- `main_current_limit` (number): The current limit for the Main channel in Amps. `0` means disabled.
|
||||||
|
- `usb_current_limit` (number): The current limit for the USB channel in Amps. `0` means disabled.
|
||||||
- `ip` (object): Contains IP configuration details. Present even if using DHCP (may show the last-leased IP).
|
- `ip` (object): Contains IP configuration details. Present even if using DHCP (may show the last-leased IP).
|
||||||
- `ip` (string): The device's IP address.
|
- `ip` (string): The device's IP address.
|
||||||
- `gateway` (string): The network gateway address.
|
- `gateway` (string): The network gateway address.
|
||||||
@@ -160,17 +166,14 @@ This is a multi-purpose endpoint. The server determines the action based on the
|
|||||||
{ "net_type": "dhcp" }
|
{ "net_type": "dhcp" }
|
||||||
```
|
```
|
||||||
- **Request Body (for Static IP)**:
|
- **Request Body (for Static IP)**:
|
||||||
*Note: The `ip` object structure is consistent with the `GET /api/setting` response.*
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"net_type": "static",
|
"net_type": "static",
|
||||||
"ip": {
|
"ip": "192.168.1.100",
|
||||||
"ip": "192.168.1.100",
|
"gateway": "192.168.1.1",
|
||||||
"gateway": "192.168.1.1",
|
"subnet": "255.255.255.0",
|
||||||
"subnet": "255.255.255.0",
|
"dns1": "8.8.8.8",
|
||||||
"dns1": "8.8.8.8",
|
"dns2": "8.8.4.4"
|
||||||
"dns2": "8.8.4.4"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
- **Success Response (200 OK)**:
|
- **Success Response (200 OK)**:
|
||||||
@@ -203,6 +206,18 @@ This is a multi-purpose endpoint. The server determines the action based on the
|
|||||||
{ "status": "baudrate_updated" }
|
{ "status": "baudrate_updated" }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
- **Action: Configure Current Limits**
|
||||||
|
- **Request Body**:
|
||||||
|
*Note: You can set one or more limits in a single request. A value of `-1.0` disables the limit.*
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vin_current_limit": 7.5,
|
||||||
|
"main_current_limit": 6.0,
|
||||||
|
"usb_current_limit": -1.0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- **Success Response (200 OK)**: `{"status":"current_limit_updated"}`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Endpoint: `/api/wifi/scan`
|
### Endpoint: `/api/wifi/scan`
|
||||||
|
|||||||
@@ -58,6 +58,13 @@ menu "ODROID-MONITOR"
|
|||||||
help
|
help
|
||||||
GPIO number for LED.
|
GPIO number for LED.
|
||||||
|
|
||||||
|
config GPIO_EXPANDER_RESET
|
||||||
|
int "Trigger reset GPIO Num"
|
||||||
|
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||||
|
default 8
|
||||||
|
help
|
||||||
|
GPIO number for Reset expander.
|
||||||
|
|
||||||
config EXPANDER_GPIO_SW_12V
|
config EXPANDER_GPIO_SW_12V
|
||||||
int "12v Load Switch GPIO Num"
|
int "12v Load Switch GPIO Num"
|
||||||
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
range ENV_GPIO_RANGE_MIN ENV_GPIO_OUT_RANGE_MAX
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ enum nconfig_type
|
|||||||
NETIF_DNS2, ///< The secondary DNS server address.
|
NETIF_DNS2, ///< The secondary DNS server address.
|
||||||
NETIF_TYPE, ///< The network interface type (e.g., "dhcp" or "static").
|
NETIF_TYPE, ///< The network interface type (e.g., "dhcp" or "static").
|
||||||
UART_BAUD_RATE, ///< The baud rate for the UART communication.
|
UART_BAUD_RATE, ///< The baud rate for the UART communication.
|
||||||
|
VIN_CURRENT_LIMIT, ///< The maximum current limit for the VIN.
|
||||||
|
MAIN_CURRENT_LIMIT, ///< The maximum current limit for the MAIN out.
|
||||||
|
USB_CURRENT_LIMIT, ///< The maximum current limit for the USB out.
|
||||||
NCONFIG_TYPE_MAX, ///< Sentinel for the maximum number of configuration types.
|
NCONFIG_TYPE_MAX, ///< Sentinel for the maximum number of configuration types.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,22 @@
|
|||||||
static nvs_handle_t handle;
|
static nvs_handle_t handle;
|
||||||
|
|
||||||
const static char* keys[NCONFIG_TYPE_MAX] = {
|
const static char* keys[NCONFIG_TYPE_MAX] = {
|
||||||
[WIFI_SSID] = "wifi_ssid", [WIFI_PASSWORD] = "wifi_pw", [WIFI_MODE] = "wifi_mode", [AP_SSID] = "ap_ssid",
|
[WIFI_SSID] = "wifi_ssid",
|
||||||
[AP_PASSWORD] = "ap_pw", [NETIF_HOSTNAME] = "hostname", [NETIF_IP] = "ip", [NETIF_GATEWAY] = "gw",
|
[WIFI_PASSWORD] = "wifi_pw",
|
||||||
[NETIF_SUBNET] = "sn", [NETIF_DNS1] = "dns1", [NETIF_DNS2] = "dns2", [NETIF_TYPE] = "dhcp",
|
[WIFI_MODE] = "wifi_mode",
|
||||||
|
[AP_SSID] = "ap_ssid",
|
||||||
|
[AP_PASSWORD] = "ap_pw",
|
||||||
|
[NETIF_HOSTNAME] = "hostname",
|
||||||
|
[NETIF_IP] = "ip",
|
||||||
|
[NETIF_GATEWAY] = "gw",
|
||||||
|
[NETIF_SUBNET] = "sn",
|
||||||
|
[NETIF_DNS1] = "dns1",
|
||||||
|
[NETIF_DNS2] = "dns2",
|
||||||
|
[NETIF_TYPE] = "dhcp",
|
||||||
[UART_BAUD_RATE] = "baudrate",
|
[UART_BAUD_RATE] = "baudrate",
|
||||||
|
[VIN_CURRENT_LIMIT] = "vin_climit",
|
||||||
|
[MAIN_CURRENT_LIMIT] = "main_climit",
|
||||||
|
[USB_CURRENT_LIMIT] = "usb_climit",
|
||||||
};
|
};
|
||||||
|
|
||||||
struct default_value
|
struct default_value
|
||||||
@@ -33,6 +45,9 @@ struct default_value const default_values[] = {
|
|||||||
{WIFI_MODE, "apsta"},
|
{WIFI_MODE, "apsta"},
|
||||||
{AP_SSID, "odroid-pm"},
|
{AP_SSID, "odroid-pm"},
|
||||||
{AP_PASSWORD, "powermate"},
|
{AP_PASSWORD, "powermate"},
|
||||||
|
{VIN_CURRENT_LIMIT, "8.0"},
|
||||||
|
{MAIN_CURRENT_LIMIT, "7.0"},
|
||||||
|
{USB_CURRENT_LIMIT, "5.0"},
|
||||||
};
|
};
|
||||||
|
|
||||||
esp_err_t init_nconfig()
|
esp_err_t init_nconfig()
|
||||||
|
|||||||
21
main/service/climit.h
Normal file
21
main/service/climit.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
//
|
||||||
|
// Created by shinys on 25. 9. 4..
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef ODROID_POWER_MATE_CLIMIT_H
|
||||||
|
#define ODROID_POWER_MATE_CLIMIT_H
|
||||||
|
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#define VIN_CURRENT_LIMIT_MAX 8.0f
|
||||||
|
#define MAIN_CURRENT_LIMIT_MAX 7.0f
|
||||||
|
#define USB_CURRENT_LIMIT_MAX 5.0f
|
||||||
|
|
||||||
|
esp_err_t climit_set_vin(double value);
|
||||||
|
esp_err_t climit_set_main(double value);
|
||||||
|
esp_err_t climit_set_usb(double value);
|
||||||
|
bool is_overcurrent();
|
||||||
|
|
||||||
|
#endif // ODROID_POWER_MATE_CLIMIT_H
|
||||||
@@ -3,27 +3,44 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include "monitor.h"
|
#include "monitor.h"
|
||||||
|
#include <nconfig.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
|
#include "climit.h"
|
||||||
#include "datalog.h"
|
#include "datalog.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "esp_wifi_types_generic.h"
|
#include "esp_wifi_types_generic.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h" // Added for FreeRTOS tasks
|
||||||
#include "ina3221.h"
|
#include "ina3221.h"
|
||||||
#include "pb.h"
|
#include "pb.h"
|
||||||
#include "pb_encode.h"
|
#include "pb_encode.h"
|
||||||
#include "status.pb.h"
|
#include "status.pb.h"
|
||||||
|
#include "sw.h"
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
|
|
||||||
|
#define CHANNEL_VIN INA3221_CHANNEL_3
|
||||||
|
#define CHANNEL_MAIN INA3221_CHANNEL_2
|
||||||
|
#define CHANNEL_USB INA3221_CHANNEL_1
|
||||||
|
|
||||||
#define PM_SDA CONFIG_I2C_GPIO_SDA
|
#define PM_SDA CONFIG_I2C_GPIO_SDA
|
||||||
#define PM_SCL CONFIG_I2C_GPIO_SCL
|
#define PM_SCL CONFIG_I2C_GPIO_SCL
|
||||||
|
|
||||||
|
#define PM_INT_CRITICAL CONFIG_GPIO_INA3221_INT_CRITICAL
|
||||||
|
#define PM_EXPANDER_RST CONFIG_GPIO_EXPANDER_RESET
|
||||||
|
|
||||||
#define PB_BUFFER_SIZE 256
|
#define PB_BUFFER_SIZE 256
|
||||||
|
|
||||||
static const char* TAG = "monitor";
|
static const char* TAG = "monitor";
|
||||||
|
|
||||||
|
static esp_timer_handle_t sensor_timer;
|
||||||
|
static esp_timer_handle_t wifi_status_timer;
|
||||||
|
// static esp_timer_handle_t shutdown_load_sw; // No longer needed
|
||||||
|
|
||||||
|
static TaskHandle_t shutdown_task_handle = NULL; // Global task handle
|
||||||
|
|
||||||
ina3221_t ina3221 = {
|
ina3221_t ina3221 = {
|
||||||
.shunt = {10, 10, 10},
|
.shunt = {10, 10, 10},
|
||||||
.mask.mask_register = INA3221_DEFAULT_MASK,
|
.mask.mask_register = INA3221_DEFAULT_MASK,
|
||||||
@@ -138,12 +155,92 @@ static void status_wifi_callback(void* arg)
|
|||||||
send_pb_message(StatusMessage_fields, &message);
|
send_pb_message(StatusMessage_fields, &message);
|
||||||
}
|
}
|
||||||
|
|
||||||
static esp_timer_handle_t sensor_timer;
|
// New FreeRTOS task for shutdown logic
|
||||||
static esp_timer_handle_t wifi_status_timer;
|
static void shutdown_load_sw_task(void* pvParameters)
|
||||||
|
{
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// Wait indefinitely for a notification from the ISR
|
||||||
|
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
|
||||||
|
|
||||||
|
ESP_LOGW(TAG, "critical interrupt triggered (via task)");
|
||||||
|
gpio_set_level(PM_EXPANDER_RST, 0);
|
||||||
|
vTaskDelay(100 / portTICK_PERIOD_MS);
|
||||||
|
gpio_set_level(PM_EXPANDER_RST, 1);
|
||||||
|
config_sw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void IRAM_ATTR critical_isr_handler(void* arg)
|
||||||
|
{
|
||||||
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
||||||
|
if (shutdown_task_handle != NULL)
|
||||||
|
{
|
||||||
|
vTaskNotifyGiveFromISR(shutdown_task_handle, &xHigherPriorityTaskWoken);
|
||||||
|
}
|
||||||
|
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gpio_init()
|
||||||
|
{
|
||||||
|
// critical int
|
||||||
|
gpio_set_intr_type(PM_INT_CRITICAL, GPIO_INTR_NEGEDGE);
|
||||||
|
gpio_set_direction(PM_INT_CRITICAL, GPIO_MODE_INPUT);
|
||||||
|
gpio_install_isr_service(0);
|
||||||
|
gpio_isr_handler_add(PM_INT_CRITICAL, critical_isr_handler, (void*)PM_INT_CRITICAL);
|
||||||
|
|
||||||
|
// rst expander
|
||||||
|
gpio_set_level(PM_EXPANDER_RST, 1);
|
||||||
|
gpio_set_direction(PM_EXPANDER_RST, GPIO_MODE_OUTPUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t climit_set_vin(double value)
|
||||||
|
{
|
||||||
|
float lim = (float)(value * 1000);
|
||||||
|
ESP_LOGI(TAG, "Setting VIN current limit to: %fmA", lim);
|
||||||
|
if (value > 0.0f)
|
||||||
|
return ina3221_set_critical_alert(&ina3221, CHANNEL_VIN, lim);
|
||||||
|
return ina3221_set_critical_alert(&ina3221, CHANNEL_VIN, (15.0f * 1000.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t climit_set_main(double value)
|
||||||
|
{
|
||||||
|
float lim = (float)(value * 1000);
|
||||||
|
ESP_LOGI(TAG, "Setting MAIN current limit to: %fmA", lim);
|
||||||
|
if (value > 0.0f)
|
||||||
|
return ina3221_set_critical_alert(&ina3221, CHANNEL_MAIN, lim);
|
||||||
|
return ina3221_set_critical_alert(&ina3221, CHANNEL_VIN, (15.0f * 1000.0f));
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t climit_set_usb(double value)
|
||||||
|
{
|
||||||
|
float lim = (float)(value * 1000);
|
||||||
|
ESP_LOGI(TAG, "Setting USB current limit to: %fmA", lim);
|
||||||
|
if (value > 0.0f)
|
||||||
|
return ina3221_set_critical_alert(&ina3221, CHANNEL_USB, lim);
|
||||||
|
return ina3221_set_critical_alert(&ina3221, CHANNEL_VIN, (15.0f * 1000.0f));
|
||||||
|
}
|
||||||
|
|
||||||
void init_status_monitor()
|
void init_status_monitor()
|
||||||
{
|
{
|
||||||
|
gpio_init();
|
||||||
ESP_ERROR_CHECK(ina3221_init_desc(&ina3221, 0x40, 0, PM_SDA, PM_SCL));
|
ESP_ERROR_CHECK(ina3221_init_desc(&ina3221, 0x40, 0, PM_SDA, PM_SCL));
|
||||||
|
|
||||||
|
double lim;
|
||||||
|
char buf[10];
|
||||||
|
|
||||||
|
nconfig_read(VIN_CURRENT_LIMIT, buf, sizeof(buf));
|
||||||
|
lim = atof(buf);
|
||||||
|
climit_set_vin(lim);
|
||||||
|
|
||||||
|
nconfig_read(MAIN_CURRENT_LIMIT, buf, sizeof(buf));
|
||||||
|
lim = atof(buf);
|
||||||
|
climit_set_main(lim);
|
||||||
|
|
||||||
|
nconfig_read(USB_CURRENT_LIMIT, buf, sizeof(buf));
|
||||||
|
lim = atof(buf);
|
||||||
|
climit_set_usb(lim);
|
||||||
|
|
||||||
datalog_init();
|
datalog_init();
|
||||||
|
|
||||||
const esp_timer_create_args_t sensor_timer_args = {.callback = &sensor_timer_callback,
|
const esp_timer_create_args_t sensor_timer_args = {.callback = &sensor_timer_callback,
|
||||||
@@ -153,6 +250,8 @@ void init_status_monitor()
|
|||||||
ESP_ERROR_CHECK(esp_timer_create(&sensor_timer_args, &sensor_timer));
|
ESP_ERROR_CHECK(esp_timer_create(&sensor_timer_args, &sensor_timer));
|
||||||
ESP_ERROR_CHECK(esp_timer_create(&wifi_timer_args, &wifi_status_timer));
|
ESP_ERROR_CHECK(esp_timer_create(&wifi_timer_args, &wifi_status_timer));
|
||||||
|
|
||||||
|
xTaskCreate(shutdown_load_sw_task, "shutdown_sw_task", configMINIMAL_STACK_SIZE * 3, NULL, 15, &shutdown_task_handle);
|
||||||
|
|
||||||
ESP_ERROR_CHECK(esp_timer_start_periodic(sensor_timer, 1000000));
|
ESP_ERROR_CHECK(esp_timer_start_periodic(sensor_timer, 1000000));
|
||||||
ESP_ERROR_CHECK(esp_timer_start_periodic(wifi_status_timer, 1000000 * 5));
|
ESP_ERROR_CHECK(esp_timer_start_periodic(wifi_status_timer, 1000000 * 5));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
#include "cJSON.h"
|
#include "cJSON.h"
|
||||||
|
#include "climit.h"
|
||||||
#include "esp_http_server.h"
|
#include "esp_http_server.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_netif.h"
|
#include "esp_netif.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "nconfig.h"
|
#include "nconfig.h"
|
||||||
#include "system.h"
|
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
|
|
||||||
@@ -15,31 +16,42 @@ static esp_err_t setting_get_handler(httpd_req_t* req)
|
|||||||
wifi_ap_record_t ap_info;
|
wifi_ap_record_t ap_info;
|
||||||
cJSON* root = cJSON_CreateObject();
|
cJSON* root = cJSON_CreateObject();
|
||||||
|
|
||||||
char mode_buf[16];
|
char buf[16];
|
||||||
if (nconfig_read(WIFI_MODE, mode_buf, sizeof(mode_buf)) == ESP_OK)
|
if (nconfig_read(WIFI_MODE, buf, sizeof(buf)) == ESP_OK)
|
||||||
{
|
{
|
||||||
cJSON_AddStringToObject(root, "mode", mode_buf);
|
cJSON_AddStringToObject(root, "mode", buf);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cJSON_AddStringToObject(root, "mode", "sta"); // Default to sta
|
cJSON_AddStringToObject(root, "mode", "sta"); // Default to sta
|
||||||
}
|
}
|
||||||
|
|
||||||
char net_type_buf[16];
|
if (nconfig_read(NETIF_TYPE, buf, sizeof(buf)) == ESP_OK)
|
||||||
if (nconfig_read(NETIF_TYPE, net_type_buf, sizeof(net_type_buf)) == ESP_OK)
|
|
||||||
{
|
{
|
||||||
cJSON_AddStringToObject(root, "net_type", net_type_buf);
|
cJSON_AddStringToObject(root, "net_type", buf);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cJSON_AddStringToObject(root, "net_type", "dhcp"); // Default to dhcp
|
cJSON_AddStringToObject(root, "net_type", "dhcp"); // Default to dhcp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add baudrate to the response
|
if (nconfig_read(UART_BAUD_RATE, buf, sizeof(buf)) == ESP_OK)
|
||||||
char baud_buf[16];
|
|
||||||
if (nconfig_read(UART_BAUD_RATE, baud_buf, sizeof(baud_buf)) == ESP_OK)
|
|
||||||
{
|
{
|
||||||
cJSON_AddStringToObject(root, "baudrate", baud_buf);
|
cJSON_AddStringToObject(root, "baudrate", buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add current limits to the response
|
||||||
|
if (nconfig_read(VIN_CURRENT_LIMIT, buf, sizeof(buf)) == ESP_OK)
|
||||||
|
{
|
||||||
|
cJSON_AddNumberToObject(root, "vin_current_limit", atof(buf));
|
||||||
|
}
|
||||||
|
if (nconfig_read(MAIN_CURRENT_LIMIT, buf, sizeof(buf)) == ESP_OK)
|
||||||
|
{
|
||||||
|
cJSON_AddNumberToObject(root, "main_current_limit", atof(buf));
|
||||||
|
}
|
||||||
|
if (nconfig_read(USB_CURRENT_LIMIT, buf, sizeof(buf)) == ESP_OK)
|
||||||
|
{
|
||||||
|
cJSON_AddNumberToObject(root, "usb_current_limit", atof(buf));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wifi_get_current_ap_info(&ap_info) == ESP_OK)
|
if (wifi_get_current_ap_info(&ap_info) == ESP_OK)
|
||||||
@@ -143,6 +155,9 @@ static esp_err_t setting_post_handler(httpd_req_t* req)
|
|||||||
cJSON* net_type_item = cJSON_GetObjectItem(root, "net_type");
|
cJSON* net_type_item = cJSON_GetObjectItem(root, "net_type");
|
||||||
cJSON* ssid_item = cJSON_GetObjectItem(root, "ssid");
|
cJSON* ssid_item = cJSON_GetObjectItem(root, "ssid");
|
||||||
cJSON* baud_item = cJSON_GetObjectItem(root, "baudrate");
|
cJSON* baud_item = cJSON_GetObjectItem(root, "baudrate");
|
||||||
|
cJSON* vin_climit_item = cJSON_GetObjectItem(root, "vin_current_limit");
|
||||||
|
cJSON* main_climit_item = cJSON_GetObjectItem(root, "main_current_limit");
|
||||||
|
cJSON* usb_climit_item = cJSON_GetObjectItem(root, "usb_current_limit");
|
||||||
|
|
||||||
if (mode_item && cJSON_IsString(mode_item))
|
if (mode_item && cJSON_IsString(mode_item))
|
||||||
{
|
{
|
||||||
@@ -253,6 +268,41 @@ static esp_err_t setting_post_handler(httpd_req_t* req)
|
|||||||
change_baud_rate(strtol(baudrate, NULL, 10));
|
change_baud_rate(strtol(baudrate, NULL, 10));
|
||||||
httpd_resp_sendstr(req, "{\"status\":\"baudrate_updated\"}");
|
httpd_resp_sendstr(req, "{\"status\":\"baudrate_updated\"}");
|
||||||
}
|
}
|
||||||
|
else if (vin_climit_item || main_climit_item || usb_climit_item)
|
||||||
|
{
|
||||||
|
char num_buf[10];
|
||||||
|
if (vin_climit_item && cJSON_IsNumber(vin_climit_item))
|
||||||
|
{
|
||||||
|
double val = vin_climit_item->valuedouble;
|
||||||
|
if (val >= 0.0 && val <= VIN_CURRENT_LIMIT_MAX)
|
||||||
|
{
|
||||||
|
snprintf(num_buf, sizeof(num_buf), "%.2f", val);
|
||||||
|
nconfig_write(VIN_CURRENT_LIMIT, num_buf);
|
||||||
|
climit_set_vin(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (main_climit_item && cJSON_IsNumber(main_climit_item))
|
||||||
|
{
|
||||||
|
double val = main_climit_item->valuedouble;
|
||||||
|
if (val >= 0.0 && val <= MAIN_CURRENT_LIMIT_MAX)
|
||||||
|
{
|
||||||
|
snprintf(num_buf, sizeof(num_buf), "%.2f", val);
|
||||||
|
nconfig_write(MAIN_CURRENT_LIMIT, num_buf);
|
||||||
|
climit_set_main(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (usb_climit_item && cJSON_IsNumber(usb_climit_item))
|
||||||
|
{
|
||||||
|
double val = usb_climit_item->valuedouble;
|
||||||
|
if (val >= 0.0 && val <= USB_CURRENT_LIMIT_MAX)
|
||||||
|
{
|
||||||
|
snprintf(num_buf, sizeof(num_buf), "%.2f", val);
|
||||||
|
nconfig_write(USB_CURRENT_LIMIT, num_buf);
|
||||||
|
climit_set_usb(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
httpd_resp_sendstr(req, "{\"status\":\"current_limit_updated\"}");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid payload");
|
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid payload");
|
||||||
|
|||||||
@@ -54,9 +54,8 @@ static void trigger_off_callback(void* arg)
|
|||||||
xSemaphoreGive(expander_mutex);
|
xSemaphoreGive(expander_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void init_sw()
|
void config_sw()
|
||||||
{
|
{
|
||||||
ESP_ERROR_CHECK(pca9557_init_desc(&pca, 0x18, I2C_PORT, GPIO_SDA, GPIO_SCL));
|
|
||||||
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_MAIN, PCA9557_MODE_OUTPUT));
|
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_MAIN, PCA9557_MODE_OUTPUT));
|
||||||
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_USB, PCA9557_MODE_OUTPUT));
|
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_USB, PCA9557_MODE_OUTPUT));
|
||||||
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_PWR, PCA9557_MODE_OUTPUT));
|
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_PWR, PCA9557_MODE_OUTPUT));
|
||||||
@@ -70,6 +69,13 @@ void init_sw()
|
|||||||
load_switch_12v_status = val != 0 ? true : false;
|
load_switch_12v_status = val != 0 ? true : false;
|
||||||
ESP_ERROR_CHECK(pca9557_get_level(&pca, CONFIG_EXPANDER_GPIO_SW_5V, &val));
|
ESP_ERROR_CHECK(pca9557_get_level(&pca, CONFIG_EXPANDER_GPIO_SW_5V, &val));
|
||||||
load_switch_5v_status = val != 0 ? true : false;
|
load_switch_5v_status = val != 0 ? true : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void init_sw()
|
||||||
|
{
|
||||||
|
ESP_ERROR_CHECK(pca9557_init_desc(&pca, 0x18, I2C_PORT, GPIO_SDA, GPIO_SCL));
|
||||||
|
|
||||||
|
config_sw();
|
||||||
|
|
||||||
const esp_timer_create_args_t power_timer_args = {
|
const esp_timer_create_args_t power_timer_args = {
|
||||||
.callback = &trigger_off_callback, .arg = (void*)GPIO_PWR, .name = "power_trigger_off"};
|
.callback = &trigger_off_callback, .arg = (void*)GPIO_PWR, .name = "power_trigger_off"};
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
void init_sw();
|
void init_sw();
|
||||||
|
void config_sw();
|
||||||
void trig_power();
|
void trig_power();
|
||||||
void trig_reset();
|
void trig_reset();
|
||||||
void set_main_load_switch(bool on);
|
void set_main_load_switch(bool on);
|
||||||
|
|||||||
@@ -86,5 +86,6 @@ void start_webserver(void)
|
|||||||
register_wifi_endpoint(server);
|
register_wifi_endpoint(server);
|
||||||
register_ws_endpoint(server);
|
register_ws_endpoint(server);
|
||||||
register_control_endpoint(server);
|
register_control_endpoint(server);
|
||||||
|
register_reboot_endpoint(server);
|
||||||
init_status_monitor();
|
init_status_monitor();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,6 +159,11 @@
|
|||||||
data-bs-target="#ap-mode-settings-pane" type="button" role="tab">AP Mode
|
data-bs-target="#ap-mode-settings-pane" type="button" role="tab">AP Mode
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" data-bs-target="#current-limit-settings-pane" data-bs-toggle="tab"
|
||||||
|
id="current-limit-settings-tab" role="tab" type="button">Current Limit
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="device-settings-tab" data-bs-toggle="tab"
|
<button class="nav-link" id="device-settings-tab" data-bs-toggle="tab"
|
||||||
data-bs-target="#device-settings-pane" type="button" role="tab">Device
|
data-bs-target="#device-settings-pane" type="button" role="tab">Device
|
||||||
@@ -260,6 +265,36 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="d-flex justify-content-end pt-3 border-top mt-3">
|
<div class="d-flex justify-content-end pt-3 border-top mt-3">
|
||||||
<button type="button" class="btn btn-primary me-2" id="ap-mode-apply-button">Apply</button>
|
<button type="button" class="btn btn-primary me-2" id="ap-mode-apply-button">Apply</button>
|
||||||
|
<button class="btn btn-secondary" data-bs-dismiss="modal" type="button">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="tab-pane fade" id="current-limit-settings-pane" role="tabpanel">
|
||||||
|
<div class="alert alert-info mt-3" role="alert">
|
||||||
|
<i class="bi bi-info-circle-fill me-2"></i>
|
||||||
|
Set a value of <strong>-1.0</strong> to disable a specific current limit. The value will be
|
||||||
|
rounded to one decimal place.
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label" for="vin-current-limit-slider">VIN Current Limit: <span
|
||||||
|
class="fw-bold text-primary" id="vin-current-limit-value">...</span> A</label>
|
||||||
|
<input class="form-range" id="vin-current-limit-slider" max="7.0" min="0" step="0.1"
|
||||||
|
type="range">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label" for="main-current-limit-slider">Main Current Limit: <span
|
||||||
|
class="fw-bold text-primary" id="main-current-limit-value">...</span> A</label>
|
||||||
|
<input class="form-range" id="main-current-limit-slider" max="6.0" min="0" step="0.1"
|
||||||
|
type="range">
|
||||||
|
</div>
|
||||||
|
<div class="mb-4">
|
||||||
|
<label class="form-label" for="usb-current-limit-slider">USB Current Limit: <span
|
||||||
|
class="fw-bold text-primary" id="usb-current-limit-value">...</span> A</label>
|
||||||
|
<input class="form-range" id="usb-current-limit-slider" max="3.0" min="0" step="0.1"
|
||||||
|
type="range">
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-end pt-3 border-top mt-3">
|
||||||
|
<button class="btn btn-primary me-2" id="current-limit-apply-button" type="button">Apply
|
||||||
|
</button>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -320,38 +355,5 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
const rebootButton = document.getElementById('reboot-button');
|
|
||||||
if (rebootButton) {
|
|
||||||
rebootButton.addEventListener('click', () => {
|
|
||||||
if (confirm('Are you sure you want to reboot the device?')) {
|
|
||||||
fetch('/api/reboot', {
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Network response was not ok');
|
|
||||||
}
|
|
||||||
return response.json();
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
console.log('Reboot command sent:', data);
|
|
||||||
const settingsModalEl = document.getElementById('settingsModal');
|
|
||||||
const settingsModal = bootstrap.Modal.getInstance(settingsModalEl);
|
|
||||||
if (settingsModal) {
|
|
||||||
settingsModal.hide();
|
|
||||||
}
|
|
||||||
alert('Reboot command sent. The device will restart in 3 seconds.');
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.error('Error sending reboot command:', error);
|
|
||||||
alert('Failed to send reboot command.');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -76,3 +76,13 @@ export const apPasswordInput = document.getElementById('ap-password');
|
|||||||
// --- Device Settings Elements ---
|
// --- Device Settings Elements ---
|
||||||
export const baudRateSelect = document.getElementById('baud-rate-select');
|
export const baudRateSelect = document.getElementById('baud-rate-select');
|
||||||
export const baudRateApplyButton = document.getElementById('baud-rate-apply-button');
|
export const baudRateApplyButton = document.getElementById('baud-rate-apply-button');
|
||||||
|
export const rebootButton = document.getElementById('reboot-button');
|
||||||
|
|
||||||
|
// --- Current Limit Settings Elements ---
|
||||||
|
export const vinSlider = document.getElementById('vin-current-limit-slider');
|
||||||
|
export const vinValueSpan = document.getElementById('vin-current-limit-value');
|
||||||
|
export const mainSlider = document.getElementById('main-current-limit-slider');
|
||||||
|
export const mainValueSpan = document.getElementById('main-current-limit-value');
|
||||||
|
export const usbSlider = document.getElementById('usb-current-limit-slider');
|
||||||
|
export const usbValueSpan = document.getElementById('usb-current-limit-value');
|
||||||
|
export const currentLimitApplyButton = document.getElementById('current-limit-apply-button');
|
||||||
|
|||||||
@@ -15,6 +15,38 @@ import {debounce, isMobile} from './utils.js';
|
|||||||
let chartsInitialized = false;
|
let chartsInitialized = false;
|
||||||
let listenersAttached = false;
|
let listenersAttached = false;
|
||||||
|
|
||||||
|
// --- Helper functions for settings ---
|
||||||
|
|
||||||
|
function updateSliderValue(slider, span) {
|
||||||
|
if (!slider || !span) return;
|
||||||
|
let value = parseFloat(slider.value).toFixed(1);
|
||||||
|
if (value <= 0) {
|
||||||
|
span.textContent = 'Disabled';
|
||||||
|
} else {
|
||||||
|
span.textContent = `${value} A`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadCurrentLimitSettings() {
|
||||||
|
fetch('/api/setting')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.vin_current_limit !== undefined) {
|
||||||
|
dom.vinSlider.value = data.vin_current_limit;
|
||||||
|
updateSliderValue(dom.vinSlider, dom.vinValueSpan);
|
||||||
|
}
|
||||||
|
if (data.main_current_limit !== undefined) {
|
||||||
|
dom.mainSlider.value = data.main_current_limit;
|
||||||
|
updateSliderValue(dom.mainSlider, dom.mainValueSpan);
|
||||||
|
}
|
||||||
|
if (data.usb_current_limit !== undefined) {
|
||||||
|
dom.usbSlider.value = data.usb_current_limit;
|
||||||
|
updateSliderValue(dom.usbSlider, dom.usbValueSpan);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error fetching current limit settings:', error));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up all event listeners for the application's interactive elements.
|
* Sets up all event listeners for the application's interactive elements.
|
||||||
* This function is now idempotent and will only attach listeners once.
|
* This function is now idempotent and will only attach listeners once.
|
||||||
@@ -50,6 +82,53 @@ export function setupEventListeners() {
|
|||||||
dom.apModeApplyButton.addEventListener('click', ui.applyApModeSettings);
|
dom.apModeApplyButton.addEventListener('click', ui.applyApModeSettings);
|
||||||
dom.baudRateApplyButton.addEventListener('click', ui.applyBaudRateSettings);
|
dom.baudRateApplyButton.addEventListener('click', ui.applyBaudRateSettings);
|
||||||
|
|
||||||
|
// --- Device Settings (Reboot) ---
|
||||||
|
if (dom.rebootButton) {
|
||||||
|
dom.rebootButton.addEventListener('click', () => {
|
||||||
|
if (confirm('Are you sure you want to reboot the device?')) {
|
||||||
|
fetch('/api/reboot', {method: 'POST'})
|
||||||
|
.then(response => response.ok ? response.json() : Promise.reject('Network response was not ok'))
|
||||||
|
.then(data => {
|
||||||
|
console.log('Reboot command sent:', data);
|
||||||
|
ui.hideSettingsModal();
|
||||||
|
alert('Reboot command sent. The device will restart in 3 seconds.');
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error sending reboot command:', error);
|
||||||
|
alert('Failed to send reboot command.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Current Limit Settings ---
|
||||||
|
dom.vinSlider.addEventListener('input', () => updateSliderValue(dom.vinSlider, dom.vinValueSpan));
|
||||||
|
dom.mainSlider.addEventListener('input', () => updateSliderValue(dom.mainSlider, dom.mainValueSpan));
|
||||||
|
dom.usbSlider.addEventListener('input', () => updateSliderValue(dom.usbSlider, dom.usbValueSpan));
|
||||||
|
|
||||||
|
dom.currentLimitApplyButton.addEventListener('click', () => {
|
||||||
|
const settings = {
|
||||||
|
vin_current_limit: parseFloat(dom.vinSlider.value),
|
||||||
|
main_current_limit: parseFloat(dom.mainSlider.value),
|
||||||
|
usb_current_limit: parseFloat(dom.usbSlider.value)
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch('/api/setting', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {'Content-Type': 'application/json'},
|
||||||
|
body: JSON.stringify(settings),
|
||||||
|
})
|
||||||
|
.then(response => response.ok ? response.json() : Promise.reject('Failed to apply settings'))
|
||||||
|
.then(data => {
|
||||||
|
console.log('Current limit settings applied:', data);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error applying current limit settings:', error);
|
||||||
|
alert('Failed to apply current limit settings.');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// --- Settings Modal Toggles (for showing/hiding sections) ---
|
// --- Settings Modal Toggles (for showing/hiding sections) ---
|
||||||
dom.apModeToggle.addEventListener('change', () => {
|
dom.apModeToggle.addEventListener('change', () => {
|
||||||
dom.apModeConfig.style.display = dom.apModeToggle.checked ? 'block' : 'none';
|
dom.apModeConfig.style.display = dom.apModeToggle.checked ? 'block' : 'none';
|
||||||
@@ -62,7 +141,12 @@ export function setupEventListeners() {
|
|||||||
// --- General App Listeners ---
|
// --- General App Listeners ---
|
||||||
dom.settingsButton.addEventListener('click', ui.initializeSettings);
|
dom.settingsButton.addEventListener('click', ui.initializeSettings);
|
||||||
|
|
||||||
// --- Accessibility: Remove focus from modal elements before hiding ---
|
// --- Accessibility & Modal Events ---
|
||||||
|
dom.settingsModal.addEventListener('show.bs.modal', () => {
|
||||||
|
// Load settings when the modal is about to be shown
|
||||||
|
loadCurrentLimitSettings();
|
||||||
|
});
|
||||||
|
|
||||||
const blurActiveElement = () => {
|
const blurActiveElement = () => {
|
||||||
if (document.activeElement && typeof document.activeElement.blur === 'function') {
|
if (document.activeElement && typeof document.activeElement.blur === 'function') {
|
||||||
document.activeElement.blur();
|
document.activeElement.blur();
|
||||||
|
|||||||
Reference in New Issue
Block a user