Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ esptool.py --chip esp32s3 -p /dev/ttyACM0 write_flash \

## 当前状态

**版本**: 0.4.4
**版本**: 0.4.5
**阶段**: Phase 38 完成 - WebUI 多语言支持

### 已完成功能
Expand Down
110 changes: 98 additions & 12 deletions components/ts_api/src/ts_api_temp.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "ts_api.h"
#include "ts_temp_source.h"
#include "ts_variable.h"
#include "ts_log.h"
#include <string.h>

Expand Down Expand Up @@ -274,23 +275,77 @@ static esp_err_t api_temp_select(const cJSON *params, ts_api_result_t *result)
}

/**
* @brief temp.bind - Bind/unbind temperature to a system variable
* @brief temp.bind - Bind/unbind temperature to system variable(s)
*
* Params: { "variable": "agx.cpu_temp" } // 绑定到变量
* { "variable": null } // 取消绑定
* {} // 查询当前绑定
* New format (weighted):
* { "variables": [{"name": "agx.cpu_temp", "weight": 0.4}, ...] }
*
* 绑定变量后,温度源会自动从该变量读取温度值。
* 变量必须为浮点类型,单位为摄氏度。
* Legacy format (single variable, weight 1.0):
* { "variable": "agx.cpu_temp" }
*
* Unbind:
* { "variable": null } or { "variables": [] }
*
* Query:
* {}
*/
static esp_err_t api_temp_bind(const cJSON *params, ts_api_result_t *result)
{
const cJSON *vars_arr = cJSON_GetObjectItem(params, "variables");
const cJSON *var_item = cJSON_GetObjectItem(params, "variable");

/* 处理绑定/解绑请求 */
if (var_item != NULL) {
/* New array format takes priority */
if (vars_arr != NULL && cJSON_IsArray(vars_arr)) {
int arr_size = cJSON_GetArraySize(vars_arr);
if (arr_size == 0) {
esp_err_t ret = ts_temp_unbind_variable();
if (ret != ESP_OK) {
ts_api_result_error(result, TS_API_ERR_INTERNAL, "Failed to unbind");
return ret;
}
} else {
if (arr_size > TS_TEMP_MAX_BOUND_VARS) {
ts_api_result_error(result, TS_API_ERR_INVALID_ARG, "Too many variables (max 8)");
return ESP_ERR_INVALID_ARG;
}
ts_temp_bound_var_t bind_arr[TS_TEMP_MAX_BOUND_VARS] = {0};
double total_weight = 0.0;
for (int i = 0; i < arr_size; i++) {
cJSON *item = cJSON_GetArrayItem(vars_arr, i);
cJSON *jname = cJSON_GetObjectItem(item, "name");
cJSON *jweight = cJSON_GetObjectItem(item, "weight");
if (!jname || !cJSON_IsString(jname) || !jname->valuestring[0]) {
ts_api_result_error(result, TS_API_ERR_INVALID_ARG, "Each variable needs a name");
return ESP_ERR_INVALID_ARG;
}
strncpy(bind_arr[i].name, jname->valuestring, TS_TEMP_MAX_VARNAME_LEN - 1);
bind_arr[i].weight = (jweight && cJSON_IsNumber(jweight))
? (float)jweight->valuedouble : 1.0f;
if (bind_arr[i].weight < 0.0f) bind_arr[i].weight = 0.0f;
if (bind_arr[i].weight > 1.0f) bind_arr[i].weight = 1.0f;
total_weight += bind_arr[i].weight;
}
if (total_weight <= 0.001) {
ts_api_result_error(result, TS_API_ERR_INVALID_ARG,
"At least one variable must have a positive weight");
return ESP_ERR_INVALID_ARG;
}
esp_err_t ret = ts_temp_bind_variables(bind_arr, (uint8_t)arr_size);
if (ret != ESP_OK) {
if (ret == ESP_ERR_INVALID_ARG) {
ts_api_result_error(result, TS_API_ERR_INVALID_ARG,
"Invalid weighted binding configuration");
} else if (ret == ESP_ERR_INVALID_SIZE) {
ts_api_result_error(result, TS_API_ERR_INVALID_ARG, "Variable name too long");
} else {
ts_api_result_error(result, TS_API_ERR_INTERNAL, "Failed to bind variables");
}
return ret;
}
}
} else if (var_item != NULL) {
/* Legacy single-variable format */
if (cJSON_IsString(var_item) && var_item->valuestring && var_item->valuestring[0] != '\0') {
/* 绑定到变量 */
esp_err_t ret = ts_temp_bind_variable(var_item->valuestring);
if (ret != ESP_OK) {
if (ret == ESP_ERR_INVALID_SIZE) {
Expand All @@ -301,7 +356,6 @@ static esp_err_t api_temp_bind(const cJSON *params, ts_api_result_t *result)
return ret;
}
} else if (cJSON_IsNull(var_item)) {
/* 取消绑定 */
esp_err_t ret = ts_temp_unbind_variable();
if (ret != ESP_OK) {
ts_api_result_error(result, TS_API_ERR_INTERNAL, "Failed to unbind variable");
Expand All @@ -313,9 +367,10 @@ static esp_err_t api_temp_bind(const cJSON *params, ts_api_result_t *result)
}
}

/* 返回当前状态 */
/* Build response with full binding info */
cJSON *data = cJSON_CreateObject();

/* Legacy compatible: first variable name */
char var_name[TS_TEMP_MAX_VARNAME_LEN];
esp_err_t ret = ts_temp_get_bound_variable(var_name, sizeof(var_name));
if (ret == ESP_OK && var_name[0] != '\0') {
Expand All @@ -324,7 +379,38 @@ static esp_err_t api_temp_bind(const cJSON *params, ts_api_result_t *result)
cJSON_AddNullToObject(data, "bound_variable");
}

/* 添加活动源和当前温度信息 */
/* New: full weighted binding array with live values */
ts_temp_bound_var_t bound[TS_TEMP_MAX_BOUND_VARS];
uint8_t bound_count = 0;
ts_temp_get_bound_variables(bound, &bound_count);

cJSON *bv_arr = cJSON_CreateArray();
double weighted_sum = 0.0;
double total_weight = 0.0;

for (uint8_t i = 0; i < bound_count; i++) {
cJSON *bv_item = cJSON_CreateObject();
cJSON_AddStringToObject(bv_item, "name", bound[i].name);
cJSON_AddNumberToObject(bv_item, "weight", bound[i].weight);

double val = 0;
bool readable = (ts_variable_get_float(bound[i].name, &val) == ESP_OK);
if (readable) {
cJSON_AddNumberToObject(bv_item, "value", val);
weighted_sum += val * bound[i].weight;
total_weight += bound[i].weight;
} else {
cJSON_AddNullToObject(bv_item, "value");
}
cJSON_AddItemToArray(bv_arr, bv_item);
}
cJSON_AddItemToObject(data, "bound_variables", bv_arr);

/* Weighted temperature */
if (bound_count > 0 && total_weight > 0.001) {
cJSON_AddNumberToObject(data, "weighted_temp_c", weighted_sum / total_weight);
}

ts_temp_source_type_t active = ts_temp_get_active_source();
cJSON_AddStringToObject(data, "active_source", ts_temp_source_type_to_str(active));

Expand Down
44 changes: 38 additions & 6 deletions components/ts_drivers/include/ts_temp_source.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,14 +106,27 @@ typedef struct {
/** 最大绑定变量名长度 */
#define TS_TEMP_MAX_VARNAME_LEN 64

/** 最大加权绑定变量数量 */
#define TS_TEMP_MAX_BOUND_VARS 8

/**
* @brief 加权绑定变量条目
*/
typedef struct {
char name[TS_TEMP_MAX_VARNAME_LEN]; /**< 变量名 */
float weight; /**< 权重 0.0 - 1.0 */
} ts_temp_bound_var_t;

/**
* @brief 温度源状态信息
*/
typedef struct {
bool initialized; /**< 初始化状态 */
ts_temp_source_type_t active_source; /**< 当前活动源 */
ts_temp_source_type_t preferred_source; /**< 用户首选源(0 表示自动选择)*/
char bound_variable[TS_TEMP_MAX_VARNAME_LEN]; /**< 绑定的变量名 */
char bound_variable[TS_TEMP_MAX_VARNAME_LEN]; /**< 绑定的变量名(向后兼容,取第一个) */
ts_temp_bound_var_t bound_vars[TS_TEMP_MAX_BOUND_VARS]; /**< 加权绑定变量数组 */
uint8_t bound_var_count; /**< 绑定变量数量 */
int16_t current_temp; /**< 当前温度 */
bool manual_mode; /**< 手动模式启用 */
uint32_t provider_count; /**< 注册的 provider 数量 */
Expand Down Expand Up @@ -261,25 +274,44 @@ esp_err_t ts_temp_clear_preferred_source(void);
/*---------------------------------------------------------------------------*/

/**
* @brief 绑定温度源到系统变量
*
* 当首选源设置为 TS_TEMP_SOURCE_VARIABLE 时,系统将从指定的变量读取温度值。
* 变量值应为浮点数(单位:°C)。
* @brief 绑定温度源到系统变量(单变量,权重 1.0,向后兼容)
*
* @param var_name 变量名(如 "agx.cpu_temp")
* @return ESP_OK 成功
*/
esp_err_t ts_temp_bind_variable(const char *var_name);

/**
* @brief 获取绑定的变量名
* @brief 绑定多个加权温度变量
*
* 系统将按权重加权求和计算有效温度。
* 例如: cpu_temp*0.4 + gpu_temp*0.6 = 加权温度
* 每个权重范围为 0.0 ~ 1.0,且总权重必须大于 0。
*
* @param vars 加权变量数组
* @param count 数组长度(1 ~ TS_TEMP_MAX_BOUND_VARS)
* @return ESP_OK 成功
*/
esp_err_t ts_temp_bind_variables(const ts_temp_bound_var_t *vars, uint8_t count);

/**
* @brief 获取绑定的变量名(向后兼容,返回第一个变量名)
*
* @param buffer 输出缓冲区
* @param buffer_size 缓冲区大小
* @return ESP_OK 成功,ESP_ERR_NOT_FOUND 未绑定
*/
esp_err_t ts_temp_get_bound_variable(char *buffer, size_t buffer_size);

/**
* @brief 获取所有加权绑定变量
*
* @param[out] vars 输出数组(至少 TS_TEMP_MAX_BOUND_VARS 大小)
* @param[out] count 输出实际数量
* @return ESP_OK 成功,ESP_ERR_NOT_FOUND 无绑定
*/
esp_err_t ts_temp_get_bound_variables(ts_temp_bound_var_t *vars, uint8_t *count);

/**
* @brief 清除变量绑定
*
Expand Down
Loading
Loading