web_server.c 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. /**
  2. * @file web_server.c
  3. * @brief Mongoose 7.22 HTTP Web Server - MPPT Monitor
  4. *
  5. * REST API endpoints:
  6. * GET /api/data - All devices real-time data (complete)
  7. * GET /api/devices - Device list with status
  8. * GET /api/status - System status
  9. * GET /api/health - Health check
  10. * GET /api/history - Historical data (paginated)
  11. * GET /api/history/stats - Database statistics
  12. * GET / - HTML Dashboard (multi-device)
  13. */
  14. #include "web_server.h"
  15. #include "../lib/mongoose-7.22/mongoose.h"
  16. #include "../lib/cJSON-1.7.19/cJSON.h"
  17. #include <stdio.h>
  18. #include <stdlib.h>
  19. #include <string.h>
  20. #include <time.h>
  21. /* ---- Global context ---- */
  22. static web_ctx_t g_ctx;
  23. static struct mg_mgr s_mgr;
  24. static bool g_running = false;
  25. /* ---- Forward declarations ---- */
  26. static void handle_api_data(struct mg_connection *c, struct mg_http_message *hm);
  27. static void handle_api_devices(struct mg_connection *c, struct mg_http_message *hm);
  28. static void handle_api_status(struct mg_connection *c, struct mg_http_message *hm);
  29. static void handle_api_health(struct mg_connection *c, struct mg_http_message *hm);
  30. static void handle_api_history(struct mg_connection *c, struct mg_http_message *hm);
  31. static void handle_api_history_stats(struct mg_connection *c, struct mg_http_message *hm);
  32. static void handle_api_settings(struct mg_connection *c, struct mg_http_message *hm);
  33. /* ---- Helper: get query parameter ---- */
  34. static void get_query_param(struct mg_http_message *hm,
  35. const char *key, char *buf, size_t buf_size)
  36. {
  37. buf[0] = '\0';
  38. mg_http_get_var(&hm->query, key, buf, buf_size);
  39. }
  40. /* ---- Helper: build JSON for one device (complete data) ---- */
  41. static cJSON *device_to_json(const mppt_device_data_t *dev)
  42. {
  43. cJSON *root = cJSON_CreateObject();
  44. cJSON_AddNumberToObject(root, "device_addr", dev->device_addr);
  45. cJSON_AddBoolToObject(root, "online", dev->online);
  46. cJSON_AddNumberToObject(root, "last_update", (double)dev->last_update);
  47. cJSON_AddNumberToObject(root, "read_error_count", dev->read_error_count);
  48. /* Input Registers */
  49. cJSON *input = cJSON_AddObjectToObject(root, "input_registers");
  50. cJSON_AddNumberToObject(input, "battery_voltage", dev->battery_voltage);
  51. cJSON_AddNumberToObject(input, "pv_voltage", dev->pv_voltage);
  52. cJSON_AddNumberToObject(input, "pv_charge_current", dev->pv_charge_current);
  53. cJSON_AddNumberToObject(input, "pv_input_power", dev->pv_input_power);
  54. cJSON_AddNumberToObject(input, "cumulative_energy", dev->cumulative_energy);
  55. cJSON_AddNumberToObject(input, "machine_temp", dev->machine_temp);
  56. cJSON_AddNumberToObject(input, "fault_code", dev->fault_code_raw);
  57. cJSON_AddStringToObject(input, "fault_desc", modbus_fault_desc(dev->fault_code_raw));
  58. cJSON_AddNumberToObject(input, "battery_soc", dev->battery_soc);
  59. cJSON_AddNumberToObject(input, "battery_string_count", dev->battery_string_count);
  60. cJSON_AddNumberToObject(input, "charge_state", dev->charge_state_raw);
  61. cJSON_AddStringToObject(input, "charge_state_desc", modbus_charge_state_str(dev->charge_state_raw));
  62. cJSON_AddNumberToObject(input, "device_type", dev->device_type_raw);
  63. cJSON_AddNumberToObject(input, "software_version", dev->software_version_raw);
  64. /* Output Registers */
  65. cJSON *output = cJSON_AddObjectToObject(root, "output_registers");
  66. cJSON_AddNumberToObject(output, "battery_type", dev->battery_type_raw);
  67. cJSON_AddStringToObject(output, "battery_type_desc", modbus_battery_type_str(dev->battery_type_raw));
  68. cJSON_AddNumberToObject(output, "battery_level", dev->battery_level_raw);
  69. cJSON_AddStringToObject(output, "battery_level_desc", modbus_battery_level_str(dev->battery_level_raw));
  70. cJSON_AddNumberToObject(output, "pv_charge_current_limit", dev->pv_charge_current_limit);
  71. cJSON_AddNumberToObject(output, "batt_over_discharge_v", dev->batt_over_discharge_v * 0.1);
  72. cJSON_AddNumberToObject(output, "batt_boost_voltage", dev->batt_boost_voltage * 0.1);
  73. cJSON_AddNumberToObject(output, "batt_full_voltage", dev->batt_full_voltage * 0.1);
  74. cJSON_AddNumberToObject(output, "batt_recovery_voltage", dev->batt_recovery_voltage * 0.1);
  75. cJSON_AddNumberToObject(output, "device_485_addr", dev->device_485_addr);
  76. cJSON_AddNumberToObject(output, "charge_mode", dev->charge_mode_raw);
  77. cJSON_AddStringToObject(output, "charge_mode_desc", modbus_charge_mode_str(dev->charge_mode_raw));
  78. return root;
  79. }
  80. /* ---- GET /api/data ---- */
  81. static void handle_api_data(struct mg_connection *c, struct mg_http_message *hm)
  82. {
  83. mppt_device_data_t devices[MODBUS_MAX_DEVICES];
  84. int count = 0;
  85. modbus_get_all_data(g_ctx.modbus, devices, &count);
  86. cJSON *root = cJSON_CreateObject();
  87. cJSON_AddNumberToObject(root, "device_count", count);
  88. cJSON *arr = cJSON_AddArrayToObject(root, "devices");
  89. for (int i = 0; i < count; i++) {
  90. cJSON_AddItemToArray(arr, device_to_json(&devices[i]));
  91. }
  92. char *json_str = cJSON_PrintUnformatted(root);
  93. mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n", "%s", json_str);
  94. free(json_str);
  95. cJSON_Delete(root);
  96. }
  97. /* ---- Handle POST /api/settings - Write device configuration ---- */
  98. static void handle_api_settings(struct mg_connection *c, struct mg_http_message *hm)
  99. {
  100. /* Only accept POST */
  101. if (mg_match(hm->method, mg_str("GET"), NULL)) {
  102. /* GET returns current settings info */
  103. cJSON *root = cJSON_CreateObject();
  104. cJSON_AddStringToObject(root, "method", "GET");
  105. cJSON_AddStringToObject(root, "description", "Use POST to write device settings");
  106. cJSON *writable = cJSON_CreateArray();
  107. cJSON_AddItemToArray(writable, cJSON_CreateString("battery_type (0-5): 0=胶体 1=密封 2=磷酸铁锂 3=三元锂电 4=自定义 5=加水"));
  108. cJSON_AddItemToArray(writable, cJSON_CreateString("battery_level (0-6): 0=自动识别 1=12V 2=24V 3=36V 4=48V 5=60V 6=72V"));
  109. cJSON_AddItemToArray(writable, cJSON_CreateString("charge_current_limit (5-100): PV充电电流限制%"));
  110. cJSON_AddItemToArray(writable, cJSON_CreateString("full_charge_voltage (80-600): 充满电压0.1V,仅自定义电池可写"));
  111. cJSON_AddItemToArray(writable, cJSON_CreateString("device_address (0-247): 485设备地址"));
  112. cJSON_AddItemToArray(writable, cJSON_CreateString("charge_mode (0-1): 0=MPPT 1=DCDC"));
  113. cJSON_AddItemToObject(root, "writable_registers", writable);
  114. char *json_str = cJSON_PrintUnformatted(root);
  115. mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n", "%s", json_str);
  116. free(json_str);
  117. cJSON_Delete(root);
  118. return;
  119. }
  120. if (!mg_match(hm->method, mg_str("POST"), NULL)) {
  121. mg_http_reply(c, 405, "Content-Type: application/json\r\n", "{\"error\":\"Method not allowed. Use POST.\"}");
  122. return;
  123. }
  124. /* Parse JSON body */
  125. char body_buf[2048];
  126. size_t body_len = (hm->body.len < sizeof(body_buf) - 1) ? hm->body.len : sizeof(body_buf) - 1;
  127. memcpy(body_buf, hm->body.buf, body_len);
  128. body_buf[body_len] = '\0';
  129. cJSON *req = cJSON_Parse(body_buf);
  130. if (!req) {
  131. mg_http_reply(c, 400, "Content-Type: application/json\r\n", "{\"error\":\"Invalid JSON body\"}");
  132. return;
  133. }
  134. /* Extract parameters */
  135. cJSON *j_addr = cJSON_GetObjectItem(req, "device_addr");
  136. cJSON *j_param = cJSON_GetObjectItem(req, "param");
  137. cJSON *j_value = cJSON_GetObjectItem(req, "value");
  138. if (!cJSON_IsNumber(j_addr) || !cJSON_IsString(j_param) || !cJSON_IsNumber(j_value)) {
  139. cJSON_Delete(req);
  140. mg_http_reply(c, 400, "Content-Type: application/json\r\n",
  141. "{\"error\":\"Missing fields: device_addr(int), param(string), value(int)\"}");
  142. return;
  143. }
  144. uint8_t dev_addr = (uint8_t)j_addr->valueint;
  145. const char *param = j_param->valuestring;
  146. uint16_t value = (uint16_t)j_value->valueint;
  147. /* Map parameter name to register address */
  148. uint16_t reg_addr = 0xFFFF;
  149. const char *param_desc = "";
  150. if (strcmp(param, "battery_type") == 0) {
  151. reg_addr = 0x0000;
  152. param_desc = "电池类型";
  153. } else if (strcmp(param, "battery_level") == 0) {
  154. reg_addr = 0x0001;
  155. param_desc = "电池等级";
  156. } else if (strcmp(param, "charge_current_limit") == 0) {
  157. reg_addr = 0x0002;
  158. param_desc = "充电电流限制";
  159. } else if (strcmp(param, "full_charge_voltage") == 0) {
  160. reg_addr = 0x0005;
  161. param_desc = "充满电压";
  162. } else if (strcmp(param, "device_address") == 0) {
  163. reg_addr = 0x0007;
  164. param_desc = "设备485地址";
  165. } else if (strcmp(param, "charge_mode") == 0) {
  166. reg_addr = 0x0008;
  167. param_desc = "充电模式";
  168. } else {
  169. cJSON_Delete(req);
  170. mg_http_reply(c, 400, "Content-Type: application/json\r\n",
  171. "{\"error\":\"Unknown param. Valid: battery_type, battery_level, "
  172. "charge_current_limit, full_charge_voltage, device_address, charge_mode\"}");
  173. return;
  174. }
  175. /* Get current battery type for validation */
  176. mppt_device_data_t dev_data;
  177. uint16_t current_battery_type = 4; /* default to custom */
  178. if (modbus_get_device_data(g_ctx.modbus, dev_addr, &dev_data)) {
  179. current_battery_type = dev_data.battery_type_raw;
  180. }
  181. /* Validate */
  182. if (modbus_validate_setting(reg_addr, value, current_battery_type) != 0) {
  183. cJSON_Delete(req);
  184. char err_msg[256];
  185. snprintf(err_msg, sizeof(err_msg),
  186. "{\"error\":\"Invalid value for %s (reg 0x%04X). Value %u out of range.\"}",
  187. param, reg_addr, value);
  188. mg_http_reply(c, 400, "Content-Type: application/json\r\n", "%s", err_msg);
  189. return;
  190. }
  191. /* Check if Modbus is connected */
  192. if (!g_ctx.modbus || !g_ctx.modbus->connected) {
  193. cJSON_Delete(req);
  194. mg_http_reply(c, 503, "Content-Type: application/json\r\n",
  195. "{\"error\":\"Modbus not connected\"}");
  196. return;
  197. }
  198. /* Write register */
  199. int ret = modbus_write_register(g_ctx.modbus, dev_addr, reg_addr, value);
  200. cJSON_Delete(req);
  201. if (ret == 0) {
  202. char resp[512];
  203. snprintf(resp, sizeof(resp),
  204. "{\"success\":true,\"device_addr\":%d,\"param\":\"%s\","
  205. "\"param_desc\":\"%s\",\"reg_addr\":\"0x%04X\",\"value\":%d,"
  206. "\"message\":\"写入成功\"}",
  207. dev_addr, param, param_desc, reg_addr, value);
  208. mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n",
  209. "%s", resp);
  210. printf("[WEB] Settings write success: dev %d, %s (0x%04X) = %u\n",
  211. dev_addr, param, reg_addr, value);
  212. } else {
  213. char resp[256];
  214. snprintf(resp, sizeof(resp),
  215. "{\"success\":false,\"error\":\"Modbus写入失败,请检查设备连接\","
  216. "\"device_addr\":%d,\"param\":\"%s\"}",
  217. dev_addr, param);
  218. mg_http_reply(c, 500, "Content-Type: application/json\r\n", "%s", resp);
  219. }
  220. }
  221. /* ---- Embedded HTML Dashboard ---- */
  222. static const char *HTML_DASHBOARD = NULL;
  223. static void load_html_dashboard(void)
  224. {
  225. FILE *fp = fopen("www/index.html", "r");
  226. if (!fp) {
  227. fprintf(stderr, "[WEB] Warning: www/index.html not found, using built-in\n");
  228. return;
  229. }
  230. fseek(fp, 0, SEEK_END);
  231. long size = ftell(fp);
  232. fseek(fp, 0, SEEK_SET);
  233. char *buf = (char *)malloc(size + 1);
  234. if (buf) {
  235. size_t nread = fread(buf, 1, size, fp);
  236. (void)nread;
  237. buf[size] = '\0';
  238. HTML_DASHBOARD = buf;
  239. printf("[WEB] Loaded dashboard HTML: %ld bytes\n", size);
  240. }
  241. fclose(fp);
  242. }
  243. /* ---- HTTP Event Handler ---- */
  244. static void event_handler(struct mg_connection *c, int ev, void *ev_data)
  245. {
  246. if (ev == MG_EV_HTTP_MSG) {
  247. struct mg_http_message *hm = (struct mg_http_message *)ev_data;
  248. /* API routes */
  249. if (mg_match(hm->uri, mg_str("/api/data"), NULL)) {
  250. handle_api_data(c, hm);
  251. } else if (mg_match(hm->uri, mg_str("/api/devices"), NULL)) {
  252. handle_api_devices(c, hm);
  253. } else if (mg_match(hm->uri, mg_str("/api/status"), NULL)) {
  254. handle_api_status(c, hm);
  255. } else if (mg_match(hm->uri, mg_str("/api/health"), NULL)) {
  256. handle_api_health(c, hm);
  257. } else if (mg_match(hm->uri, mg_str("/api/history/stats"), NULL)) {
  258. handle_api_history_stats(c, hm);
  259. } else if (mg_match(hm->uri, mg_str("/api/history"), NULL)) {
  260. handle_api_history(c, hm);
  261. } else if (mg_match(hm->uri, mg_str("/api/settings"), NULL)) {
  262. handle_api_settings(c, hm);
  263. } else if (mg_match(hm->uri, mg_str("/"), NULL)) {
  264. /* Serve HTML dashboard */
  265. if (HTML_DASHBOARD) {
  266. mg_http_reply(c, 200, "Content-Type: text/html; charset=utf-8\r\n",
  267. "%s", HTML_DASHBOARD);
  268. } else {
  269. mg_http_reply(c, 200, "Content-Type: text/html\r\n",
  270. "<html><body><h1>MPPT Monitor</h1>"
  271. "<p>Dashboard HTML not found. Place index.html in www/ directory.</p>"
  272. "<p>API: <a href='/api/data'>/api/data</a> | "
  273. "<a href='/api/devices'>/api/devices</a> | "
  274. "<a href='/api/status'>/api/status</a> | "
  275. "<a href='/api/health'>/api/health</a> | "
  276. "<a href='/api/history'>/api/history</a></p>"
  277. "</body></html>");
  278. }
  279. } else {
  280. mg_http_reply(c, 404, "", "Not Found");
  281. }
  282. }
  283. }
  284. /* ---- Web server thread ---- */
  285. static void *web_thread_func(void *arg)
  286. {
  287. uint16_t port = *(uint16_t *)arg;
  288. char url[64];
  289. snprintf(url, sizeof(url), "http://0.0.0.0:%d", port);
  290. mg_mgr_init(&s_mgr);
  291. load_html_dashboard();
  292. struct mg_connection *listener = mg_http_listen(&s_mgr, url, event_handler, NULL);
  293. if (!listener) {
  294. fprintf(stderr, "[WEB] Failed to start HTTP listener on %s\n", url);
  295. return NULL;
  296. }
  297. printf("[WEB] HTTP server started on %s\n", url);
  298. while (g_running) {
  299. mg_mgr_poll(&s_mgr, 200);
  300. }
  301. mg_mgr_free(&s_mgr);
  302. return NULL;
  303. }
  304. /* ---- Public API ---- */
  305. int web_server_start(uint16_t port,
  306. modbus_client_t *modbus,
  307. db_handle_t *db,
  308. app_config_t *config)
  309. {
  310. memset(&g_ctx, 0, sizeof(g_ctx));
  311. g_ctx.modbus = modbus;
  312. g_ctx.db = db;
  313. g_ctx.config = config;
  314. g_ctx.running = true;
  315. g_running = true;
  316. static uint16_t s_port;
  317. s_port = port;
  318. int ret = pthread_create(&g_ctx.thread, NULL, web_thread_func, &s_port);
  319. if (ret != 0) {
  320. fprintf(stderr, "[WEB] Failed to create web thread: %s\n", strerror(ret));
  321. g_running = false;
  322. return -1;
  323. }
  324. return 0;
  325. }
  326. void web_server_stop(void)
  327. {
  328. g_running = false;
  329. g_ctx.running = false;
  330. if (g_ctx.thread) {
  331. pthread_join(g_ctx.thread, NULL);
  332. }
  333. if (HTML_DASHBOARD) {
  334. free((void *)HTML_DASHBOARD);
  335. HTML_DASHBOARD = NULL;
  336. }
  337. }
  338. /* ---- GET /api/devices ---- */
  339. static void handle_api_devices(struct mg_connection *c, struct mg_http_message *hm)
  340. {
  341. mppt_device_data_t devices[MODBUS_MAX_DEVICES];
  342. int count = 0;
  343. modbus_get_all_data(g_ctx.modbus, devices, &count);
  344. cJSON *root = cJSON_CreateObject();
  345. cJSON_AddNumberToObject(root, "total", count);
  346. cJSON *arr = cJSON_AddArrayToObject(root, "devices");
  347. for (int i = 0; i < count; i++) {
  348. cJSON *dev = cJSON_CreateObject();
  349. cJSON_AddNumberToObject(dev, "addr", devices[i].device_addr);
  350. cJSON_AddBoolToObject(dev, "online", devices[i].online);
  351. cJSON_AddNumberToObject(dev, "battery_voltage", devices[i].battery_voltage);
  352. cJSON_AddNumberToObject(dev, "battery_soc", devices[i].battery_soc);
  353. cJSON_AddNumberToObject(dev, "pv_power", devices[i].pv_input_power);
  354. cJSON_AddNumberToObject(dev, "machine_temp", devices[i].machine_temp);
  355. cJSON_AddStringToObject(dev, "fault_desc", modbus_fault_desc(devices[i].fault_code_raw));
  356. cJSON_AddStringToObject(dev, "charge_state", modbus_charge_state_str(devices[i].charge_state_raw));
  357. cJSON_AddStringToObject(dev, "battery_type", modbus_battery_type_str(devices[i].battery_type_raw));
  358. cJSON_AddNumberToObject(dev, "software_version", devices[i].software_version_raw);
  359. cJSON_AddItemToArray(arr, dev);
  360. }
  361. char *json_str = cJSON_PrintUnformatted(root);
  362. mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n", "%s", json_str);
  363. free(json_str);
  364. cJSON_Delete(root);
  365. }
  366. /* ---- GET /api/status ---- */
  367. static void handle_api_status(struct mg_connection *c, struct mg_http_message *hm)
  368. {
  369. mppt_device_data_t devices[MODBUS_MAX_DEVICES];
  370. int count = 0;
  371. modbus_get_all_data(g_ctx.modbus, devices, &count);
  372. int online_count = 0;
  373. for (int i = 0; i < count; i++) {
  374. if (devices[i].online) online_count++;
  375. }
  376. cJSON *root = cJSON_CreateObject();
  377. cJSON_AddStringToObject(root, "status", online_count > 0 ? "ok" : "degraded");
  378. cJSON_AddBoolToObject(root, "modbus_connected", g_ctx.modbus->connected);
  379. cJSON_AddNumberToObject(root, "device_total", count);
  380. cJSON_AddNumberToObject(root, "device_online", online_count);
  381. cJSON_AddNumberToObject(root, "db_records", db_get_record_count(g_ctx.db));
  382. char *json_str = cJSON_PrintUnformatted(root);
  383. mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n", "%s", json_str);
  384. free(json_str);
  385. cJSON_Delete(root);
  386. }
  387. /* ---- GET /api/health ---- */
  388. static void handle_api_health(struct mg_connection *c, struct mg_http_message *hm)
  389. {
  390. mg_http_reply(c, 200, "Content-Type: application/json\r\n", "{\"status\":\"ok\"}");
  391. }
  392. /* ---- GET /api/history ---- */
  393. static void handle_api_history(struct mg_connection *c, struct mg_http_message *hm)
  394. {
  395. char start_time[64] = {0}, end_time[64] = {0};
  396. char addr_str[16] = {0}, page_str[16] = {0}, size_str[16] = {0}, sort_str[16] = {0};
  397. get_query_param(hm, "start_time", start_time, sizeof(start_time));
  398. get_query_param(hm, "end_time", end_time, sizeof(end_time));
  399. get_query_param(hm, "device_addr", addr_str, sizeof(addr_str));
  400. get_query_param(hm, "page", page_str, sizeof(page_str));
  401. get_query_param(hm, "page_size", size_str, sizeof(size_str));
  402. get_query_param(hm, "sort", sort_str, sizeof(sort_str));
  403. uint8_t device_addr = addr_str[0] ? (uint8_t)atoi(addr_str) : 0;
  404. int page = page_str[0] ? atoi(page_str) : 1;
  405. int page_size = size_str[0] ? atoi(size_str) : g_ctx.config->web_page_size;
  406. bool sort_asc = (strcmp(sort_str, "asc") == 0);
  407. if (page < 1) page = 1;
  408. if (page_size < 1) page_size = 20;
  409. if (page_size > 200) page_size = 200;
  410. db_query_result_t result;
  411. int rc = db_query_history(g_ctx.db, device_addr,
  412. start_time[0] ? start_time : NULL,
  413. end_time[0] ? end_time : NULL,
  414. page, page_size, sort_asc, &result);
  415. cJSON *root = cJSON_CreateObject();
  416. if (rc != 0) {
  417. cJSON_AddStringToObject(root, "error", "Query failed");
  418. } else {
  419. cJSON_AddNumberToObject(root, "total", result.total);
  420. cJSON_AddNumberToObject(root, "page", result.page);
  421. cJSON_AddNumberToObject(root, "page_size", result.page_size);
  422. cJSON_AddNumberToObject(root, "total_pages", result.total_pages);
  423. cJSON *arr = cJSON_AddArrayToObject(root, "records");
  424. for (int i = 0; i < result.count; i++) {
  425. db_history_record_t *r = &result.records[i];
  426. cJSON *rec = cJSON_CreateObject();
  427. cJSON_AddNumberToObject(rec, "id", r->id);
  428. cJSON_AddStringToObject(rec, "timestamp", r->timestamp);
  429. cJSON_AddNumberToObject(rec, "device_addr", r->device_addr);
  430. cJSON_AddNumberToObject(rec, "battery_voltage", r->battery_voltage);
  431. cJSON_AddNumberToObject(rec, "pv_voltage", r->pv_voltage);
  432. cJSON_AddNumberToObject(rec, "pv_charge_current", r->pv_charge_current);
  433. cJSON_AddNumberToObject(rec, "pv_input_power", r->pv_input_power);
  434. cJSON_AddNumberToObject(rec, "cumulative_energy", r->cumulative_energy);
  435. cJSON_AddNumberToObject(rec, "machine_temp", r->machine_temp);
  436. cJSON_AddNumberToObject(rec, "fault_code", r->fault_code);
  437. cJSON_AddStringToObject(rec, "fault_desc", modbus_fault_desc(r->fault_code));
  438. cJSON_AddNumberToObject(rec, "battery_soc", r->battery_soc);
  439. cJSON_AddNumberToObject(rec, "charge_state", r->charge_state);
  440. cJSON_AddNumberToObject(rec, "device_type", r->device_type);
  441. cJSON_AddNumberToObject(rec, "software_version", r->software_version);
  442. cJSON_AddNumberToObject(rec, "battery_type", r->battery_type);
  443. cJSON_AddStringToObject(rec, "battery_type_desc", modbus_battery_type_str(r->battery_type));
  444. cJSON_AddNumberToObject(rec, "battery_level", r->battery_level);
  445. cJSON_AddStringToObject(rec, "battery_level_desc", modbus_battery_level_str(r->battery_level));
  446. cJSON_AddNumberToObject(rec, "charge_mode", r->charge_mode);
  447. cJSON_AddStringToObject(rec, "charge_mode_desc", modbus_charge_mode_str(r->charge_mode));
  448. cJSON_AddItemToArray(arr, rec);
  449. }
  450. }
  451. char *json_str = cJSON_PrintUnformatted(root);
  452. mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n", "%s", json_str);
  453. free(json_str);
  454. cJSON_Delete(root);
  455. db_free_query_result(&result);
  456. }
  457. /* ---- GET /api/history/stats ---- */
  458. static void handle_api_history_stats(struct mg_connection *c, struct mg_http_message *hm)
  459. {
  460. db_stats_t stats;
  461. int rc = db_get_stats(g_ctx.db, &stats);
  462. cJSON *root = cJSON_CreateObject();
  463. if (rc != 0) {
  464. cJSON_AddStringToObject(root, "error", "Stats query failed");
  465. } else {
  466. cJSON_AddNumberToObject(root, "total_records", stats.total_records);
  467. cJSON_AddStringToObject(root, "first_record_time", stats.first_record_time);
  468. cJSON_AddStringToObject(root, "last_record_time", stats.last_record_time);
  469. cJSON_AddNumberToObject(root, "device_count", stats.device_count);
  470. cJSON_AddNumberToObject(root, "max_pv_power", stats.max_pv_power);
  471. cJSON_AddNumberToObject(root, "max_battery_voltage", stats.max_battery_voltage);
  472. cJSON_AddNumberToObject(root, "max_temperature", stats.max_temperature);
  473. }
  474. char *json_str = cJSON_PrintUnformatted(root);
  475. mg_http_reply(c, 200, "Content-Type: application/json\r\nAccess-Control-Allow-Origin: *\r\n", "%s", json_str);
  476. free(json_str);
  477. cJSON_Delete(root);
  478. }