Apply modifications to mass-produced boards
- 3 channel power sensor ina3221 - io expander pca9557 - Some gpio moves - ... Signed-off-by: YoungSoo Shin <shinys000114@gmail.com>
This commit is contained in:
@@ -4,46 +4,16 @@
|
||||
#include "esp_log.h"
|
||||
#include "cJSON.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_timer.h"
|
||||
#include "sw.h"
|
||||
|
||||
#define GPIO_12V_SWITCH CONFIG_GPIO_SW_12V
|
||||
#define GPIO_5V_SWITCH CONFIG_GPIO_SW_5V
|
||||
#define GPIO_POWER_TRIGGER CONFIG_GPIO_TRIGGER_POWER
|
||||
#define GPIO_RESET_TRIGGER CONFIG_GPIO_TRIGGER_RESET
|
||||
|
||||
static bool status_12v_on = false;
|
||||
static bool status_5v_on = false;
|
||||
static SemaphoreHandle_t state_mutex;
|
||||
static esp_timer_handle_t power_trigger_timer;
|
||||
static esp_timer_handle_t reset_trigger_timer;
|
||||
|
||||
/**
|
||||
* @brief 타이머 만료 시 GPIO를 다시 HIGH로 설정하는 콜백 함수
|
||||
*/
|
||||
static void trigger_off_callback(void* arg)
|
||||
static esp_err_t control_get_handler(httpd_req_t* req)
|
||||
{
|
||||
gpio_num_t gpio_pin = (int) arg;
|
||||
gpio_set_level(gpio_pin, 1); // 핀을 다시 HIGH로 복구
|
||||
}
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
|
||||
static void update_gpio_switches()
|
||||
{
|
||||
gpio_set_level(GPIO_12V_SWITCH, status_12v_on);
|
||||
gpio_set_level(GPIO_5V_SWITCH, status_5v_on);
|
||||
}
|
||||
cJSON_AddBoolToObject(root, "load_12v_on", get_main_load_switch());
|
||||
cJSON_AddBoolToObject(root, "load_5v_on", get_usb_load_switch());
|
||||
|
||||
static esp_err_t control_get_handler(httpd_req_t *req)
|
||||
{
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
|
||||
xSemaphoreTake(state_mutex, portMAX_DELAY);
|
||||
cJSON_AddBoolToObject(root, "load_12v_on", status_12v_on);
|
||||
cJSON_AddBoolToObject(root, "load_5v_on", status_5v_on);
|
||||
xSemaphoreGive(state_mutex);
|
||||
|
||||
char *json_string = cJSON_Print(root);
|
||||
char* json_string = cJSON_Print(root);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, json_string, strlen(json_string));
|
||||
|
||||
@@ -53,64 +23,46 @@ static esp_err_t control_get_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t control_post_handler(httpd_req_t *req)
|
||||
static esp_err_t control_post_handler(httpd_req_t* req)
|
||||
{
|
||||
char buf[128];
|
||||
int ret, remaining = req->content_len;
|
||||
|
||||
if (remaining >= sizeof(buf)) {
|
||||
if (remaining >= sizeof(buf))
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Request content too long");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ret = httpd_req_recv(req, buf, remaining);
|
||||
if (ret <= 0) {
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT) {
|
||||
if (ret <= 0)
|
||||
{
|
||||
if (ret == HTTPD_SOCK_ERR_TIMEOUT)
|
||||
{
|
||||
httpd_resp_send_408(req);
|
||||
}
|
||||
return ESP_FAIL;
|
||||
}
|
||||
buf[ret] = '\0';
|
||||
|
||||
cJSON *root = cJSON_Parse(buf);
|
||||
if (root == NULL) {
|
||||
cJSON* root = cJSON_Parse(buf);
|
||||
if (root == NULL)
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON format");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
bool state_changed = false;
|
||||
xSemaphoreTake(state_mutex, portMAX_DELAY);
|
||||
cJSON* item_12v = cJSON_GetObjectItem(root, "load_12v_on");
|
||||
if (cJSON_IsBool(item_12v)) set_main_load_switch(cJSON_IsTrue(item_12v));
|
||||
|
||||
cJSON *item_12v = cJSON_GetObjectItem(root, "load_12v_on");
|
||||
if (cJSON_IsBool(item_12v)) {
|
||||
status_12v_on = cJSON_IsTrue(item_12v);
|
||||
state_changed = true;
|
||||
}
|
||||
cJSON* item_5v = cJSON_GetObjectItem(root, "load_5v_on");
|
||||
if (cJSON_IsBool(item_5v)) set_usb_load_switch(cJSON_IsTrue(item_5v));
|
||||
|
||||
cJSON *item_5v = cJSON_GetObjectItem(root, "load_5v_on");
|
||||
if (cJSON_IsBool(item_5v)) {
|
||||
status_5v_on = cJSON_IsTrue(item_5v);
|
||||
state_changed = true;
|
||||
}
|
||||
cJSON* power_trigger = cJSON_GetObjectItem(root, "power_trigger");
|
||||
if (cJSON_IsTrue(power_trigger)) trig_power();
|
||||
|
||||
if (state_changed) {
|
||||
update_gpio_switches();
|
||||
}
|
||||
xSemaphoreGive(state_mutex);
|
||||
|
||||
cJSON *power_trigger = cJSON_GetObjectItem(root, "power_trigger");
|
||||
if (cJSON_IsTrue(power_trigger)) {
|
||||
gpio_set_level(GPIO_POWER_TRIGGER, 0);
|
||||
esp_timer_stop(power_trigger_timer); // Stop timer if it's already running
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(power_trigger_timer, 3000000)); // 3초
|
||||
}
|
||||
|
||||
cJSON *reset_trigger = cJSON_GetObjectItem(root, "reset_trigger");
|
||||
if (cJSON_IsTrue(reset_trigger)) {
|
||||
gpio_set_level(GPIO_RESET_TRIGGER, 0);
|
||||
esp_timer_stop(reset_trigger_timer); // Stop timer if it's already running
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(reset_trigger_timer, 3000000)); // 3초
|
||||
}
|
||||
cJSON* reset_trigger = cJSON_GetObjectItem(root, "reset_trigger");
|
||||
if (cJSON_IsTrue(reset_trigger)) trig_reset();
|
||||
|
||||
cJSON_Delete(root);
|
||||
|
||||
@@ -118,64 +70,22 @@ static esp_err_t control_post_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void control_module_init(void)
|
||||
{
|
||||
state_mutex = xSemaphoreCreateMutex();
|
||||
|
||||
gpio_config_t switch_conf = {
|
||||
.pin_bit_mask = (1ULL << GPIO_12V_SWITCH) | (1ULL << GPIO_5V_SWITCH),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_DISABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&switch_conf);
|
||||
update_gpio_switches();
|
||||
|
||||
gpio_config_t trigger_conf = {
|
||||
.pin_bit_mask = (1ULL << GPIO_POWER_TRIGGER) | (1ULL << GPIO_RESET_TRIGGER),
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||
.intr_type = GPIO_INTR_DISABLE,
|
||||
};
|
||||
gpio_config(&trigger_conf);
|
||||
gpio_set_level(GPIO_POWER_TRIGGER, 1);
|
||||
gpio_set_level(GPIO_RESET_TRIGGER, 1);
|
||||
|
||||
const esp_timer_create_args_t power_timer_args = {
|
||||
.callback = &trigger_off_callback,
|
||||
.arg = (void*) GPIO_POWER_TRIGGER,
|
||||
.name = "power_trigger_off"
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&power_timer_args, &power_trigger_timer));
|
||||
|
||||
const esp_timer_create_args_t reset_timer_args = {
|
||||
.callback = &trigger_off_callback,
|
||||
.arg = (void*) GPIO_RESET_TRIGGER,
|
||||
.name = "reset_trigger_off"
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&reset_timer_args, &reset_trigger_timer));
|
||||
|
||||
}
|
||||
|
||||
void register_control_endpoint(httpd_handle_t server)
|
||||
{
|
||||
control_module_init();
|
||||
init_sw();
|
||||
httpd_uri_t get_uri = {
|
||||
.uri = "/api/control",
|
||||
.method = HTTP_GET,
|
||||
.handler = control_get_handler,
|
||||
.uri = "/api/control",
|
||||
.method = HTTP_GET,
|
||||
.handler = control_get_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &get_uri);
|
||||
|
||||
httpd_uri_t post_uri = {
|
||||
.uri = "/api/control",
|
||||
.method = HTTP_POST,
|
||||
.handler = control_post_handler,
|
||||
.uri = "/api/control",
|
||||
.method = HTTP_POST,
|
||||
.handler = control_post_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &post_uri);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
#include "esp_littlefs.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char* TAG = "DATALOG";
|
||||
static const char* TAG = "datalog";
|
||||
static const char* LOG_FILE_PATH = "/littlefs/datalog.csv";
|
||||
#define MAX_LOG_SIZE (512 * 1024)
|
||||
|
||||
#define MAX_LOG_SIZE (700 * 1024)
|
||||
|
||||
void datalog_init(void)
|
||||
{
|
||||
@@ -42,7 +43,7 @@ void datalog_init(void)
|
||||
}
|
||||
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_littlefs_info(NULL, &total, &used);
|
||||
ret = esp_littlefs_info(conf.partition_label, &total, &used);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to get LittleFS partition information (%s)", esp_err_to_name(ret));
|
||||
@@ -64,19 +65,20 @@ void datalog_init(void)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add header
|
||||
fprintf(f_write, "timestamp,voltage,current,power\n");
|
||||
// Add header for 3 channels
|
||||
fprintf(f_write, "timestamp,usb_voltage,usb_current,usb_power,main_voltage,main_current,main_power,vin_voltage,vin_current,vin_power\n");
|
||||
fclose(f_write);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Here we could check if the header is correct, but for now we assume it is.
|
||||
ESP_LOGI(TAG, "Log file found.");
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
void datalog_add(uint32_t timestamp, float voltage, float current, float power)
|
||||
void datalog_add(uint32_t timestamp, const channel_data_t* channel_data)
|
||||
{
|
||||
struct stat st;
|
||||
if (stat(LOG_FILE_PATH, &st) == 0)
|
||||
@@ -139,7 +141,11 @@ void datalog_add(uint32_t timestamp, float voltage, float current, float power)
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(f, "%lu,%.3f,%.3f,%.3f\n", timestamp, voltage, current, power);
|
||||
fprintf(f, "%lu", timestamp);
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
fprintf(f, ",%.3f,%.3f,%.3f", channel_data[i].voltage, channel_data[i].current, channel_data[i].power);
|
||||
}
|
||||
fprintf(f, "\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,16 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define NUM_CHANNELS 3
|
||||
|
||||
typedef struct {
|
||||
float voltage;
|
||||
float current;
|
||||
float power;
|
||||
} channel_data_t;
|
||||
|
||||
void datalog_init(void);
|
||||
void datalog_add(uint32_t timestamp, float voltage, float current, float power);
|
||||
void datalog_add(uint32_t timestamp, const channel_data_t* channel_data);
|
||||
const char* datalog_get_path(void);
|
||||
|
||||
#endif /* MAIN_SERVICE_DATALOG_H_ */
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
|
||||
#include "monitor.h"
|
||||
#include <time.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
@@ -17,39 +15,78 @@
|
||||
#include "webserver.h"
|
||||
#include "wifi.h"
|
||||
#include "datalog.h"
|
||||
#include "ina3221.h"
|
||||
|
||||
#define INA226_SDA CONFIG_GPIO_INA226_SDA
|
||||
#define INA226_SCL CONFIG_GPIO_INA226_SCL
|
||||
#define PM_SDA CONFIG_I2C_GPIO_SDA
|
||||
#define PM_SCL CONFIG_I2C_GPIO_SCL
|
||||
|
||||
ina226_t ina;
|
||||
i2c_master_bus_handle_t bus_handle;
|
||||
i2c_master_dev_handle_t dev_handle;
|
||||
const char* channel_names[] = {
|
||||
"USB",
|
||||
"MAIN",
|
||||
"VIN"
|
||||
};
|
||||
|
||||
ina3221_t ina3221 =
|
||||
{
|
||||
/* shunt values are 100 mOhm for each channel */
|
||||
.shunt = {
|
||||
10,
|
||||
10,
|
||||
10
|
||||
},
|
||||
.mask.mask_register = INA3221_DEFAULT_MASK,
|
||||
.i2c_dev = {0},
|
||||
.config = {
|
||||
.mode = true, // mode selection
|
||||
.esht = true, // shunt enable
|
||||
.ebus = true, // bus enable
|
||||
.ch1 = true, // channel 1 enable
|
||||
.ch2 = true, // channel 2 enable
|
||||
.ch3 = true, // channel 3 enable
|
||||
.avg = INA3221_AVG_64, // 64 samples average
|
||||
.vbus = INA3221_CT_2116, // 2ms by channel (bus)
|
||||
.vsht = INA3221_CT_2116, // 2ms by channel (shunt)
|
||||
},
|
||||
};
|
||||
|
||||
// Timer callback function to read sensor data
|
||||
static void sensor_timer_callback(void *arg)
|
||||
static void sensor_timer_callback(void* arg)
|
||||
{
|
||||
// Generate random sensor data
|
||||
float voltage = 0;
|
||||
float current = 0;
|
||||
float power = 0;
|
||||
|
||||
ina226_get_bus_voltage(&ina, &voltage);
|
||||
ina226_get_power(&ina, &power);
|
||||
ina226_get_current(&ina, ¤t);
|
||||
|
||||
// Get system uptime
|
||||
int64_t uptime_us = esp_timer_get_time();
|
||||
uint32_t uptime_sec = (uint32_t)(uptime_us / 1000000);
|
||||
uint32_t timestamp = (uint32_t)time(NULL);
|
||||
|
||||
datalog_add(timestamp, voltage, current, power);
|
||||
channel_data_t channel_data[NUM_CHANNELS];
|
||||
|
||||
// Create JSON object with sensor data
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
for (uint8_t i = 0; i < INA3221_BUS_NUMBER; i++)
|
||||
{
|
||||
float voltage, current, power;
|
||||
|
||||
ina3221_get_bus_voltage(&ina3221, i, &voltage);
|
||||
ina3221_get_shunt_value(&ina3221, i, NULL, ¤t);
|
||||
|
||||
current /= 1000.0f; // mA to A
|
||||
power = voltage * current;
|
||||
|
||||
// Populate data for datalog
|
||||
channel_data[i].voltage = voltage;
|
||||
channel_data[i].current = current;
|
||||
channel_data[i].power = power;
|
||||
|
||||
// Populate data for websocket
|
||||
cJSON* v = cJSON_AddObjectToObject(root, channel_names[i]);
|
||||
cJSON_AddNumberToObject(v, "voltage", voltage);
|
||||
cJSON_AddNumberToObject(v, "current", current);
|
||||
cJSON_AddNumberToObject(v, "power", power);
|
||||
}
|
||||
|
||||
// Add data to log file
|
||||
datalog_add(timestamp, channel_data);
|
||||
|
||||
cJSON_AddStringToObject(root, "type", "sensor_data");
|
||||
cJSON_AddNumberToObject(root, "voltage", voltage);
|
||||
cJSON_AddNumberToObject(root, "current", current);
|
||||
cJSON_AddNumberToObject(root, "power", power);
|
||||
cJSON_AddNumberToObject(root, "timestamp", timestamp);
|
||||
cJSON_AddNumberToObject(root, "uptime_sec", uptime_sec);
|
||||
|
||||
@@ -57,69 +94,40 @@ static void sensor_timer_callback(void *arg)
|
||||
push_data_to_ws(root);
|
||||
}
|
||||
|
||||
static void status_wifi_callback(void *arg)
|
||||
static void status_wifi_callback(void* arg)
|
||||
{
|
||||
wifi_ap_record_t ap_info;
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
|
||||
if (wifi_get_current_ap_info(&ap_info) == ESP_OK) {
|
||||
if (wifi_get_current_ap_info(&ap_info) == ESP_OK)
|
||||
{
|
||||
cJSON_AddStringToObject(root, "type", "wifi_status");
|
||||
cJSON_AddBoolToObject(root, "connected", true);
|
||||
cJSON_AddStringToObject(root, "ssid", (const char *)ap_info.ssid);
|
||||
cJSON_AddStringToObject(root, "ssid", (const char*)ap_info.ssid);
|
||||
cJSON_AddNumberToObject(root, "rssi", ap_info.rssi);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON_AddBoolToObject(root, "connected", false);
|
||||
}
|
||||
|
||||
push_data_to_ws(root);
|
||||
}
|
||||
|
||||
ina226_config_t ina_config = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.i2c_addr = 0x40,
|
||||
.timeout_ms = 100,
|
||||
.averages = INA226_AVERAGES_16,
|
||||
.bus_conv_time = INA226_BUS_CONV_TIME_1100_US,
|
||||
.shunt_conv_time = INA226_SHUNT_CONV_TIME_1100_US,
|
||||
.mode = INA226_MODE_SHUNT_BUS_CONT,
|
||||
.r_shunt = 0.01f,
|
||||
.max_current = 8
|
||||
};
|
||||
|
||||
static void init_ina226()
|
||||
{
|
||||
i2c_master_bus_config_t bus_config = {
|
||||
.i2c_port = I2C_NUM_0,
|
||||
.sda_io_num = (gpio_num_t) INA226_SDA,
|
||||
.scl_io_num = (gpio_num_t) INA226_SCL,
|
||||
.clk_source = I2C_CLK_SRC_DEFAULT,
|
||||
.glitch_ignore_cnt = 7,
|
||||
.flags.enable_internal_pullup = true,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &bus_handle));
|
||||
|
||||
i2c_device_config_t dev_config = {
|
||||
.dev_addr_length = I2C_ADDR_BIT_LEN_7,
|
||||
.device_address = 0x40,
|
||||
.scl_speed_hz = 400000,
|
||||
};
|
||||
ESP_ERROR_CHECK(i2c_master_bus_add_device(bus_handle, &dev_config, &dev_handle));
|
||||
|
||||
ESP_ERROR_CHECK(ina226_init(&ina, dev_handle, &ina_config));
|
||||
}
|
||||
|
||||
static esp_timer_handle_t sensor_timer;
|
||||
static esp_timer_handle_t wifi_status_timer;
|
||||
|
||||
void init_status_monitor()
|
||||
{
|
||||
init_ina226();
|
||||
ESP_ERROR_CHECK(ina3221_init_desc(&ina3221, 0x40, 0, PM_SDA, PM_SCL));
|
||||
|
||||
// logger
|
||||
datalog_init();
|
||||
|
||||
// Timer configuration
|
||||
const esp_timer_create_args_t sensor_timer_args = {
|
||||
.callback = &sensor_timer_callback,
|
||||
.name = "sensor_reading_timer" // Optional name for debugging
|
||||
.callback = &sensor_timer_callback,
|
||||
.name = "sensor_reading_timer" // Optional name for debugging
|
||||
};
|
||||
|
||||
const esp_timer_create_args_t wifi_timer_args = {
|
||||
|
||||
@@ -9,17 +9,16 @@
|
||||
|
||||
#include "esp_http_server.h"
|
||||
|
||||
// 버퍼에 저장할 데이터의 개수
|
||||
#define SENSOR_BUFFER_SIZE 100
|
||||
|
||||
// 단일 센서 데이터를 저장하기 위한 구조체
|
||||
typedef struct {
|
||||
typedef struct
|
||||
{
|
||||
float voltage;
|
||||
float current;
|
||||
float power;
|
||||
uint32_t timestamp; // 데이터를 읽은 시간 (부팅 후 ms)
|
||||
uint32_t timestamp;
|
||||
} sensor_data_t;
|
||||
|
||||
void init_status_monitor();
|
||||
|
||||
#endif //ODROID_REMOTE_HTTP_MONITOR_H
|
||||
#endif //ODROID_REMOTE_HTTP_MONITOR_H
|
||||
|
||||
@@ -8,41 +8,50 @@
|
||||
#include "esp_netif.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "webserver";
|
||||
static const char* TAG = "webserver";
|
||||
|
||||
static esp_err_t setting_get_handler(httpd_req_t *req)
|
||||
static esp_err_t setting_get_handler(httpd_req_t* req)
|
||||
{
|
||||
wifi_ap_record_t ap_info;
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON* root = cJSON_CreateObject();
|
||||
|
||||
char mode_buf[16];
|
||||
if (nconfig_read(WIFI_MODE, mode_buf, sizeof(mode_buf)) == ESP_OK) {
|
||||
if (nconfig_read(WIFI_MODE, mode_buf, sizeof(mode_buf)) == ESP_OK)
|
||||
{
|
||||
cJSON_AddStringToObject(root, "mode", mode_buf);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON_AddStringToObject(root, "mode", "sta"); // Default to sta
|
||||
}
|
||||
|
||||
char net_type_buf[16];
|
||||
if (nconfig_read(NETIF_TYPE, net_type_buf, sizeof(net_type_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);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON_AddStringToObject(root, "net_type", "dhcp"); // Default to dhcp
|
||||
}
|
||||
|
||||
// Add baudrate to the response
|
||||
char baud_buf[16];
|
||||
if (nconfig_read(UART_BAUD_RATE, baud_buf, sizeof(baud_buf)) == ESP_OK) {
|
||||
if (nconfig_read(UART_BAUD_RATE, baud_buf, sizeof(baud_buf)) == ESP_OK)
|
||||
{
|
||||
cJSON_AddStringToObject(root, "baudrate", baud_buf);
|
||||
}
|
||||
|
||||
if (wifi_get_current_ap_info(&ap_info) == ESP_OK) {
|
||||
if (wifi_get_current_ap_info(&ap_info) == ESP_OK)
|
||||
{
|
||||
cJSON_AddBoolToObject(root, "connected", true);
|
||||
cJSON_AddStringToObject(root, "ssid", (const char *)ap_info.ssid);
|
||||
cJSON_AddStringToObject(root, "ssid", (const char*)ap_info.ssid);
|
||||
cJSON_AddNumberToObject(root, "rssi", ap_info.rssi);
|
||||
|
||||
esp_netif_ip_info_t ip_info;
|
||||
cJSON* ip_obj = cJSON_CreateObject();
|
||||
if (wifi_get_current_ip_info(&ip_info) == ESP_OK) {
|
||||
if (wifi_get_current_ip_info(&ip_info) == ESP_OK)
|
||||
{
|
||||
char ip_str[16];
|
||||
esp_ip4addr_ntoa(&ip_info.ip, ip_str, sizeof(ip_str));
|
||||
cJSON_AddStringToObject(ip_obj, "ip", ip_str);
|
||||
@@ -54,21 +63,24 @@ static esp_err_t setting_get_handler(httpd_req_t *req)
|
||||
|
||||
esp_netif_dns_info_t dns_info;
|
||||
char dns_str[16];
|
||||
if (wifi_get_dns_info(ESP_NETIF_DNS_MAIN, &dns_info) == ESP_OK) {
|
||||
if (wifi_get_dns_info(ESP_NETIF_DNS_MAIN, &dns_info) == ESP_OK)
|
||||
{
|
||||
esp_ip4addr_ntoa(&dns_info.ip.u_addr.ip4, dns_str, sizeof(dns_str));
|
||||
cJSON_AddStringToObject(ip_obj, "dns1", dns_str);
|
||||
}
|
||||
if (wifi_get_dns_info(ESP_NETIF_DNS_BACKUP, &dns_info) == ESP_OK) {
|
||||
if (wifi_get_dns_info(ESP_NETIF_DNS_BACKUP, &dns_info) == ESP_OK)
|
||||
{
|
||||
esp_ip4addr_ntoa(&dns_info.ip.u_addr.ip4, dns_str, sizeof(dns_str));
|
||||
cJSON_AddStringToObject(ip_obj, "dns2", dns_str);
|
||||
}
|
||||
cJSON_AddItemToObject(root, "ip", ip_obj);
|
||||
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
cJSON_AddBoolToObject(root, "connected", false);
|
||||
}
|
||||
|
||||
const char *json_string = cJSON_Print(root);
|
||||
const char* json_string = cJSON_Print(root);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, json_string, HTTPD_RESP_USE_STRLEN);
|
||||
cJSON_Delete(root);
|
||||
@@ -77,18 +89,18 @@ static esp_err_t setting_get_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t wifi_scan(httpd_req_t *req)
|
||||
static esp_err_t wifi_scan(httpd_req_t* req)
|
||||
{
|
||||
wifi_ap_record_t *ap_records;
|
||||
wifi_ap_record_t* ap_records;
|
||||
uint16_t count;
|
||||
|
||||
wifi_scan_aps(&ap_records, &count);
|
||||
|
||||
cJSON *root = cJSON_CreateArray();
|
||||
cJSON* root = cJSON_CreateArray();
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
cJSON *ap_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(ap_obj, "ssid", (const char *)ap_records[i].ssid);
|
||||
cJSON* ap_obj = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(ap_obj, "ssid", (const char*)ap_records[i].ssid);
|
||||
cJSON_AddNumberToObject(ap_obj, "rssi", ap_records[i].rssi);
|
||||
cJSON_AddStringToObject(ap_obj, "authmode", auth_mode_str(ap_records[i].authmode));
|
||||
cJSON_AddItemToArray(root, ap_obj);
|
||||
@@ -98,7 +110,7 @@ static esp_err_t wifi_scan(httpd_req_t *req)
|
||||
free(ap_records);
|
||||
|
||||
|
||||
const char *json_string = cJSON_Print(root);
|
||||
const char* json_string = cJSON_Print(root);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, json_string, HTTPD_RESP_USE_STRLEN);
|
||||
cJSON_Delete(root);
|
||||
@@ -107,67 +119,83 @@ static esp_err_t wifi_scan(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t setting_post_handler(httpd_req_t *req)
|
||||
static esp_err_t setting_post_handler(httpd_req_t* req)
|
||||
{
|
||||
char buf[512];
|
||||
int received = httpd_req_recv(req, buf, sizeof(buf) - 1);
|
||||
|
||||
if (received <= 0) {
|
||||
if (received <= 0)
|
||||
{
|
||||
if (received == HTTPD_SOCK_ERR_TIMEOUT) httpd_resp_send_408(req);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
buf[received] = '\0';
|
||||
|
||||
cJSON *root = cJSON_Parse(buf);
|
||||
if (root == NULL) {
|
||||
cJSON* root = cJSON_Parse(buf);
|
||||
if (root == NULL)
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid JSON");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
cJSON *mode_item = cJSON_GetObjectItem(root, "mode");
|
||||
cJSON *net_type_item = cJSON_GetObjectItem(root, "net_type");
|
||||
cJSON *ssid_item = cJSON_GetObjectItem(root, "ssid");
|
||||
cJSON *baud_item = cJSON_GetObjectItem(root, "baudrate");
|
||||
cJSON* mode_item = cJSON_GetObjectItem(root, "mode");
|
||||
cJSON* net_type_item = cJSON_GetObjectItem(root, "net_type");
|
||||
cJSON* ssid_item = cJSON_GetObjectItem(root, "ssid");
|
||||
cJSON* baud_item = cJSON_GetObjectItem(root, "baudrate");
|
||||
|
||||
if (mode_item && cJSON_IsString(mode_item)) {
|
||||
if (mode_item && cJSON_IsString(mode_item))
|
||||
{
|
||||
const char* mode = mode_item->valuestring;
|
||||
ESP_LOGI(TAG, "Received mode switch request: %s", mode);
|
||||
|
||||
if (strcmp(mode, "sta") == 0 || strcmp(mode, "apsta") == 0) {
|
||||
if (strcmp(mode, "apsta") == 0) {
|
||||
cJSON *ap_ssid_item = cJSON_GetObjectItem(root, "ap_ssid");
|
||||
cJSON *ap_pass_item = cJSON_GetObjectItem(root, "ap_password");
|
||||
if (strcmp(mode, "sta") == 0 || strcmp(mode, "apsta") == 0)
|
||||
{
|
||||
if (strcmp(mode, "apsta") == 0)
|
||||
{
|
||||
cJSON* ap_ssid_item = cJSON_GetObjectItem(root, "ap_ssid");
|
||||
cJSON* ap_pass_item = cJSON_GetObjectItem(root, "ap_password");
|
||||
|
||||
if (ap_ssid_item && cJSON_IsString(ap_ssid_item)) {
|
||||
if (ap_ssid_item && cJSON_IsString(ap_ssid_item))
|
||||
{
|
||||
nconfig_write(AP_SSID, ap_ssid_item->valuestring);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "AP SSID required for APSTA mode");
|
||||
cJSON_Delete(root);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (ap_pass_item && cJSON_IsString(ap_pass_item)) {
|
||||
if (ap_pass_item && cJSON_IsString(ap_pass_item))
|
||||
{
|
||||
nconfig_write(AP_PASSWORD, ap_pass_item->valuestring);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
nconfig_delete(AP_PASSWORD); // Open network
|
||||
}
|
||||
}
|
||||
|
||||
wifi_switch_mode(mode);
|
||||
httpd_resp_sendstr(req, "{\"status\":\"mode_switch_initiated\"}");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid mode");
|
||||
}
|
||||
} else if (net_type_item && cJSON_IsString(net_type_item)) {
|
||||
}
|
||||
else if (net_type_item && cJSON_IsString(net_type_item))
|
||||
{
|
||||
const char* type = net_type_item->valuestring;
|
||||
ESP_LOGI(TAG, "Received network config: %s", type);
|
||||
|
||||
if (strcmp(type, "static") == 0) {
|
||||
cJSON *ip_item = cJSON_GetObjectItem(root, "ip");
|
||||
cJSON *gw_item = cJSON_GetObjectItem(root, "gateway");
|
||||
cJSON *sn_item = cJSON_GetObjectItem(root, "subnet");
|
||||
cJSON *d1_item = cJSON_GetObjectItem(root, "dns1");
|
||||
cJSON *d2_item = cJSON_GetObjectItem(root, "dns2");
|
||||
if (strcmp(type, "static") == 0)
|
||||
{
|
||||
cJSON* ip_item = cJSON_GetObjectItem(root, "ip");
|
||||
cJSON* gw_item = cJSON_GetObjectItem(root, "gateway");
|
||||
cJSON* sn_item = cJSON_GetObjectItem(root, "subnet");
|
||||
cJSON* d1_item = cJSON_GetObjectItem(root, "dns1");
|
||||
cJSON* d2_item = cJSON_GetObjectItem(root, "dns2");
|
||||
|
||||
const char* ip = cJSON_IsString(ip_item) ? ip_item->valuestring : NULL;
|
||||
const char* gw = cJSON_IsString(gw_item) ? gw_item->valuestring : NULL;
|
||||
@@ -175,27 +203,36 @@ static esp_err_t setting_post_handler(httpd_req_t *req)
|
||||
const char* d1 = cJSON_IsString(d1_item) ? d1_item->valuestring : NULL;
|
||||
const char* d2 = cJSON_IsString(d2_item) ? d2_item->valuestring : NULL;
|
||||
|
||||
if (ip && gw && sn && d1) {
|
||||
if (ip && gw && sn && d1)
|
||||
{
|
||||
nconfig_write(NETIF_TYPE, "static");
|
||||
nconfig_write(NETIF_IP, ip);
|
||||
nconfig_write(NETIF_GATEWAY, gw);
|
||||
nconfig_write(NETIF_SUBNET, sn);
|
||||
nconfig_write(NETIF_DNS1, d1);
|
||||
if (d2) nconfig_write(NETIF_DNS2, d2); else nconfig_delete(NETIF_DNS2);
|
||||
if (d2) nconfig_write(NETIF_DNS2, d2);
|
||||
else nconfig_delete(NETIF_DNS2);
|
||||
|
||||
wifi_use_static(ip, gw, sn, d1, d2);
|
||||
httpd_resp_sendstr(req, "{\"status\":\"static_config_applied\"}");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Missing static IP fields");
|
||||
}
|
||||
} else if (strcmp(type, "dhcp") == 0) {
|
||||
}
|
||||
else if (strcmp(type, "dhcp") == 0)
|
||||
{
|
||||
nconfig_write(NETIF_TYPE, "dhcp");
|
||||
wifi_use_dhcp();
|
||||
httpd_resp_sendstr(req, "{\"status\":\"dhcp_config_applied\"}");
|
||||
}
|
||||
} else if (ssid_item && cJSON_IsString(ssid_item)) {
|
||||
cJSON *pass_item = cJSON_GetObjectItem(root, "password");
|
||||
if (cJSON_IsString(pass_item)) {
|
||||
}
|
||||
else if (ssid_item && cJSON_IsString(ssid_item))
|
||||
{
|
||||
cJSON* pass_item = cJSON_GetObjectItem(root, "password");
|
||||
if (cJSON_IsString(pass_item))
|
||||
{
|
||||
nconfig_write(WIFI_SSID, ssid_item->valuestring);
|
||||
nconfig_write(WIFI_PASSWORD, pass_item->valuestring);
|
||||
nconfig_write(NETIF_TYPE, "dhcp"); // Default to DHCP on new connection
|
||||
@@ -204,16 +241,22 @@ static esp_err_t setting_post_handler(httpd_req_t *req)
|
||||
wifi_disconnect();
|
||||
vTaskDelay(pdMS_TO_TICKS(500));
|
||||
wifi_connect();
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Password required");
|
||||
}
|
||||
} else if (baud_item && cJSON_IsString(baud_item)) {
|
||||
}
|
||||
else if (baud_item && cJSON_IsString(baud_item))
|
||||
{
|
||||
const char* baudrate = baud_item->valuestring;
|
||||
ESP_LOGI(TAG, "Received baudrate set request: %s", baudrate);
|
||||
nconfig_write(UART_BAUD_RATE, baudrate);
|
||||
change_baud_rate(strtol(baudrate, NULL, 10));
|
||||
httpd_resp_sendstr(req, "{\"status\":\"baudrate_updated\"}");
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
httpd_resp_send_err(req, HTTPD_400_BAD_REQUEST, "Invalid payload");
|
||||
}
|
||||
|
||||
@@ -224,26 +267,26 @@ static esp_err_t setting_post_handler(httpd_req_t *req)
|
||||
void register_wifi_endpoint(httpd_handle_t server)
|
||||
{
|
||||
httpd_uri_t status = {
|
||||
.uri = "/api/setting",
|
||||
.method = HTTP_GET,
|
||||
.handler = setting_get_handler,
|
||||
.user_ctx = NULL
|
||||
.uri = "/api/setting",
|
||||
.method = HTTP_GET,
|
||||
.handler = setting_get_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &status);
|
||||
|
||||
httpd_uri_t set = {
|
||||
.uri = "/api/setting",
|
||||
.method = HTTP_POST,
|
||||
.handler = setting_post_handler,
|
||||
.user_ctx = NULL
|
||||
.uri = "/api/setting",
|
||||
.method = HTTP_POST,
|
||||
.handler = setting_post_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &set);
|
||||
|
||||
httpd_uri_t scan = {
|
||||
.uri = "/api/wifi/scan",
|
||||
.method = HTTP_GET,
|
||||
.handler = wifi_scan,
|
||||
.user_ctx = NULL
|
||||
.uri = "/api/wifi/scan",
|
||||
.method = HTTP_GET,
|
||||
.handler = wifi_scan,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &scan);
|
||||
}
|
||||
|
||||
157
main/service/sw.c
Normal file
157
main/service/sw.c
Normal file
@@ -0,0 +1,157 @@
|
||||
//
|
||||
// Created by vl011 on 2025-08-28.
|
||||
//
|
||||
|
||||
#include "sw.h"
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/task.h>
|
||||
#include <ina3221.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "pca9557.h"
|
||||
#include "driver/gpio.h"
|
||||
|
||||
#define I2C_PORT 0
|
||||
|
||||
#define GPIO_SDA CONFIG_I2C_GPIO_SDA
|
||||
#define GPIO_SCL CONFIG_I2C_GPIO_SCL
|
||||
#define GPIO_MAIN CONFIG_EXPANDER_GPIO_SW_12V
|
||||
#define GPIO_USB CONFIG_EXPANDER_GPIO_SW_5V
|
||||
#define GPIO_PWR CONFIG_EXPANDER_GPIO_TRIGGER_POWER
|
||||
#define GPIO_RST CONFIG_EXPANDER_GPIO_TRIGGER_RESET
|
||||
|
||||
#define POWER_DELAY (CONFIG_TRIGGER_POWER_DELAY_MS * 1000)
|
||||
#define RESET_DELAY (CONFIG_TRIGGER_RESET_DELAY_MS * 1000)
|
||||
|
||||
static const char* TAG = "control";
|
||||
|
||||
static bool load_switch_12v_status = false;
|
||||
static bool load_switch_5v_status = false;
|
||||
|
||||
static SemaphoreHandle_t expander_mutex;
|
||||
#define MUTEX_TIMEOUT (pdMS_TO_TICKS(100))
|
||||
|
||||
static i2c_dev_t pca = {0};
|
||||
|
||||
static esp_timer_handle_t power_trigger_timer;
|
||||
static esp_timer_handle_t reset_trigger_timer;
|
||||
|
||||
static void trigger_off_callback(void* arg)
|
||||
{
|
||||
if (xSemaphoreTake(expander_mutex, MUTEX_TIMEOUT) == pdFALSE)
|
||||
{
|
||||
ESP_LOGW(TAG, "Control error");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t gpio_pin = (int)arg;
|
||||
pca9557_set_level(&pca, gpio_pin, 1);
|
||||
xSemaphoreGive(expander_mutex);
|
||||
}
|
||||
|
||||
void init_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_USB, PCA9557_MODE_OUTPUT));
|
||||
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_PWR, PCA9557_MODE_OUTPUT));
|
||||
ESP_ERROR_CHECK(pca9557_set_mode(&pca, GPIO_RST, PCA9557_MODE_OUTPUT));
|
||||
|
||||
ESP_ERROR_CHECK(pca9557_set_level(&pca, GPIO_PWR, 1));
|
||||
ESP_ERROR_CHECK(pca9557_set_level(&pca, GPIO_RST, 1));
|
||||
|
||||
uint32_t val = 0;
|
||||
ESP_ERROR_CHECK(pca9557_get_level(&pca, CONFIG_EXPANDER_GPIO_SW_12V, &val));
|
||||
load_switch_12v_status = val != 0 ? true : false;
|
||||
ESP_ERROR_CHECK(pca9557_get_level(&pca, CONFIG_EXPANDER_GPIO_SW_5V, &val));
|
||||
load_switch_5v_status = val != 0 ? true : false;
|
||||
|
||||
const esp_timer_create_args_t power_timer_args = {
|
||||
.callback = &trigger_off_callback,
|
||||
.arg = (void*)GPIO_PWR,
|
||||
.name = "power_trigger_off"
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&power_timer_args, &power_trigger_timer));
|
||||
|
||||
const esp_timer_create_args_t reset_timer_args = {
|
||||
.callback = &trigger_off_callback,
|
||||
.arg = (void*)GPIO_RST,
|
||||
.name = "power_trigger_off"
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_timer_create(&reset_timer_args, &reset_trigger_timer));
|
||||
|
||||
expander_mutex = xSemaphoreCreateMutex();
|
||||
}
|
||||
|
||||
void trig_power()
|
||||
{
|
||||
ESP_LOGI(TAG, "Trig power");
|
||||
if (xSemaphoreTake(expander_mutex, MUTEX_TIMEOUT) == pdFALSE)
|
||||
{
|
||||
ESP_LOGW(TAG, "Control error");
|
||||
return;
|
||||
}
|
||||
pca9557_set_level(&pca, GPIO_PWR, 0);
|
||||
xSemaphoreGive(expander_mutex);
|
||||
esp_timer_stop(power_trigger_timer);
|
||||
esp_timer_start_once(power_trigger_timer, POWER_DELAY);
|
||||
}
|
||||
|
||||
void trig_reset()
|
||||
{
|
||||
ESP_LOGI(TAG, "Trig reset");
|
||||
if (xSemaphoreTake(expander_mutex, MUTEX_TIMEOUT) == pdFALSE)
|
||||
{
|
||||
ESP_LOGW(TAG, "Control error");
|
||||
return;
|
||||
}
|
||||
pca9557_set_level(&pca, GPIO_RST, 0);
|
||||
xSemaphoreGive(expander_mutex);
|
||||
esp_timer_stop(reset_trigger_timer);
|
||||
esp_timer_start_once(reset_trigger_timer, RESET_DELAY);
|
||||
}
|
||||
|
||||
void set_main_load_switch(bool on)
|
||||
{
|
||||
ESP_LOGI(TAG, "Set main load switch to %s", on ? "on" : "off");
|
||||
if (load_switch_12v_status == on) return;
|
||||
if (xSemaphoreTake(expander_mutex, MUTEX_TIMEOUT) == pdFALSE)
|
||||
{
|
||||
ESP_LOGW(TAG, "Control error");
|
||||
return;
|
||||
}
|
||||
pca9557_set_level(&pca, GPIO_MAIN, on);
|
||||
xSemaphoreGive(expander_mutex);
|
||||
load_switch_12v_status = on;
|
||||
xSemaphoreGive(expander_mutex);
|
||||
}
|
||||
|
||||
void set_usb_load_switch(bool on)
|
||||
{
|
||||
ESP_LOGI(TAG, "Set usb load switch to %s", on ? "on" : "off");
|
||||
if (load_switch_5v_status == on) return;
|
||||
if (xSemaphoreTake(expander_mutex, MUTEX_TIMEOUT) == pdFALSE)
|
||||
{
|
||||
ESP_LOGW(TAG, "Control error");
|
||||
return;
|
||||
}
|
||||
pca9557_set_level(&pca, GPIO_USB, on);
|
||||
xSemaphoreGive(expander_mutex);
|
||||
load_switch_5v_status = on;
|
||||
xSemaphoreGive(expander_mutex);
|
||||
}
|
||||
|
||||
bool get_main_load_switch()
|
||||
{
|
||||
return load_switch_12v_status;
|
||||
}
|
||||
|
||||
bool get_usb_load_switch()
|
||||
{
|
||||
return load_switch_5v_status;
|
||||
}
|
||||
17
main/service/sw.h
Normal file
17
main/service/sw.h
Normal file
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// Created by vl011 on 2025-08-28.
|
||||
//
|
||||
|
||||
#ifndef ODROID_POWER_MATE_SW_H
|
||||
#define ODROID_POWER_MATE_SW_H
|
||||
#include <stdbool.h>
|
||||
|
||||
void init_sw();
|
||||
void trig_power();
|
||||
void trig_reset();
|
||||
void set_main_load_switch(bool on);
|
||||
void set_usb_load_switch(bool on);
|
||||
bool get_main_load_switch();
|
||||
bool get_usb_load_switch();
|
||||
|
||||
#endif //ODROID_POWER_MATE_SW_H
|
||||
@@ -9,29 +9,30 @@
|
||||
#include "nconfig.h"
|
||||
#include "monitor.h"
|
||||
#include "datalog.h"
|
||||
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sys.h"
|
||||
|
||||
static const char *TAG = "WEBSERVER";
|
||||
static const char* TAG = "WEBSERVER";
|
||||
|
||||
static esp_err_t index_handler(httpd_req_t *req) {
|
||||
static esp_err_t index_handler(httpd_req_t* req)
|
||||
{
|
||||
extern const unsigned char index_html_start[] asm("_binary_index_html_gz_start");
|
||||
extern const unsigned char index_html_end[] asm("_binary_index_html_gz_end");
|
||||
const size_t index_html_size = (index_html_end - index_html_start);
|
||||
|
||||
httpd_resp_set_hdr(req, "Content-Encoding", "gzip");
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, (const char *)index_html_start, index_html_size);
|
||||
httpd_resp_send(req, (const char*)index_html_start, index_html_size);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t datalog_download_handler(httpd_req_t *req)
|
||||
static esp_err_t datalog_download_handler(httpd_req_t* req)
|
||||
{
|
||||
const char *filepath = datalog_get_path();
|
||||
FILE *f = fopen(filepath, "r");
|
||||
if (f == NULL) {
|
||||
const char* filepath = datalog_get_path();
|
||||
FILE* f = fopen(filepath, "r");
|
||||
if (f == NULL)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to open datalog file for reading");
|
||||
httpd_resp_send_404(req);
|
||||
return ESP_FAIL;
|
||||
@@ -42,8 +43,10 @@ static esp_err_t datalog_download_handler(httpd_req_t *req)
|
||||
|
||||
char buffer[1024];
|
||||
size_t bytes_read;
|
||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) > 0) {
|
||||
if (httpd_resp_send_chunk(req, buffer, bytes_read) != ESP_OK) {
|
||||
while ((bytes_read = fread(buffer, 1, sizeof(buffer), f)) > 0)
|
||||
{
|
||||
if (httpd_resp_send_chunk(req, buffer, bytes_read) != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "File sending failed!");
|
||||
fclose(f);
|
||||
httpd_resp_send_chunk(req, NULL, 0);
|
||||
@@ -57,31 +60,32 @@ static esp_err_t datalog_download_handler(httpd_req_t *req)
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
// HTTP 서버 시작
|
||||
void start_webserver(void) {
|
||||
void start_webserver(void)
|
||||
{
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
|
||||
config.stack_size = 1024 * 8;
|
||||
config.max_uri_handlers = 10;
|
||||
|
||||
if (httpd_start(&server, &config) != ESP_OK) {
|
||||
return ;
|
||||
if (httpd_start(&server, &config) != ESP_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Index page
|
||||
httpd_uri_t index = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = index_handler,
|
||||
.user_ctx = NULL
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = index_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &index);
|
||||
|
||||
httpd_uri_t datalog_uri = {
|
||||
.uri = "/datalog.csv",
|
||||
.method = HTTP_GET,
|
||||
.handler = datalog_download_handler,
|
||||
.user_ctx = NULL
|
||||
.uri = "/datalog.csv",
|
||||
.method = HTTP_GET,
|
||||
.handler = datalog_download_handler,
|
||||
.user_ctx = NULL
|
||||
};
|
||||
httpd_register_uri_handler(server, &datalog_uri);
|
||||
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#define ODROID_REMOTE_HTTP_WEBSERVER_H
|
||||
#include "cJSON.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "system.h"
|
||||
|
||||
void register_wifi_endpoint(httpd_handle_t server);
|
||||
void register_ws_endpoint(httpd_handle_t server);
|
||||
void register_control_endpoint(httpd_handle_t server);
|
||||
void push_data_to_ws(cJSON *data);
|
||||
void push_data_to_ws(cJSON* data);
|
||||
void register_reboot_endpoint(httpd_handle_t server);
|
||||
esp_err_t change_baud_rate(int baud_rate);
|
||||
|
||||
#endif //ODROID_REMOTE_HTTP_WEBSERVER_H
|
||||
#endif //ODROID_REMOTE_HTTP_WEBSERVER_H
|
||||
|
||||
@@ -17,13 +17,14 @@
|
||||
#define UART_RX_PIN CONFIG_GPIO_UART_RX
|
||||
#define CHUNK_SIZE (1024)
|
||||
|
||||
static const char *TAG = "ws-uart";
|
||||
static const char* TAG = "ws-uart";
|
||||
|
||||
static int client_fd = -1;
|
||||
static SemaphoreHandle_t client_fd_mutex;
|
||||
|
||||
// Unified message structure for the websocket queue
|
||||
enum ws_message_type {
|
||||
enum ws_message_type
|
||||
{
|
||||
WS_MSG_STATUS,
|
||||
WS_MSG_UART
|
||||
};
|
||||
@@ -31,12 +32,17 @@ enum ws_message_type {
|
||||
struct ws_message
|
||||
{
|
||||
enum ws_message_type type;
|
||||
union {
|
||||
struct {
|
||||
cJSON *data;
|
||||
|
||||
union
|
||||
{
|
||||
struct
|
||||
{
|
||||
cJSON* data;
|
||||
} status;
|
||||
struct {
|
||||
uint8_t *data;
|
||||
|
||||
struct
|
||||
{
|
||||
uint8_t* data;
|
||||
size_t len;
|
||||
} uart;
|
||||
} content;
|
||||
@@ -45,23 +51,29 @@ struct ws_message
|
||||
static QueueHandle_t ws_queue;
|
||||
|
||||
// Unified task to send data from the queue to the websocket client
|
||||
static void unified_ws_sender_task(void *arg)
|
||||
static void unified_ws_sender_task(void* arg)
|
||||
{
|
||||
httpd_handle_t server = (httpd_handle_t)arg;
|
||||
struct ws_message msg;
|
||||
const TickType_t PING_INTERVAL = pdMS_TO_TICKS(5000);
|
||||
|
||||
while (1) {
|
||||
if (xQueueReceive(ws_queue, &msg, PING_INTERVAL)) {
|
||||
while (1)
|
||||
{
|
||||
if (xQueueReceive(ws_queue, &msg, PING_INTERVAL))
|
||||
{
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
int fd = client_fd;
|
||||
|
||||
if (fd <= 0) {
|
||||
if (fd <= 0)
|
||||
{
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
// Free memory if client is not connected
|
||||
if (msg.type == WS_MSG_STATUS) {
|
||||
if (msg.type == WS_MSG_STATUS)
|
||||
{
|
||||
cJSON_Delete(msg.content.status.data);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
free(msg.content.uart.data);
|
||||
}
|
||||
continue;
|
||||
@@ -70,17 +82,20 @@ static void unified_ws_sender_task(void *arg)
|
||||
httpd_ws_frame_t ws_pkt = {0};
|
||||
esp_err_t err = ESP_FAIL;
|
||||
|
||||
if (msg.type == WS_MSG_STATUS) {
|
||||
char *json_string = cJSON_Print(msg.content.status.data);
|
||||
if (msg.type == WS_MSG_STATUS)
|
||||
{
|
||||
char* json_string = cJSON_Print(msg.content.status.data);
|
||||
cJSON_Delete(msg.content.status.data);
|
||||
|
||||
ws_pkt.payload = (uint8_t *)json_string;
|
||||
ws_pkt.payload = (uint8_t*)json_string;
|
||||
ws_pkt.len = strlen(json_string);
|
||||
ws_pkt.type = HTTPD_WS_TYPE_TEXT;
|
||||
err = httpd_ws_send_frame_async(server, fd, &ws_pkt);
|
||||
free(json_string);
|
||||
|
||||
} else { // WS_MSG_UART
|
||||
}
|
||||
else
|
||||
{
|
||||
// WS_MSG_UART
|
||||
ws_pkt.payload = msg.content.uart.data;
|
||||
ws_pkt.len = msg.content.uart.len;
|
||||
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
|
||||
@@ -88,24 +103,30 @@ static void unified_ws_sender_task(void *arg)
|
||||
free(msg.content.uart.data);
|
||||
}
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "unified_ws_sender_task: async send failed for fd %d, error: %s", fd, esp_err_to_name(err));
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "unified_ws_sender_task: async send failed for fd %d, error: %s", fd,
|
||||
esp_err_to_name(err));
|
||||
client_fd = -1;
|
||||
}
|
||||
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
// Queue receive timed out, send a PING to keep connection alive
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
int fd = client_fd;
|
||||
if (fd > 0) {
|
||||
if (fd > 0)
|
||||
{
|
||||
httpd_ws_frame_t ping_pkt = {0};
|
||||
ping_pkt.type = HTTPD_WS_TYPE_PING;
|
||||
ping_pkt.final = true;
|
||||
esp_err_t err = httpd_ws_send_frame_async(server, fd, &ping_pkt);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to send PING frame, closing connection for fd %d, error: %s", fd, esp_err_to_name(err));
|
||||
if (err != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "Failed to send PING frame, closing connection for fd %d, error: %s", fd,
|
||||
esp_err_to_name(err));
|
||||
client_fd = -1;
|
||||
}
|
||||
}
|
||||
@@ -114,23 +135,25 @@ static void unified_ws_sender_task(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
static void uart_polling_task(void *arg)
|
||||
static void uart_polling_task(void* arg)
|
||||
{
|
||||
static uint8_t data_buf[BUF_SIZE];
|
||||
const TickType_t MIN_POLLING_INTERVAL = pdMS_TO_TICKS(1);
|
||||
const TickType_t MAX_POLLING_INTERVAL = pdMS_TO_TICKS(10);
|
||||
const TickType_t READ_TIMEOUT = pdMS_TO_TICKS(5);
|
||||
|
||||
|
||||
TickType_t current_interval = MIN_POLLING_INTERVAL;
|
||||
int consecutive_empty_polls = 0;
|
||||
int cached_client_fd = -1;
|
||||
TickType_t last_client_check = 0;
|
||||
const TickType_t CLIENT_CHECK_INTERVAL = pdMS_TO_TICKS(100);
|
||||
|
||||
while(1) {
|
||||
while (1)
|
||||
{
|
||||
TickType_t current_time = xTaskGetTickCount();
|
||||
|
||||
if (current_time - last_client_check >= CLIENT_CHECK_INTERVAL) {
|
||||
if (current_time - last_client_check >= CLIENT_CHECK_INTERVAL)
|
||||
{
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
cached_client_fd = client_fd;
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
@@ -139,20 +162,25 @@ static void uart_polling_task(void *arg)
|
||||
|
||||
size_t available_len;
|
||||
esp_err_t err = uart_get_buffered_data_len(UART_NUM, &available_len);
|
||||
|
||||
if (err != ESP_OK || available_len == 0) {
|
||||
|
||||
if (err != ESP_OK || available_len == 0)
|
||||
{
|
||||
consecutive_empty_polls++;
|
||||
if (consecutive_empty_polls > 5) {
|
||||
if (consecutive_empty_polls > 5)
|
||||
{
|
||||
current_interval = MAX_POLLING_INTERVAL;
|
||||
} else if (consecutive_empty_polls > 2) {
|
||||
}
|
||||
else if (consecutive_empty_polls > 2)
|
||||
{
|
||||
current_interval = pdMS_TO_TICKS(5);
|
||||
}
|
||||
|
||||
if (cached_client_fd <= 0) {
|
||||
|
||||
if (cached_client_fd <= 0)
|
||||
{
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
vTaskDelay(current_interval);
|
||||
continue;
|
||||
}
|
||||
@@ -160,72 +188,88 @@ static void uart_polling_task(void *arg)
|
||||
consecutive_empty_polls = 0;
|
||||
current_interval = MIN_POLLING_INTERVAL;
|
||||
|
||||
if (cached_client_fd <= 0) {
|
||||
if (cached_client_fd <= 0)
|
||||
{
|
||||
uart_flush_input(UART_NUM);
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t total_processed = 0;
|
||||
while (available_len > 0 && total_processed < BUF_SIZE) {
|
||||
size_t read_size = (available_len > (BUF_SIZE - total_processed)) ?
|
||||
(BUF_SIZE - total_processed) : available_len;
|
||||
|
||||
int bytes_read = uart_read_bytes(UART_NUM, data_buf + total_processed,
|
||||
read_size, READ_TIMEOUT);
|
||||
|
||||
if (bytes_read <= 0) {
|
||||
while (available_len > 0 && total_processed < BUF_SIZE)
|
||||
{
|
||||
size_t read_size = (available_len > (BUF_SIZE - total_processed))
|
||||
? (BUF_SIZE - total_processed)
|
||||
: available_len;
|
||||
|
||||
int bytes_read = uart_read_bytes(UART_NUM, data_buf + total_processed,
|
||||
read_size, READ_TIMEOUT);
|
||||
|
||||
if (bytes_read <= 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
total_processed += bytes_read;
|
||||
available_len -= bytes_read;
|
||||
|
||||
uart_get_buffered_data_len(UART_NUM, &available_len);
|
||||
}
|
||||
|
||||
if (total_processed > 0) {
|
||||
if (total_processed > 0)
|
||||
{
|
||||
size_t offset = 0;
|
||||
|
||||
while (offset < total_processed) {
|
||||
const size_t chunk_size = (total_processed - offset > CHUNK_SIZE) ?
|
||||
CHUNK_SIZE : (total_processed - offset);
|
||||
|
||||
|
||||
while (offset < total_processed)
|
||||
{
|
||||
const size_t chunk_size = (total_processed - offset > CHUNK_SIZE)
|
||||
? CHUNK_SIZE
|
||||
: (total_processed - offset);
|
||||
|
||||
struct ws_message msg;
|
||||
msg.type = WS_MSG_UART;
|
||||
msg.content.uart.data = malloc(chunk_size);
|
||||
if (!msg.content.uart.data) {
|
||||
if (!msg.content.uart.data)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for uart ws msg");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
memcpy(msg.content.uart.data, data_buf + offset, chunk_size);
|
||||
msg.content.uart.len = chunk_size;
|
||||
|
||||
if (xQueueSend(ws_queue, &msg, 0) != pdPASS) {
|
||||
if (xQueueSend(ws_queue, &msg, pdMS_TO_TICKS(5)) != pdPASS) {
|
||||
if (xQueueSend(ws_queue, &msg, 0) != pdPASS)
|
||||
{
|
||||
if (xQueueSend(ws_queue, &msg, pdMS_TO_TICKS(5)) != pdPASS)
|
||||
{
|
||||
ESP_LOGW(TAG, "ws sender queue full, dropping %zu bytes", chunk_size);
|
||||
free(msg.content.uart.data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
offset += chunk_size;
|
||||
}
|
||||
}
|
||||
|
||||
if (available_len > 0) {
|
||||
if (available_len > 0)
|
||||
{
|
||||
vTaskDelay(MIN_POLLING_INTERVAL);
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
vTaskDelay(current_interval);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
static esp_err_t ws_handler(httpd_req_t *req) {
|
||||
if (req->method == HTTP_GET) {
|
||||
static esp_err_t ws_handler(httpd_req_t* req)
|
||||
{
|
||||
if (req->method == HTTP_GET)
|
||||
{
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
if (client_fd > 0) {
|
||||
if (client_fd > 0)
|
||||
{
|
||||
// A client is already connected. Reject the new connection.
|
||||
ESP_LOGW(TAG, "Another client tried to connect, but a session is already active. Rejecting.");
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
@@ -251,17 +295,19 @@ static esp_err_t ws_handler(httpd_req_t *req) {
|
||||
ws_pkt.type = HTTPD_WS_TYPE_BINARY;
|
||||
|
||||
esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, BUF_SIZE);
|
||||
if (ret != ESP_OK) {
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "httpd_ws_recv_frame failed with error: %s", esp_err_to_name(ret));
|
||||
xSemaphoreTake(client_fd_mutex, portMAX_DELAY);
|
||||
if (httpd_req_to_sockfd(req) == client_fd) {
|
||||
if (httpd_req_to_sockfd(req) == client_fd)
|
||||
{
|
||||
client_fd = -1;
|
||||
}
|
||||
xSemaphoreGive(client_fd_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
uart_write_bytes(UART_NUM, (const char *)ws_pkt.payload, ws_pkt.len);
|
||||
uart_write_bytes(UART_NUM, (const char*)ws_pkt.payload, ws_pkt.len);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -288,10 +334,10 @@ void register_ws_endpoint(httpd_handle_t server)
|
||||
ESP_ERROR_CHECK(uart_driver_install(UART_NUM, BUF_SIZE * 2, BUF_SIZE * 2, 0, NULL, 0));
|
||||
|
||||
httpd_uri_t ws = {
|
||||
.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = ws_handler,
|
||||
.user_ctx = NULL,
|
||||
.uri = "/ws",
|
||||
.method = HTTP_GET,
|
||||
.handler = ws_handler,
|
||||
.user_ctx = NULL,
|
||||
.is_websocket = true
|
||||
};
|
||||
httpd_register_uri_handler(server, &ws);
|
||||
@@ -299,11 +345,11 @@ void register_ws_endpoint(httpd_handle_t server)
|
||||
client_fd_mutex = xSemaphoreCreateMutex();
|
||||
ws_queue = xQueueCreate(10, sizeof(struct ws_message)); // Combined queue
|
||||
|
||||
xTaskCreate(uart_polling_task, "uart_polling_task", 1024*4, NULL, 8, NULL);
|
||||
xTaskCreate(unified_ws_sender_task, "ws_sender_task", 1024*6, server, 9, NULL);
|
||||
xTaskCreate(uart_polling_task, "uart_polling_task", 1024 * 4, NULL, 8, NULL);
|
||||
xTaskCreate(unified_ws_sender_task, "ws_sender_task", 1024 * 6, server, 9, NULL);
|
||||
}
|
||||
|
||||
void push_data_to_ws(cJSON *data)
|
||||
void push_data_to_ws(cJSON* data)
|
||||
{
|
||||
struct ws_message msg;
|
||||
msg.type = WS_MSG_STATUS;
|
||||
|
||||
Reference in New Issue
Block a user