Skip to content
Open
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
1 change: 1 addition & 0 deletions components/embed_claw/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ idf_component_register(
"."
REQUIRES
driver
esp_adc
esp_http_client
esp_http_server
esp_netif
Expand Down
4 changes: 4 additions & 0 deletions components/embed_claw/tools/ec_tools_reg.inc
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ EC_TOOLS_REG(cron_add)
EC_TOOLS_REG(cron_list)
EC_TOOLS_REG(cron_remove)
EC_TOOLS_REG(web_search)
EC_TOOLS_REG(bh1750)
EC_TOOLS_REG(fill_light)
EC_TOOLS_REG(water_ion_temp_uart)
EC_TOOLS_REG(ntu_test_adc)

186 changes: 186 additions & 0 deletions components/embed_claw/tools/tools_bh1750.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/**
* @file ec_tool_bh1750.c
* @author your_name (your_email@example.com)
* @brief BH1750 light sensor tool implementation
* @version 0.1
* @date 2026-03-21
*
* @copyright Copyright (c) 2026, Wireless-Tag. All rights reserved.
*
*/

/* ==================== [Includes] ========================================== */

#include "core/ec_tools.h"

#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_err.h"
#include "driver/i2c.h"


/* ==================== [Defines] =========================================== */

#define TAG "ec_tool_bh1750"

/* 定义 BH1750 的 I2C 地址与指令 */
#define BH1750_I2C_ADDR 0x23
#define BH1750_CMD_PWR_ON 0x01 /* 打开模块等待测量指令 */
#define BH1750_CMD_ONE_TIME_H_RES 0x20 /* 一次高分辨率模式,测量后自动转入 PowerDown */
#define BH1750_MEASURE_TIME_MS 180 /* 高分辨率模式典型转换时间 */
#define BH1750_DIVISOR 1.2f /* 光照强度计算系数 */

/* I2C 主机参数 */
#define I2C_MASTER_SCL_IO 8 /*!< 根据实际接线的 SCL 引脚修改 */
#define I2C_MASTER_SDA_IO 9 /*!< 根据实际接线的 SDA 引脚修改 */
#define I2C_MASTER_NUM 0 /*!< I2C 端口号 */
#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C 时钟频率 100KHz */
#define I2C_MASTER_TIMEOUT_MS 1000

/* ==================== [Typedefs] ========================================== */

/* ==================== [Static Prototypes] ================================= */

static esp_err_t i2c_master_init(void);
static esp_err_t ec_tool_bh1750_execute(const char *input_json, char *output, size_t output_size);

/* ==================== [Static Variables] ================================== */

static const ec_tools_t s_bh1750_tool = {
.name = "read_light_intensity",
.description = "Read the current ambient light intensity in lux from the BH1750 sensor.",
.input_schema_json = "{\"type\":\"object\",\"properties\":{},\"required\":[]}",
.execute = ec_tool_bh1750_execute,
};

/* ==================== [Macros] ============================================ */

/* ==================== [Global Functions] ================================== */

esp_err_t ec_tools_bh1750(void)
{
esp_err_t err = i2c_master_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "BH1750 I2C init failed: %s", esp_err_to_name(err));
return err;
}

ec_tools_register(&s_bh1750_tool);
return ESP_OK;
}

static esp_err_t i2c_master_init(void)
{
int i2c_master_port = I2C_MASTER_NUM;

ESP_LOGI(TAG, "Configuring I2C master: port=%d, SDA=%d, SCL=%d, freq=%dHz",
i2c_master_port, I2C_MASTER_SDA_IO, I2C_MASTER_SCL_IO, I2C_MASTER_FREQ_HZ);

i2c_config_t conf = {
.mode = I2C_MODE_MASTER,
.sda_io_num = I2C_MASTER_SDA_IO,
.scl_io_num = I2C_MASTER_SCL_IO,
.sda_pullup_en = GPIO_PULLUP_ENABLE,
.scl_pullup_en = GPIO_PULLUP_ENABLE,
.master.clk_speed = I2C_MASTER_FREQ_HZ,
};

esp_err_t err = i2c_param_config(i2c_master_port, &conf);
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2C param config failed: %s", esp_err_to_name(err));
return err;
}

err = i2c_driver_install(i2c_master_port, conf.mode, 0, 0, 0);
if (err != ESP_OK) {
ESP_LOGE(TAG, "I2C driver install failed: %s", esp_err_to_name(err));
return err;
}

ESP_LOGI(TAG, "I2C master initialized successfully");

return ESP_OK;
}

/**
* @brief 底层 BH1750 读取硬件接口
*/
esp_err_t s_bh1750_read_lux(float *lux)
{
esp_err_t err;
uint8_t cmd;
uint8_t data[2] = {0};
uint16_t level = 0;

if (lux == NULL) {
return ESP_ERR_INVALID_ARG;
}

// 1. 发送 Power On 启动命令
cmd = BH1750_CMD_PWR_ON;
err = i2c_master_write_to_device(I2C_MASTER_NUM, BH1750_I2C_ADDR, &cmd, 1,
I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
if (err != ESP_OK) {
ESP_LOGE(TAG, "BH1750 i2c write PWR_ON failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGD(TAG, "BH1750: sent PWR_ON");

// 2. 发送 One-Time 高分辨率模式指令 (测量一次后自动断电)
cmd = BH1750_CMD_ONE_TIME_H_RES;
err = i2c_master_write_to_device(I2C_MASTER_NUM, BH1750_I2C_ADDR, &cmd, 1,
I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
if (err != ESP_OK) {
ESP_LOGE(TAG, "BH1750 i2c write ONE_TIME_H_RES failed: %s", esp_err_to_name(err));
return err;
}
ESP_LOGD(TAG, "BH1750: sent ONE_TIME_H_RES");

// 3. 等待传感器完成转换测量 (手册要求高分辨率下至少等 120ms,这里给 180ms 留出裕量)
vTaskDelay(pdMS_TO_TICKS(BH1750_MEASURE_TIME_MS));

// 4. 读取 2 字节的测量数据
err = i2c_master_read_from_device(I2C_MASTER_NUM, BH1750_I2C_ADDR, data, 2,
I2C_MASTER_TIMEOUT_MS / portTICK_PERIOD_MS);
if (err != ESP_OK) {
ESP_LOGE(TAG, "BH1750 i2c read failed: %s", esp_err_to_name(err));
return err;
}

ESP_LOGD(TAG, "BH1750 raw bytes: 0x%02x 0x%02x", data[0], data[1]);

// 5. 数据解析与计算
level = (data[0] << 8) | data[1];
*lux = (float)level / BH1750_DIVISOR;

ESP_LOGI(TAG, "BH1750 level=%u => %.2f lux", level, *lux);

return ESP_OK;
}

/* ==================== [Static Functions] ================================== */

static esp_err_t ec_tool_bh1750_execute(const char *input_json, char *output, size_t output_size)
{
float lux = 0.0f;
esp_err_t err;

if (output == NULL || output_size == 0) {
return ESP_ERR_INVALID_ARG;
}

err = s_bh1750_read_lux(&lux);
if (err != ESP_OK) {
ESP_LOGE(TAG, "BH1750 read failed: %s", esp_err_to_name(err));
snprintf(output, output_size, "{\"error\": \"Failed to read BH1750 sensor, err: %d\"}", err);
return err;
}

snprintf(output, output_size, "{\"lux\": %.2f}", lux);

return ESP_OK;
}

189 changes: 189 additions & 0 deletions components/embed_claw/tools/tools_fill_light.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* @file ec_tool_fill_light.c
* @author embedclaw_developer
* @brief 补光灯 PWM 控制工具 (Fill Light Control Tool)
* @version 0.1
* @date 2026-03-27
*
* @copyright Copyright (c) 2026, Wireless-Tag. All rights reserved.
*
*/

/* ==================== [Includes] ========================================== */

#include "core/ec_tools.h"

#include "esp_log.h"
#include "driver/ledc.h"
#include "cJSON.h"
#include <string.h>
#include <stdio.h>

/* ==================== [Defines] =========================================== */

#define TAG "ec_tool_fill_light"

/* 硬件引脚与外设配置宏 */
#define FILL_LIGHT_GPIO GPIO_NUM_21
#define FILL_LIGHT_LEDC_TIMER LEDC_TIMER_0
#define FILL_LIGHT_LEDC_MODE LEDC_LOW_SPEED_MODE
#define FILL_LIGHT_LEDC_CHANNEL LEDC_CHANNEL_0
#define FILL_LIGHT_LEDC_RESOLUTION LEDC_TIMER_10_BIT
#define FILL_LIGHT_LEDC_FREQ_HZ 5000

/* 业务逻辑约束宏 */
#define FILL_LIGHT_MAX_DUTY ((1 << 10) - 1) /* 10-bit 分辨率对应的最大占空比 1023 */
#define FILL_LIGHT_MAX_BRIGHTNESS 100
#define FILL_LIGHT_MIN_BRIGHTNESS 0

/* ==================== [Typedefs] ========================================== */

/* ==================== [Static Prototypes] ================================= */

static esp_err_t fill_light_init(void);
static esp_err_t ec_tool_fill_light_execute(const char *input_json, char *output, size_t output_size);

/* ==================== [Static Variables] ================================== */

static bool s_fill_light_inited = false;

static const ec_tools_t s_fill_light_tool = {
.name = "control_fill_light",
.description = "CRITICAL HARDWARE ACTUATOR. Use this to physically change LED brightness. "
"PROTOCOL FOR AUTO-ADJUST: "
"1. You MUST have current lux from 'read_light_intensity' (DO NOT GUESS). "
"2. Map lux to brightness: <50lx=100%, 50-200lx=80%, 200-500lx=60%, 500-1000lx=40%, >1000lx=10%. "
"3. CALL THIS TOOL IMMEDIATELY after reading lux. "
"4. DO NOT generate a final text response to the user until you see '{\"status\": \"success\"}' in this tool's output. "
"A reply without calling this tool is a system violation.",
.input_schema_json = "{\"type\":\"object\",\"properties\":{\"brightness\":{\"type\":\"integer\",\"description\":\"Brightness percentage (0-100). 0=OFF, 100=Max.\"}},\"required\":[\"brightness\"]}",
.execute = ec_tool_fill_light_execute,
};

/* ==================== [Macros] ============================================ */

/* ==================== [Global Functions] ================================== */

esp_err_t fill_light_set_brightness(int brightness)
{
esp_err_t err = fill_light_init();
if (err != ESP_OK) {
return err;
}

/* 参数合法性边界收束 */
if (brightness < FILL_LIGHT_MIN_BRIGHTNESS) {
brightness = FILL_LIGHT_MIN_BRIGHTNESS;
} else if (brightness > FILL_LIGHT_MAX_BRIGHTNESS) {
brightness = FILL_LIGHT_MAX_BRIGHTNESS;
}

uint32_t duty = (brightness * FILL_LIGHT_MAX_DUTY) / FILL_LIGHT_MAX_BRIGHTNESS;

err = ledc_set_duty(FILL_LIGHT_LEDC_MODE, FILL_LIGHT_LEDC_CHANNEL, duty);
if (err != ESP_OK) {
ESP_LOGE(TAG, "LEDC set duty failed");
return err;
}

err = ledc_update_duty(FILL_LIGHT_LEDC_MODE, FILL_LIGHT_LEDC_CHANNEL);
if (err != ESP_OK) {
ESP_LOGE(TAG, "LEDC update duty failed");
return err;
}

ESP_LOGI(TAG, "Fill light brightness set to %d%% (duty %" PRIu32 ")", brightness, duty);

return ESP_OK;
}

esp_err_t ec_tools_fill_light(void)
{
ec_tools_register(&s_fill_light_tool);
return ESP_OK;
}

/* ==================== [Static Functions] ================================== */

static esp_err_t fill_light_init(void)
{
if (s_fill_light_inited) {
return ESP_OK;
}

ledc_timer_config_t ledc_timer = {
.duty_resolution = FILL_LIGHT_LEDC_RESOLUTION,
.freq_hz = FILL_LIGHT_LEDC_FREQ_HZ,
.speed_mode = FILL_LIGHT_LEDC_MODE,
.timer_num = FILL_LIGHT_LEDC_TIMER,
.clk_cfg = LEDC_AUTO_CLK,
};

esp_err_t err = ledc_timer_config(&ledc_timer);
if (err != ESP_OK) {
ESP_LOGE(TAG, "LEDC timer config failed");
return err;
}

ledc_channel_config_t ledc_channel = {
.channel = FILL_LIGHT_LEDC_CHANNEL,
.duty = FILL_LIGHT_MIN_BRIGHTNESS,
.gpio_num = FILL_LIGHT_GPIO,
.speed_mode = FILL_LIGHT_LEDC_MODE,
.hpoint = 0,
.timer_sel = FILL_LIGHT_LEDC_TIMER,
.flags.output_invert = 0
};

err = ledc_channel_config(&ledc_channel);
if (err != ESP_OK) {
ESP_LOGE(TAG, "LEDC channel config failed");
return err;
}

s_fill_light_inited = true;
ESP_LOGI(TAG, "Fill light initialized on GPIO %d", FILL_LIGHT_GPIO);

return ESP_OK;
}

static esp_err_t ec_tool_fill_light_execute(const char *input_json, char *output, size_t output_size)
{
if (input_json == NULL) {
snprintf(output, output_size, "{\"error\": \"input is null\"}");
return ESP_ERR_INVALID_ARG;
}

ESP_LOGI(TAG, "control_fill_light input_json: %s", input_json);

cJSON *input = cJSON_Parse(input_json);
if (input == NULL) {
snprintf(output, output_size, "{\"error\": \"invalid json\"}");
return ESP_ERR_INVALID_ARG;
}

cJSON *brightness_item = cJSON_GetObjectItem(input, "brightness");
if (brightness_item == NULL || !cJSON_IsNumber(brightness_item)) {
cJSON_Delete(input);
snprintf(output, output_size, "{\"error\": \"'brightness' (integer) is required\"}");
return ESP_ERR_INVALID_ARG;
}

int brightness = brightness_item->valueint;
ESP_LOGI(TAG, "control_fill_light parsed brightness: %d", brightness);
if (brightness < FILL_LIGHT_MIN_BRIGHTNESS || brightness > FILL_LIGHT_MAX_BRIGHTNESS) {
cJSON_Delete(input);
snprintf(output, output_size, "{\"error\": \"'brightness' must be between 0 and 100\"}");
return ESP_ERR_INVALID_ARG;
}

cJSON_Delete(input);

if (fill_light_set_brightness(brightness) == ESP_OK) {
snprintf(output, output_size, "{\"status\": \"success\", \"brightness\": %d}", brightness);
return ESP_OK;
} else {
snprintf(output, output_size, "{\"error\": \"failed to set brightness\"}");
return ESP_FAIL;
}
}
Loading
Loading