/** * @file bsp_cmd.c * @brief JSON command parser - receives JSON, executes command, returns JSON via UART1 */ #include "bsp_cmd.h" #include "bsp_relay.h" #include "bsp_uart.h" #include "bsp_flash.h" #include "cJSON.h" #include #include /** * @brief Send JSON response via UART1 */ static void send_response(cJSON *root) { char *json_str = cJSON_PrintUnformatted(root); if (json_str != NULL) { BSP_UART1_SendString(json_str); BSP_UART1_SendString("\r\n"); cJSON_free(json_str); } cJSON_Delete(root); } /** * @brief Make a simple {"status":"ok","msg":"xxx"} response */ static void respond_ok(const char *msg) { cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "status", "ok"); cJSON_AddStringToObject(root, "msg", msg); send_response(root); } /** * @brief Make a {"status":"error","msg":"xxx"} response */ static void respond_error(const char *msg) { cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "status", "error"); cJSON_AddStringToObject(root, "msg", msg); send_response(root); } /** * @brief Process JSON command * * Supported commands: * {"cmd":"status"} * {"cmd":"fan_mode","value":"auto"} or "manual" * {"cmd":"fan_threshold","value":30.0} * {"cmd":"fan","value":"on"} or "off" * {"cmd":"light","value":"on"} or "off" * {"cmd":"reset"} */ void BSP_Cmd_ProcessJSON(const uint8_t *buf, uint16_t len) { cJSON *root = NULL; cJSON *cmd_item = NULL; cJSON *val_item = NULL; if (buf == NULL || len == 0) { respond_error("empty input"); return; } /* Ensure null termination and strip non-JSON prefix/suffix */ char tmp[256]; if (len >= sizeof(tmp)) len = sizeof(tmp) - 1; memcpy(tmp, buf, len); tmp[len] = '\0'; /* Find the first '{' to skip any MQTT topic prefix */ char *json_start = strchr(tmp, '{'); if (json_start == NULL) { respond_error("no json found"); return; } /* Find matching '}' to trim trailing garbage */ char *json_end = json_start; int depth = 0; while (*json_end != '\0') { if (*json_end == '{') depth++; else if (*json_end == '}') depth--; if (depth == 0) { json_end++; break; } json_end++; } *json_end = '\0'; /* Parse JSON */ root = cJSON_Parse(json_start); if (root == NULL) { respond_error("invalid json"); return; } /* Get "cmd" field */ cmd_item = cJSON_GetObjectItemCaseSensitive(root, "cmd"); if (cmd_item == NULL || !cJSON_IsString(cmd_item)) { respond_error("missing cmd field"); cJSON_Delete(root); return; } const char *cmd = cmd_item->valuestring; /* Get "value" field (optional) */ val_item = cJSON_GetObjectItemCaseSensitive(root, "value"); /* --- STATUS --- */ if (strcasecmp(cmd, "status") == 0) { system_state_t state; BSP_System_GetState(&state); cJSON *resp = cJSON_CreateObject(); cJSON_AddStringToObject(resp, "status", "ok"); cJSON *data = cJSON_CreateObject(); cJSON_AddStringToObject(data, "fan_mode", state.fan_mode == FAN_MODE_AUTO ? "auto" : "manual"); cJSON_AddNumberToObject(data, "fan_threshold", state.fan_threshold); cJSON_AddStringToObject(data, "fan", state.fan_state ? "on" : "off"); cJSON_AddStringToObject(data, "light", state.light_state ? "on" : "off"); cJSON_AddItemToObject(resp, "data", data); send_response(resp); } /* --- FAN MODE --- */ else if (strcasecmp(cmd, "fan_mode") == 0) { if (val_item == NULL || !cJSON_IsString(val_item)) { respond_error("missing value"); } else { const char *val = val_item->valuestring; if (strcasecmp(val, "auto") == 0) { BSP_FAN_SetMode(FAN_MODE_AUTO); respond_ok("fan mode set to auto"); } else if (strcasecmp(val, "manual") == 0) { BSP_FAN_SetMode(FAN_MODE_MANUAL); respond_ok("fan mode set to manual"); } else { respond_error("invalid fan_mode value"); } } } /* --- FAN THRESHOLD --- */ else if (strcasecmp(cmd, "fan_threshold") == 0) { if (val_item == NULL || !cJSON_IsNumber(val_item)) { respond_error("missing or invalid value"); } else { float thresh = (float)val_item->valuedouble; if (thresh > 0 && thresh < 100) { BSP_FAN_SetThreshold(thresh); respond_ok("fan threshold updated"); } else { respond_error("threshold out of range"); } } } /* --- FAN ON/OFF --- */ else if (strcasecmp(cmd, "fan") == 0) { if (val_item == NULL || !cJSON_IsString(val_item)) { respond_error("missing value"); } else { const char *val = val_item->valuestring; if (strcasecmp(val, "on") == 0) { BSP_FAN_SetMode(FAN_MODE_MANUAL); BSP_FAN_On(); respond_ok("fan on"); } else if (strcasecmp(val, "off") == 0) { BSP_FAN_SetMode(FAN_MODE_MANUAL); BSP_FAN_Off(); respond_ok("fan off"); } else { respond_error("invalid fan value"); } } } /* --- LIGHT ON/OFF --- */ else if (strcasecmp(cmd, "light") == 0) { if (val_item == NULL || !cJSON_IsString(val_item)) { respond_error("missing value"); } else { const char *val = val_item->valuestring; if (strcasecmp(val, "on") == 0) { BSP_LIGHT_On(); respond_ok("light on"); } else if (strcasecmp(val, "off") == 0) { BSP_LIGHT_Off(); respond_ok("light off"); } else { respond_error("invalid light value"); } } } /* --- RESET --- */ else if (strcasecmp(cmd, "reset") == 0) { BSP_Flash_ResetSettings(); respond_ok("settings reset to default"); } /* --- UNKNOWN --- */ else { respond_error("unknown command"); } cJSON_Delete(root); }