|
|
@@ -32,6 +32,7 @@ static void handle_api_status(struct mg_connection *c, struct mg_http_message *h
|
|
|
static void handle_api_health(struct mg_connection *c, struct mg_http_message *hm);
|
|
|
static void handle_api_history(struct mg_connection *c, struct mg_http_message *hm);
|
|
|
static void handle_api_history_stats(struct mg_connection *c, struct mg_http_message *hm);
|
|
|
+static void handle_api_settings(struct mg_connection *c, struct mg_http_message *hm);
|
|
|
|
|
|
/* ---- Helper: get query parameter ---- */
|
|
|
static void get_query_param(struct mg_http_message *hm,
|
|
|
@@ -105,6 +106,143 @@ static void handle_api_data(struct mg_connection *c, struct mg_http_message *hm)
|
|
|
cJSON_Delete(root);
|
|
|
}
|
|
|
|
|
|
+/* ---- Handle POST /api/settings - Write device configuration ---- */
|
|
|
+static void handle_api_settings(struct mg_connection *c, struct mg_http_message *hm)
|
|
|
+{
|
|
|
+ /* Only accept POST */
|
|
|
+ if (mg_match(hm->method, mg_str("GET"), NULL)) {
|
|
|
+ /* GET returns current settings info */
|
|
|
+ cJSON *root = cJSON_CreateObject();
|
|
|
+ cJSON_AddStringToObject(root, "method", "GET");
|
|
|
+ cJSON_AddStringToObject(root, "description", "Use POST to write device settings");
|
|
|
+ cJSON *writable = cJSON_CreateArray();
|
|
|
+ cJSON_AddItemToArray(writable, cJSON_CreateString("battery_type (0-5): 0=胶体 1=密封 2=磷酸铁锂 3=三元锂电 4=自定义 5=加水"));
|
|
|
+ cJSON_AddItemToArray(writable, cJSON_CreateString("battery_level (0-6): 0=自动识别 1=12V 2=24V 3=36V 4=48V 5=60V 6=72V"));
|
|
|
+ cJSON_AddItemToArray(writable, cJSON_CreateString("charge_current_limit (5-100): PV充电电流限制%"));
|
|
|
+ cJSON_AddItemToArray(writable, cJSON_CreateString("full_charge_voltage (80-600): 充满电压0.1V,仅自定义电池可写"));
|
|
|
+ cJSON_AddItemToArray(writable, cJSON_CreateString("device_address (0-247): 485设备地址"));
|
|
|
+ cJSON_AddItemToArray(writable, cJSON_CreateString("charge_mode (0-1): 0=MPPT 1=DCDC"));
|
|
|
+ cJSON_AddItemToObject(root, "writable_registers", writable);
|
|
|
+ char *json_str = cJSON_PrintUnformatted(root);
|
|
|
+ mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n", "%s", json_str);
|
|
|
+ free(json_str);
|
|
|
+ cJSON_Delete(root);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!mg_match(hm->method, mg_str("POST"), NULL)) {
|
|
|
+ mg_http_reply(c, 405, "Content-Type: application/json\r\n", "{\"error\":\"Method not allowed. Use POST.\"}");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Parse JSON body */
|
|
|
+ char body_buf[2048];
|
|
|
+ size_t body_len = (hm->body.len < sizeof(body_buf) - 1) ? hm->body.len : sizeof(body_buf) - 1;
|
|
|
+ memcpy(body_buf, hm->body.buf, body_len);
|
|
|
+ body_buf[body_len] = '\0';
|
|
|
+
|
|
|
+ cJSON *req = cJSON_Parse(body_buf);
|
|
|
+ if (!req) {
|
|
|
+ mg_http_reply(c, 400, "Content-Type: application/json\r\n", "{\"error\":\"Invalid JSON body\"}");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Extract parameters */
|
|
|
+ cJSON *j_addr = cJSON_GetObjectItem(req, "device_addr");
|
|
|
+ cJSON *j_param = cJSON_GetObjectItem(req, "param");
|
|
|
+ cJSON *j_value = cJSON_GetObjectItem(req, "value");
|
|
|
+
|
|
|
+ if (!cJSON_IsNumber(j_addr) || !cJSON_IsString(j_param) || !cJSON_IsNumber(j_value)) {
|
|
|
+ cJSON_Delete(req);
|
|
|
+ mg_http_reply(c, 400, "Content-Type: application/json\r\n",
|
|
|
+ "{\"error\":\"Missing fields: device_addr(int), param(string), value(int)\"}");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ uint8_t dev_addr = (uint8_t)j_addr->valueint;
|
|
|
+ const char *param = j_param->valuestring;
|
|
|
+ uint16_t value = (uint16_t)j_value->valueint;
|
|
|
+
|
|
|
+ /* Map parameter name to register address */
|
|
|
+ uint16_t reg_addr = 0xFFFF;
|
|
|
+ const char *param_desc = "";
|
|
|
+ if (strcmp(param, "battery_type") == 0) {
|
|
|
+ reg_addr = 0x0000;
|
|
|
+ param_desc = "电池类型";
|
|
|
+ } else if (strcmp(param, "battery_level") == 0) {
|
|
|
+ reg_addr = 0x0001;
|
|
|
+ param_desc = "电池等级";
|
|
|
+ } else if (strcmp(param, "charge_current_limit") == 0) {
|
|
|
+ reg_addr = 0x0002;
|
|
|
+ param_desc = "充电电流限制";
|
|
|
+ } else if (strcmp(param, "full_charge_voltage") == 0) {
|
|
|
+ reg_addr = 0x0005;
|
|
|
+ param_desc = "充满电压";
|
|
|
+ } else if (strcmp(param, "device_address") == 0) {
|
|
|
+ reg_addr = 0x0007;
|
|
|
+ param_desc = "设备485地址";
|
|
|
+ } else if (strcmp(param, "charge_mode") == 0) {
|
|
|
+ reg_addr = 0x0008;
|
|
|
+ param_desc = "充电模式";
|
|
|
+ } else {
|
|
|
+ cJSON_Delete(req);
|
|
|
+ mg_http_reply(c, 400, "Content-Type: application/json\r\n",
|
|
|
+ "{\"error\":\"Unknown param. Valid: battery_type, battery_level, "
|
|
|
+ "charge_current_limit, full_charge_voltage, device_address, charge_mode\"}");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Get current battery type for validation */
|
|
|
+ mppt_device_data_t dev_data;
|
|
|
+ uint16_t current_battery_type = 4; /* default to custom */
|
|
|
+ if (modbus_get_device_data(g_ctx.modbus, dev_addr, &dev_data)) {
|
|
|
+ current_battery_type = dev_data.battery_type_raw;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Validate */
|
|
|
+ if (modbus_validate_setting(reg_addr, value, current_battery_type) != 0) {
|
|
|
+ cJSON_Delete(req);
|
|
|
+ char err_msg[256];
|
|
|
+ snprintf(err_msg, sizeof(err_msg),
|
|
|
+ "{\"error\":\"Invalid value for %s (reg 0x%04X). Value %u out of range.\"}",
|
|
|
+ param, reg_addr, value);
|
|
|
+ mg_http_reply(c, 400, "Content-Type: application/json\r\n", "%s", err_msg);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Check if Modbus is connected */
|
|
|
+ if (!g_ctx.modbus || !g_ctx.modbus->connected) {
|
|
|
+ cJSON_Delete(req);
|
|
|
+ mg_http_reply(c, 503, "Content-Type: application/json\r\n",
|
|
|
+ "{\"error\":\"Modbus not connected\"}");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Write register */
|
|
|
+ int ret = modbus_write_register(g_ctx.modbus, dev_addr, reg_addr, value);
|
|
|
+ cJSON_Delete(req);
|
|
|
+
|
|
|
+ if (ret == 0) {
|
|
|
+ char resp[512];
|
|
|
+ snprintf(resp, sizeof(resp),
|
|
|
+ "{\"success\":true,\"device_addr\":%d,\"param\":\"%s\","
|
|
|
+ "\"param_desc\":\"%s\",\"reg_addr\":\"0x%04X\",\"value\":%d,"
|
|
|
+ "\"message\":\"写入成功\"}",
|
|
|
+ dev_addr, param, param_desc, reg_addr, value);
|
|
|
+ mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n",
|
|
|
+ "%s", resp);
|
|
|
+ printf("[WEB] Settings write success: dev %d, %s (0x%04X) = %u\n",
|
|
|
+ dev_addr, param, reg_addr, value);
|
|
|
+ } else {
|
|
|
+ char resp[256];
|
|
|
+ snprintf(resp, sizeof(resp),
|
|
|
+ "{\"success\":false,\"error\":\"Modbus写入失败,请检查设备连接\","
|
|
|
+ "\"device_addr\":%d,\"param\":\"%s\"}",
|
|
|
+ dev_addr, param);
|
|
|
+ mg_http_reply(c, 500, "Content-Type: application/json\r\n", "%s", resp);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
/* ---- Embedded HTML Dashboard ---- */
|
|
|
static const char *HTML_DASHBOARD = NULL;
|
|
|
|
|
|
@@ -148,6 +286,8 @@ static void event_handler(struct mg_connection *c, int ev, void *ev_data)
|
|
|
handle_api_history_stats(c, hm);
|
|
|
} else if (mg_match(hm->uri, mg_str("/api/history"), NULL)) {
|
|
|
handle_api_history(c, hm);
|
|
|
+ } else if (mg_match(hm->uri, mg_str("/api/settings"), NULL)) {
|
|
|
+ handle_api_settings(c, hm);
|
|
|
} else if (mg_match(hm->uri, mg_str("/"), NULL)) {
|
|
|
/* Serve HTML dashboard */
|
|
|
if (HTML_DASHBOARD) {
|