| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531 |
- /**
- * @file web_server.c
- * @brief Mongoose 7.22 HTTP Web Server - MPPT Monitor
- *
- * REST API endpoints:
- * GET /api/data - All devices real-time data (complete)
- * GET /api/devices - Device list with status
- * GET /api/status - System status
- * GET /api/health - Health check
- * GET /api/history - Historical data (paginated)
- * GET /api/history/stats - Database statistics
- * GET / - HTML Dashboard (multi-device)
- */
- #include "web_server.h"
- #include "../lib/mongoose-7.22/mongoose.h"
- #include "../lib/cJSON-1.7.19/cJSON.h"
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <time.h>
- /* ---- Global context ---- */
- static web_ctx_t g_ctx;
- static struct mg_mgr s_mgr;
- static bool g_running = false;
- /* ---- Forward declarations ---- */
- static void handle_api_data(struct mg_connection *c, struct mg_http_message *hm);
- static void handle_api_devices(struct mg_connection *c, struct mg_http_message *hm);
- static void handle_api_status(struct mg_connection *c, struct mg_http_message *hm);
- 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,
- const char *key, char *buf, size_t buf_size)
- {
- buf[0] = '\0';
- mg_http_get_var(&hm->query, key, buf, buf_size);
- }
- /* ---- Helper: build JSON for one device (complete data) ---- */
- static cJSON *device_to_json(const mppt_device_data_t *dev)
- {
- cJSON *root = cJSON_CreateObject();
- cJSON_AddNumberToObject(root, "device_addr", dev->device_addr);
- cJSON_AddBoolToObject(root, "online", dev->online);
- cJSON_AddNumberToObject(root, "last_update", (double)dev->last_update);
- cJSON_AddNumberToObject(root, "read_error_count", dev->read_error_count);
- /* Input Registers */
- cJSON *input = cJSON_AddObjectToObject(root, "input_registers");
- cJSON_AddNumberToObject(input, "battery_voltage", dev->battery_voltage);
- cJSON_AddNumberToObject(input, "pv_voltage", dev->pv_voltage);
- cJSON_AddNumberToObject(input, "pv_charge_current", dev->pv_charge_current);
- cJSON_AddNumberToObject(input, "pv_input_power", dev->pv_input_power);
- cJSON_AddNumberToObject(input, "cumulative_energy", dev->cumulative_energy);
- cJSON_AddNumberToObject(input, "machine_temp", dev->machine_temp);
- cJSON_AddNumberToObject(input, "fault_code", dev->fault_code_raw);
- cJSON_AddStringToObject(input, "fault_desc", modbus_fault_desc(dev->fault_code_raw));
- cJSON_AddNumberToObject(input, "battery_soc", dev->battery_soc);
- cJSON_AddNumberToObject(input, "battery_string_count", dev->battery_string_count);
- cJSON_AddNumberToObject(input, "charge_state", dev->charge_state_raw);
- cJSON_AddStringToObject(input, "charge_state_desc", modbus_charge_state_str(dev->charge_state_raw));
- cJSON_AddNumberToObject(input, "device_type", dev->device_type_raw);
- cJSON_AddNumberToObject(input, "software_version", dev->software_version_raw);
- /* Output Registers */
- cJSON *output = cJSON_AddObjectToObject(root, "output_registers");
- cJSON_AddNumberToObject(output, "battery_type", dev->battery_type_raw);
- cJSON_AddStringToObject(output, "battery_type_desc", modbus_battery_type_str(dev->battery_type_raw));
- cJSON_AddNumberToObject(output, "battery_level", dev->battery_level_raw);
- cJSON_AddStringToObject(output, "battery_level_desc", modbus_battery_level_str(dev->battery_level_raw));
- cJSON_AddNumberToObject(output, "pv_charge_current_limit", dev->pv_charge_current_limit);
- cJSON_AddNumberToObject(output, "batt_over_discharge_v", dev->batt_over_discharge_v * 0.1);
- cJSON_AddNumberToObject(output, "batt_boost_voltage", dev->batt_boost_voltage * 0.1);
- cJSON_AddNumberToObject(output, "batt_full_voltage", dev->batt_full_voltage * 0.1);
- cJSON_AddNumberToObject(output, "batt_recovery_voltage", dev->batt_recovery_voltage * 0.1);
- cJSON_AddNumberToObject(output, "device_485_addr", dev->device_485_addr);
- cJSON_AddNumberToObject(output, "charge_mode", dev->charge_mode_raw);
- cJSON_AddStringToObject(output, "charge_mode_desc", modbus_charge_mode_str(dev->charge_mode_raw));
- return root;
- }
- /* ---- GET /api/data ---- */
- static void handle_api_data(struct mg_connection *c, struct mg_http_message *hm)
- {
- mppt_device_data_t devices[MODBUS_MAX_DEVICES];
- int count = 0;
- modbus_get_all_data(g_ctx.modbus, devices, &count);
- cJSON *root = cJSON_CreateObject();
- cJSON_AddNumberToObject(root, "device_count", count);
- cJSON *arr = cJSON_AddArrayToObject(root, "devices");
- for (int i = 0; i < count; i++) {
- cJSON_AddItemToArray(arr, device_to_json(&devices[i]));
- }
- 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);
- }
- /* ---- 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;
- static void load_html_dashboard(void)
- {
- FILE *fp = fopen("www/index.html", "r");
- if (!fp) {
- fprintf(stderr, "[WEB] Warning: www/index.html not found, using built-in\n");
- return;
- }
- fseek(fp, 0, SEEK_END);
- long size = ftell(fp);
- fseek(fp, 0, SEEK_SET);
- char *buf = (char *)malloc(size + 1);
- if (buf) {
- size_t nread = fread(buf, 1, size, fp);
- (void)nread;
- buf[size] = '\0';
- HTML_DASHBOARD = buf;
- printf("[WEB] Loaded dashboard HTML: %ld bytes\n", size);
- }
- fclose(fp);
- }
- /* ---- HTTP Event Handler ---- */
- static void event_handler(struct mg_connection *c, int ev, void *ev_data)
- {
- if (ev == MG_EV_HTTP_MSG) {
- struct mg_http_message *hm = (struct mg_http_message *)ev_data;
- /* API routes */
- if (mg_match(hm->uri, mg_str("/api/data"), NULL)) {
- handle_api_data(c, hm);
- } else if (mg_match(hm->uri, mg_str("/api/devices"), NULL)) {
- handle_api_devices(c, hm);
- } else if (mg_match(hm->uri, mg_str("/api/status"), NULL)) {
- handle_api_status(c, hm);
- } else if (mg_match(hm->uri, mg_str("/api/health"), NULL)) {
- handle_api_health(c, hm);
- } else if (mg_match(hm->uri, mg_str("/api/history/stats"), NULL)) {
- 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) {
- mg_http_reply(c, 200, "Content-Type: text/html; charset=utf-8\r\n",
- "%s", HTML_DASHBOARD);
- } else {
- mg_http_reply(c, 200, "Content-Type: text/html\r\n",
- "<html><body><h1>MPPT Monitor</h1>"
- "<p>Dashboard HTML not found. Place index.html in www/ directory.</p>"
- "<p>API: <a href='/api/data'>/api/data</a> | "
- "<a href='/api/devices'>/api/devices</a> | "
- "<a href='/api/status'>/api/status</a> | "
- "<a href='/api/health'>/api/health</a> | "
- "<a href='/api/history'>/api/history</a></p>"
- "</body></html>");
- }
- } else {
- mg_http_reply(c, 404, "", "Not Found");
- }
- }
- }
- /* ---- Web server thread ---- */
- static void *web_thread_func(void *arg)
- {
- uint16_t port = *(uint16_t *)arg;
- char url[64];
- snprintf(url, sizeof(url), "http://0.0.0.0:%d", port);
- mg_mgr_init(&s_mgr);
- load_html_dashboard();
- struct mg_connection *listener = mg_http_listen(&s_mgr, url, event_handler, NULL);
- if (!listener) {
- fprintf(stderr, "[WEB] Failed to start HTTP listener on %s\n", url);
- return NULL;
- }
- printf("[WEB] HTTP server started on %s\n", url);
- while (g_running) {
- mg_mgr_poll(&s_mgr, 200);
- }
- mg_mgr_free(&s_mgr);
- return NULL;
- }
- /* ---- Public API ---- */
- int web_server_start(uint16_t port,
- modbus_client_t *modbus,
- db_handle_t *db,
- app_config_t *config)
- {
- memset(&g_ctx, 0, sizeof(g_ctx));
- g_ctx.modbus = modbus;
- g_ctx.db = db;
- g_ctx.config = config;
- g_ctx.running = true;
- g_running = true;
- static uint16_t s_port;
- s_port = port;
- int ret = pthread_create(&g_ctx.thread, NULL, web_thread_func, &s_port);
- if (ret != 0) {
- fprintf(stderr, "[WEB] Failed to create web thread: %s\n", strerror(ret));
- g_running = false;
- return -1;
- }
- return 0;
- }
- void web_server_stop(void)
- {
- g_running = false;
- g_ctx.running = false;
- if (g_ctx.thread) {
- pthread_join(g_ctx.thread, NULL);
- }
- if (HTML_DASHBOARD) {
- free((void *)HTML_DASHBOARD);
- HTML_DASHBOARD = NULL;
- }
- }
- /* ---- GET /api/devices ---- */
- static void handle_api_devices(struct mg_connection *c, struct mg_http_message *hm)
- {
- mppt_device_data_t devices[MODBUS_MAX_DEVICES];
- int count = 0;
- modbus_get_all_data(g_ctx.modbus, devices, &count);
- cJSON *root = cJSON_CreateObject();
- cJSON_AddNumberToObject(root, "total", count);
- cJSON *arr = cJSON_AddArrayToObject(root, "devices");
- for (int i = 0; i < count; i++) {
- cJSON *dev = cJSON_CreateObject();
- cJSON_AddNumberToObject(dev, "addr", devices[i].device_addr);
- cJSON_AddBoolToObject(dev, "online", devices[i].online);
- cJSON_AddNumberToObject(dev, "battery_voltage", devices[i].battery_voltage);
- cJSON_AddNumberToObject(dev, "battery_soc", devices[i].battery_soc);
- cJSON_AddNumberToObject(dev, "pv_power", devices[i].pv_input_power);
- cJSON_AddNumberToObject(dev, "machine_temp", devices[i].machine_temp);
- cJSON_AddStringToObject(dev, "fault_desc", modbus_fault_desc(devices[i].fault_code_raw));
- cJSON_AddStringToObject(dev, "charge_state", modbus_charge_state_str(devices[i].charge_state_raw));
- cJSON_AddStringToObject(dev, "battery_type", modbus_battery_type_str(devices[i].battery_type_raw));
- cJSON_AddNumberToObject(dev, "software_version", devices[i].software_version_raw);
- cJSON_AddItemToArray(arr, dev);
- }
- 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);
- }
- /* ---- GET /api/status ---- */
- static void handle_api_status(struct mg_connection *c, struct mg_http_message *hm)
- {
- mppt_device_data_t devices[MODBUS_MAX_DEVICES];
- int count = 0;
- modbus_get_all_data(g_ctx.modbus, devices, &count);
- int online_count = 0;
- for (int i = 0; i < count; i++) {
- if (devices[i].online) online_count++;
- }
- cJSON *root = cJSON_CreateObject();
- cJSON_AddStringToObject(root, "status", online_count > 0 ? "ok" : "degraded");
- cJSON_AddBoolToObject(root, "modbus_connected", g_ctx.modbus->connected);
- cJSON_AddNumberToObject(root, "device_total", count);
- cJSON_AddNumberToObject(root, "device_online", online_count);
- cJSON_AddNumberToObject(root, "db_records", db_get_record_count(g_ctx.db));
- 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);
- }
- /* ---- GET /api/health ---- */
- static void handle_api_health(struct mg_connection *c, struct mg_http_message *hm)
- {
- mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{\"status\":\"ok\"}");
- }
- /* ---- GET /api/history ---- */
- static void handle_api_history(struct mg_connection *c, struct mg_http_message *hm)
- {
- char start_time[64] = {0}, end_time[64] = {0};
- char addr_str[16] = {0}, page_str[16] = {0}, size_str[16] = {0}, sort_str[16] = {0};
- get_query_param(hm, "start_time", start_time, sizeof(start_time));
- get_query_param(hm, "end_time", end_time, sizeof(end_time));
- get_query_param(hm, "device_addr", addr_str, sizeof(addr_str));
- get_query_param(hm, "page", page_str, sizeof(page_str));
- get_query_param(hm, "page_size", size_str, sizeof(size_str));
- get_query_param(hm, "sort", sort_str, sizeof(sort_str));
- uint8_t device_addr = addr_str[0] ? (uint8_t)atoi(addr_str) : 0;
- int page = page_str[0] ? atoi(page_str) : 1;
- int page_size = size_str[0] ? atoi(size_str) : g_ctx.config->web_page_size;
- bool sort_asc = (strcmp(sort_str, "asc") == 0);
- if (page < 1) page = 1;
- if (page_size < 1) page_size = 20;
- if (page_size > 200) page_size = 200;
- db_query_result_t result;
- int rc = db_query_history(g_ctx.db, device_addr,
- start_time[0] ? start_time : NULL,
- end_time[0] ? end_time : NULL,
- page, page_size, sort_asc, &result);
- cJSON *root = cJSON_CreateObject();
- if (rc != 0) {
- cJSON_AddStringToObject(root, "error", "Query failed");
- } else {
- cJSON_AddNumberToObject(root, "total", result.total);
- cJSON_AddNumberToObject(root, "page", result.page);
- cJSON_AddNumberToObject(root, "page_size", result.page_size);
- cJSON_AddNumberToObject(root, "total_pages", result.total_pages);
- cJSON *arr = cJSON_AddArrayToObject(root, "records");
- for (int i = 0; i < result.count; i++) {
- db_history_record_t *r = &result.records[i];
- cJSON *rec = cJSON_CreateObject();
- cJSON_AddNumberToObject(rec, "id", r->id);
- cJSON_AddStringToObject(rec, "timestamp", r->timestamp);
- cJSON_AddNumberToObject(rec, "device_addr", r->device_addr);
- cJSON_AddNumberToObject(rec, "battery_voltage", r->battery_voltage);
- cJSON_AddNumberToObject(rec, "pv_voltage", r->pv_voltage);
- cJSON_AddNumberToObject(rec, "pv_charge_current", r->pv_charge_current);
- cJSON_AddNumberToObject(rec, "pv_input_power", r->pv_input_power);
- cJSON_AddNumberToObject(rec, "cumulative_energy", r->cumulative_energy);
- cJSON_AddNumberToObject(rec, "machine_temp", r->machine_temp);
- cJSON_AddNumberToObject(rec, "fault_code", r->fault_code);
- cJSON_AddStringToObject(rec, "fault_desc", modbus_fault_desc(r->fault_code));
- cJSON_AddNumberToObject(rec, "battery_soc", r->battery_soc);
- cJSON_AddNumberToObject(rec, "charge_state", r->charge_state);
- cJSON_AddNumberToObject(rec, "device_type", r->device_type);
- cJSON_AddNumberToObject(rec, "software_version", r->software_version);
- cJSON_AddNumberToObject(rec, "battery_type", r->battery_type);
- cJSON_AddStringToObject(rec, "battery_type_desc", modbus_battery_type_str(r->battery_type));
- cJSON_AddNumberToObject(rec, "battery_level", r->battery_level);
- cJSON_AddStringToObject(rec, "battery_level_desc", modbus_battery_level_str(r->battery_level));
- cJSON_AddNumberToObject(rec, "charge_mode", r->charge_mode);
- cJSON_AddStringToObject(rec, "charge_mode_desc", modbus_charge_mode_str(r->charge_mode));
- cJSON_AddItemToArray(arr, rec);
- }
- }
- 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);
- db_free_query_result(&result);
- }
- /* ---- GET /api/history/stats ---- */
- static void handle_api_history_stats(struct mg_connection *c, struct mg_http_message *hm)
- {
- db_stats_t stats;
- int rc = db_get_stats(g_ctx.db, &stats);
- cJSON *root = cJSON_CreateObject();
- if (rc != 0) {
- cJSON_AddStringToObject(root, "error", "Stats query failed");
- } else {
- cJSON_AddNumberToObject(root, "total_records", stats.total_records);
- cJSON_AddStringToObject(root, "first_record_time", stats.first_record_time);
- cJSON_AddStringToObject(root, "last_record_time", stats.last_record_time);
- cJSON_AddNumberToObject(root, "device_count", stats.device_count);
- cJSON_AddNumberToObject(root, "max_pv_power", stats.max_pv_power);
- cJSON_AddNumberToObject(root, "max_battery_voltage", stats.max_battery_voltage);
- cJSON_AddNumberToObject(root, "max_temperature", stats.max_temperature);
- }
- 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);
- }
|