Files
arcs/apps/arcs-mini/mcp-tools/mcp_tool_alarm.c
2026-08-13 16:50:52 +08:00

942 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#define TAG "alarm_control"
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <sys/time.h>
#include <stdbool.h>
#include "cJSON.h"
#include "lisa_log.h"
#include "lisa_mem.h"
#include "listen_system.h"
#include "mcp.h"
#include "alarm_next.h"
#include "service_alarm.h"
#define ALARM_CTRL_FAIL(_msg) \
do { \
snprintf(result_text, text_len, "%s", (_msg)); \
return -1; \
} while (0)
typedef struct {
uint64_t cloud_id; // cloud alarm id
const char *action; // CREATE / DELETE / UPDATE / QUERY
const char *alarm_type; // ONCE / DAILY / WEEKLY / WORKDAY / WEEKEND / MONTHLY / YEARLY
const char *holiday_name; // 节日名称
const char *calendar_type; // GREGORIAN / LUNAR
const char *time_str; // HH:mm:ss
const char *date_str; // YYYY-MM-DD
const char *lunar_date_str; // MM-DD (LUNAR)
int day_of_week; // 1-7
int day_of_month; // 1-31
int month_of_year; // 1-12
const char *text; // 提醒文本
cJSON *search_keywords; // array of strings
} alarm_control_params_t;
/* ==================== 参数解析工具 ==================== */
static void alarm_control_params_init(alarm_control_params_t *params)
{
if (!params) {
return;
}
memset(params, 0, sizeof(*params));
params->cloud_id = 0;
params->day_of_week = -1;
params->day_of_month = -1;
params->month_of_year = -1;
}
static void alarm_control_params_parse(cJSON *args, alarm_control_params_t *params)
{
if (!args || !params) {
return;
}
cJSON *pval = NULL;
pval = mcp_tool_call_args_get(args, "id");
if (pval && cJSON_IsNumber(pval)) {
params->cloud_id = (uint64_t)pval->valuedouble;
}
pval = mcp_tool_call_args_get(args, "action");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->action = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "alarm_type");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->alarm_type = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "holiday_name");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->holiday_name = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "calendar_type");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->calendar_type = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "time");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->time_str = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "date");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->date_str = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "lunar_date");
if (pval && cJSON_IsString(pval) && pval->valuestring[0] != '\0') {
params->lunar_date_str = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "day_of_week");
if (pval && cJSON_IsNumber(pval)) {
params->day_of_week = pval->valueint;
}
pval = mcp_tool_call_args_get(args, "day_of_month");
if (pval && cJSON_IsNumber(pval)) {
params->day_of_month = pval->valueint;
}
pval = mcp_tool_call_args_get(args, "month_of_year");
if (pval && cJSON_IsNumber(pval)) {
params->month_of_year = pval->valueint;
}
pval = mcp_tool_call_args_get(args, "text");
if (pval && cJSON_IsString(pval)) {
params->text = pval->valuestring;
}
pval = mcp_tool_call_args_get(args, "search_keywords");
if (pval && cJSON_IsArray(pval)) {
params->search_keywords = pval;
}
}
static alarm_calendar_t parse_calendar_type(const char *calendar_type)
{
if (calendar_type && strcmp(calendar_type, "LUNAR") == 0) {
return ALARM_CAL_LUNAR;
}
return ALARM_CAL_GREGORIAN;
}
static alarm_trigger_type_t parse_trigger_type(const char *alarm_type, const char *holiday_name)
{
if (holiday_name && holiday_name[0] != '\0') {
return ALARM_TRIG_HOLIDAY;
}
if (!alarm_type) {
return ALARM_TRIG_ONCE;
}
if (strcmp(alarm_type, "ONCE") == 0) {
return ALARM_TRIG_ONCE;
}
if (strcmp(alarm_type, "DAILY") == 0) {
return ALARM_TRIG_DAILY;
}
if (strcmp(alarm_type, "WEEKLY") == 0) {
return ALARM_TRIG_WEEKLY;
}
if (strcmp(alarm_type, "WORKDAY") == 0) {
return ALARM_TRIG_WORKDAY;
}
if (strcmp(alarm_type, "WEEKEND") == 0) {
return ALARM_TRIG_WEEKEND;
}
if (strcmp(alarm_type, "MONTHLY") == 0) {
return ALARM_TRIG_MONTHLY;
}
if (strcmp(alarm_type, "YEARLY") == 0) {
return ALARM_TRIG_YEARLY;
}
return ALARM_TRIG_ONCE;
}
static bool parse_time_hms(const char *time_str, uint8_t *hour, uint8_t *minute, uint8_t *second)
{
int h = -1, m = -1, s = -1;
if (!time_str || !hour || !minute || !second) {
return false;
}
if (sscanf(time_str, "%d:%d:%d", &h, &m, &s) == 3) {
// ok
} else if (sscanf(time_str, "%d:%d", &h, &m) == 2) {
s = 0;
} else {
return false;
}
if (h < 0 || h > 23 || m < 0 || m > 59 || s < 0 || s > 59) {
return false;
}
*hour = (uint8_t)h;
*minute = (uint8_t)m;
*second = (uint8_t)s;
return true;
}
static bool parse_date_ymd(const char *date_str, uint16_t *year, uint8_t *month, uint8_t *day)
{
int y = -1, m = -1, d = -1;
if (!date_str || !year || !month || !day) {
return false;
}
if (sscanf(date_str, "%d-%d-%d", &y, &m, &d) != 3) {
return false;
}
if (y < 1970 || m < 1 || m > 12 || d < 1 || d > 31) {
return false;
}
*year = (uint16_t)y;
*month = (uint8_t)m;
*day = (uint8_t)d;
return true;
}
static bool parse_lunar_md(const char *lunar_date_str, uint8_t *month, uint8_t *day)
{
int m = -1, d = -1;
if (!lunar_date_str || !month || !day) {
return false;
}
if (sscanf(lunar_date_str, "%d-%d", &m, &d) != 2) {
return false;
}
if (m < 1 || m > 12 || d < 1 || d > 31) {
return false;
}
*month = (uint8_t)m;
*day = (uint8_t)d;
return true;
}
static void alarm_fill_keywords(const cJSON *arr, alarm_object_t *alarm)
{
if (!arr || !alarm) {
return;
}
uint8_t count = 0;
cJSON *item = NULL;
cJSON_ArrayForEach(item, arr) {
if (!cJSON_IsString(item) || !item->valuestring) {
continue;
}
if (count >= ALARM_KEYWORD_MAX) {
break;
}
strncpy(alarm->keywords[count], item->valuestring, ALARM_KEYWORD_LEN - 1);
alarm->keywords[count][ALARM_KEYWORD_LEN - 1] = '\0';
count++;
}
alarm->keyword_count = count;
}
/* ==================== 闹钟日期处理工具 ==================== */
static void normalize_tm(struct tm *tmv);
static int get_current_tm(struct tm *out_tm)
{
if (!out_tm) {
return -1;
}
struct timeval tv;
if (ls_sys_get_time(&tv) != 0) {
if (gettimeofday(&tv, NULL) != 0) {
return -1;
}
}
long int ts = (long int)tv.tv_sec;
if (!ls_sys_get_tmtime(&ts, out_tm)) {
return -1;
}
// ls_sys_get_tmtime uses year base 1970 and does not set tm_wday.
// Normalize via mktime with a 1900-based year offset, then convert back.
normalize_tm(out_tm);
return 0;
}
static void normalize_tm(struct tm *tmv)
{
if (!tmv) {
return;
}
struct tm tmp = *tmv;
tmp.tm_year += 70; // 1970-based -> 1900-based
mktime(&tmp);
tmv->tm_year = tmp.tm_year - 70;
tmv->tm_mon = tmp.tm_mon;
tmv->tm_mday = tmp.tm_mday;
tmv->tm_hour = tmp.tm_hour;
tmv->tm_min = tmp.tm_min;
tmv->tm_sec = tmp.tm_sec;
tmv->tm_wday = tmp.tm_wday;
tmv->tm_yday = tmp.tm_yday;
tmv->tm_isdst = tmp.tm_isdst;
}
static int days_in_month(int year, int month)
{
static const uint8_t days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (month < 1 || month > 12) {
return 30;
}
if (month == 2) {
bool leap = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
return leap ? 29 : 28;
}
return days[month - 1];
}
static void set_date_from_tm(alarm_object_t *alarm, const struct tm *tmv)
{
if (!alarm || !tmv) {
return;
}
alarm->trigger.year = (uint16_t)(tmv->tm_year + 1970);
alarm->trigger.month = (uint8_t)(tmv->tm_mon + 1);
alarm->trigger.day = (uint8_t)tmv->tm_mday;
}
static int calc_tm_seconds(const struct tm *tmv)
{
if (!tmv) {
return 0;
}
return tmv->tm_hour * 3600 + tmv->tm_min * 60 + tmv->tm_sec;
}
static int calc_alarm_seconds(const alarm_object_t *alarm)
{
if (!alarm) {
return 0;
}
return alarm->trigger.hour * 3600 +
alarm->trigger.minute * 60 +
alarm->trigger.second;
}
static int set_nearest_date_from_workday_rule(alarm_object_t *alarm, struct tm *tmv, bool want_workday)
{
int now_sec = calc_tm_seconds(tmv);
int alarm_sec = calc_alarm_seconds(alarm);
LISA_LOGI(TAG, "workday calc start: want=%s now=%04d-%02d-%02d %02d:%02d:%02d alarm=%02u:%02u:%02u",
want_workday ? "workday" : "weekend",
tmv->tm_year + 1970, tmv->tm_mon + 1, tmv->tm_mday,
tmv->tm_hour, tmv->tm_min, tmv->tm_sec,
alarm->trigger.hour, alarm->trigger.minute, alarm->trigger.second);
for (int i = 0; i < 370; i++) {
// 查询日期是否工作日
char date_str[16] = {0};
snprintf(date_str, sizeof(date_str), "%02d-%02d", tmv->tm_mon + 1, tmv->tm_mday);
bool is_work_day = false;
if (listenai_date_is_workday(date_str, &is_work_day) != 0) {
LISA_LOGE(TAG, "workday query failed, date=%s", date_str);
return -6;
}
bool match = want_workday ? is_work_day : !is_work_day;
LISA_LOGI(TAG, "workday calc: i=%d date=%04d-%02d-%02d is_workday=%d match=%d",
i,
tmv->tm_year + 1970, tmv->tm_mon + 1, tmv->tm_mday,
is_work_day, match);
if (match) {
if (i > 0 || alarm_sec > now_sec) {
set_date_from_tm(alarm, tmv);
return 0;
}
}
tmv->tm_mday += 1;
normalize_tm(tmv);
}
LISA_LOGW(TAG, "workday rule not found within limit");
return -6;
}
// 获取未来最近的 周/月/年循环闹钟的日期
static int set_nearest_date_from_rule(alarm_object_t *alarm,
alarm_trigger_type_t trigger_type,
int day_of_week,
int day_of_month,
int month_of_year)
{
struct tm tmv;
if (get_current_tm(&tmv) != 0) {
return -1;
}
// Ensure tm_wday is normalized
normalize_tm(&tmv);
int now_sec = calc_tm_seconds(&tmv);
int alarm_sec = calc_alarm_seconds(alarm);
LISA_LOGI(TAG, "set_nearest_date_from_rule: now=%02d:%02d:%02d alarm=%02u:%02u:%02u",
tmv.tm_hour, tmv.tm_min, tmv.tm_sec,
alarm->trigger.hour, alarm->trigger.minute, alarm->trigger.second);
switch (trigger_type) {
case ALARM_TRIG_DAILY:
{
if (alarm_sec <= now_sec) {
tmv.tm_mday += 1;
normalize_tm(&tmv);
}
set_date_from_tm(alarm, &tmv);
return 0;
}
case ALARM_TRIG_WEEKLY:
{
if (day_of_week < 1 || day_of_week > 7) {
return -2;
}
int today_wd = tmv.tm_wday == 0 ? 7 : tmv.tm_wday; // 1=Mon ... 7=Sun
int diff = (day_of_week - today_wd + 7) % 7;
if (diff == 0) {
if (alarm_sec <= now_sec) {
diff = 7;
}
}
LISA_LOGI(TAG, "weekly calc: today_wd=%d target=%d diff=%d ",
today_wd, day_of_week, diff);
tmv.tm_mday += diff;
normalize_tm(&tmv);
set_date_from_tm(alarm, &tmv);
return 0;
}
case ALARM_TRIG_WORKDAY:
{
return set_nearest_date_from_workday_rule(alarm, &tmv, true);
}
case ALARM_TRIG_WEEKEND:
{
int today_wd = tmv.tm_wday == 0 ? 7 : tmv.tm_wday; // 1=Mon ... 7=Sun
bool today_is_weekend = (today_wd == 6 || today_wd == 7);
if (today_is_weekend && alarm_sec > now_sec) {
set_date_from_tm(alarm, &tmv);
return 0;
}
tmv.tm_mday += 1;
normalize_tm(&tmv);
for (int i = 0; i < 7; i++) {
int wd = tmv.tm_wday == 0 ? 7 : tmv.tm_wday;
if (wd == 6 || wd == 7) {
set_date_from_tm(alarm, &tmv);
return 0;
}
tmv.tm_mday += 1;
normalize_tm(&tmv);
}
LISA_LOGW(TAG, "weekend rule not found within limit");
return -6;
}
case ALARM_TRIG_MONTHLY:
{
if (day_of_month < 1 || day_of_month > 31) {
return -3;
}
LISA_LOGI(TAG, "monthly calc: today=%04d-%02d-%02d now=%02d:%02d:%02d target_day=%d ",
tmv.tm_year + 1970, tmv.tm_mon + 1, tmv.tm_mday,
tmv.tm_hour, tmv.tm_min, tmv.tm_sec);
int max_day = days_in_month(tmv.tm_year + 1970, tmv.tm_mon + 1);
int target_day = day_of_month > max_day ? max_day : day_of_month;
if (tmv.tm_mday < target_day) {
tmv.tm_mday = target_day;
} else if (tmv.tm_mday == target_day) {
if (alarm_sec <= now_sec) {
tmv.tm_mon += 1;
}
max_day = days_in_month(tmv.tm_year + 1970, tmv.tm_mon + 1);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
} else {
tmv.tm_mon += 1;
max_day = days_in_month(tmv.tm_year + 1970, tmv.tm_mon + 1);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
}
normalize_tm(&tmv);
set_date_from_tm(alarm, &tmv);
return 0;
}
case ALARM_TRIG_YEARLY:
{
if (month_of_year < 1 || month_of_year > 12 || day_of_month < 1 || day_of_month > 31) {
return -4;
}
LISA_LOGI(TAG, "yearly calc: today=%04d-%02d-%02d now=%02d:%02d:%02d target=%02d-%02d ",
tmv.tm_year + 1970, tmv.tm_mon + 1, tmv.tm_mday,
tmv.tm_hour, tmv.tm_min, tmv.tm_sec,
month_of_year, day_of_month);
int max_day = days_in_month(tmv.tm_year + 1970, month_of_year);
int target_day = day_of_month > max_day ? max_day : day_of_month;
if ((tmv.tm_mon + 1 < month_of_year) ||
((tmv.tm_mon + 1 == month_of_year) && (tmv.tm_mday < target_day))) {
tmv.tm_mon = month_of_year - 1;
tmv.tm_mday = target_day;
} else if ((tmv.tm_mon + 1 == month_of_year) && (tmv.tm_mday == target_day)) {
if (alarm_sec <= now_sec) {
tmv.tm_year += 1;
}
tmv.tm_mon = month_of_year - 1;
max_day = days_in_month(tmv.tm_year + 1970, month_of_year);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
} else {
tmv.tm_year += 1;
tmv.tm_mon = month_of_year - 1;
max_day = days_in_month(tmv.tm_year + 1970, month_of_year);
target_day = day_of_month > max_day ? max_day : day_of_month;
tmv.tm_mday = target_day;
}
normalize_tm(&tmv);
set_date_from_tm(alarm, &tmv);
return 0;
}
default:
return -5;
}
}
/* ==================== 闹钟对象构造工具 ==================== */
static int build_alarm_by_params(const alarm_control_params_t *params, alarm_object_t *alarm_obj,
char *err_msg, size_t err_len)
{
if (!params || !alarm_obj || !err_msg || err_len == 0) {
return -1;
}
memset(alarm_obj, 0, sizeof(*alarm_obj));
alarm_obj->alarm_id = 0;
alarm_obj->cloud_id = params->cloud_id;
alarm_obj->calendar = parse_calendar_type(params->calendar_type);
alarm_obj->trigger.type = parse_trigger_type(params->alarm_type, params->holiday_name);
// 检查闹钟时间(必选)
if (!params->time_str || params->time_str[0] == '\0') {
LISA_LOGE(TAG, "time missing");
snprintf(err_msg, err_len, "没有指定时间或时间格式错误");
return -1;
}
if (!parse_time_hms(params->time_str, &alarm_obj->trigger.hour, &alarm_obj->trigger.minute, &alarm_obj->trigger.second)) {
LISA_LOGE(TAG, "invalid time format: %s", params->time_str);
snprintf(err_msg, err_len, "没有指定时间或时间格式错误");
return -1;
}
// 检查闹钟日期
bool date_set = false;
if (params->date_str) {
uint16_t y = 0;
uint8_t m = 0;
uint8_t d = 0;
if (parse_date_ymd(params->date_str, &y, &m, &d)) {
alarm_obj->trigger.year = y;
alarm_obj->trigger.month = m;
alarm_obj->trigger.day = d;
date_set = true;
}
}
// 如果没有下发日期,构造闹钟日期
// 先按日历类型构造
if (!date_set) {
if (alarm_obj->calendar == ALARM_CAL_LUNAR) {
if (!params->lunar_date_str || params->lunar_date_str[0] == '\0') {
LISA_LOGE(TAG, "lunar_date missing");
snprintf(err_msg, err_len, "缺少农历日期");
return -1;
}
char gregorian_date[16] = {0};
if (listenai_date_transition("lunar", params->lunar_date_str,
gregorian_date, sizeof(gregorian_date)) != 0) {
LISA_LOGE(TAG, "lunar transition failed");
snprintf(err_msg, err_len, "农历日期转换失败");
return -1;
}
uint16_t y = 0;
uint8_t m = 0;
uint8_t d = 0;
if (!parse_date_ymd(gregorian_date, &y, &m, &d)) {
LISA_LOGE(TAG, "invalid lunar transition date: %s", gregorian_date);
snprintf(err_msg, err_len, "农历日期转换失败");
return -1;
}
alarm_obj->trigger.year = y;
alarm_obj->trigger.month = m;
alarm_obj->trigger.day = d;
date_set = true;
}
}
// 再按闹钟类型构造
if (!date_set) {
if (alarm_obj->trigger.type == ALARM_TRIG_ONCE) {
struct tm tmv;
if (get_current_tm(&tmv) != 0) {
LISA_LOGE(TAG, "get today date failed");
snprintf(err_msg, err_len, "无法获取当前日期");
return -1;
}
set_date_from_tm(alarm_obj, &tmv);
date_set = true;
} else if (alarm_obj->trigger.type == ALARM_TRIG_DAILY ||
alarm_obj->trigger.type == ALARM_TRIG_WEEKLY ||
alarm_obj->trigger.type == ALARM_TRIG_MONTHLY ||
alarm_obj->trigger.type == ALARM_TRIG_YEARLY ||
alarm_obj->trigger.type == ALARM_TRIG_WORKDAY||
alarm_obj->trigger.type == ALARM_TRIG_WEEKEND ) {
int r = set_nearest_date_from_rule(alarm_obj, alarm_obj->trigger.type,
params->day_of_week, params->day_of_month, params->month_of_year);
if (r != 0) {
LISA_LOGE(TAG, "missing or invalid rule params, type=%d", alarm_obj->trigger.type);
snprintf(err_msg, err_len, "缺少必要的日期参数");
return -1;
}
}else if (alarm_obj->trigger.type == ALARM_TRIG_HOLIDAY) {
if (!params->holiday_name || params->holiday_name[0] == '\0') {
LISA_LOGE(TAG, "holiday_name missing");
snprintf(err_msg, err_len, "缺少节日名称");
return -1;
}
char gregorian_date[16] = {0};
if (listenai_date_transition("holiday", params->holiday_name,
gregorian_date, sizeof(gregorian_date)) != 0) {
LISA_LOGE(TAG, "holiday transition failed");
snprintf(err_msg, err_len, "节假日转换失败");
return -1;
}
uint16_t y = 0;
uint8_t m = 0;
uint8_t d = 0;
if (!parse_date_ymd(gregorian_date, &y, &m, &d)) {
LISA_LOGE(TAG, "invalid holiday transition date: %s", gregorian_date);
snprintf(err_msg, err_len, "节假日转换失败");
return -1;
}
alarm_obj->trigger.year = y;
alarm_obj->trigger.month = m;
alarm_obj->trigger.day = d;
date_set = true;
}
}
// 保存时间戳
alarm_obj->alarm_id = alarm_obj_to_timestamp(alarm_obj);
if (alarm_obj->alarm_id == 0) {
LISA_LOGE(TAG, "invalid alarm datetime, cannot build alarm_id");
snprintf(err_msg, err_len, "闹钟时间无效");
return -1;
}
// 保存循环信息
if (params->day_of_week >= 1 && params->day_of_week <= 7) {
alarm_obj->trigger.day_of_week = (uint8_t)params->day_of_week;
}
if (params->day_of_month >= 1 && params->day_of_month <= 31) {
alarm_obj->trigger.day_of_month = (uint8_t)params->day_of_month;
}
if (params->month_of_year >= 1 && params->month_of_year <= 12) {
alarm_obj->trigger.month = (uint8_t)params->month_of_year;
}
if (params->holiday_name && params->holiday_name[0] != '\0') {
strncpy(alarm_obj->trigger.holiday_name, params->holiday_name, sizeof(alarm_obj->trigger.holiday_name) - 1);
alarm_obj->trigger.holiday_name[sizeof(alarm_obj->trigger.holiday_name) - 1] = '\0';
}
if (params->lunar_date_str && alarm_obj->calendar == ALARM_CAL_LUNAR) {
uint8_t m = 0;
uint8_t d = 0;
if (parse_lunar_md(params->lunar_date_str, &m, &d)) {
alarm_obj->trigger.lunar_month = m;
alarm_obj->trigger.lunar_day = d;
} else {
LISA_LOGW(TAG, "invalid lunar_date format: %s", params->lunar_date_str);
}
}
// 保存文本和关键词
if (params->text && params->text[0] != '\0') {
strncpy(alarm_obj->text, params->text, sizeof(alarm_obj->text) - 1);
alarm_obj->text[sizeof(alarm_obj->text) - 1] = '\0';
}
if (params->search_keywords) {
alarm_fill_keywords(params->search_keywords, alarm_obj);
}
return 0;
}
/* ==================== 闹钟对象查找工具 ==================== */
static int delete_alarm_by_cloud_id(uint64_t cloud_id, char *result_text, size_t text_len)
{
return service_alarm_delete_by_cloud_id(cloud_id, result_text, text_len);
}
/**
* @brief 闹钟提醒相关功能的处理函数
*
* 该工具用于处理闹钟提醒的创建、查询、删除操作。
* 首先构造 alarm_obj 闹钟对象,
* 然后基于 action 调用不同的函数处理闹钟操作指令,
* 最后基于处理结果构造 MCP 响应。
*/
static int alarm_control_execute(const alarm_control_params_t *params, char *result_text, size_t text_len)
{
if (!params || !result_text || text_len == 0) {
ALARM_CTRL_FAIL("缺少参数");
}
if (!params->action || params->action[0] == '\0') {
ALARM_CTRL_FAIL("缺少操作类型");
}
if (params->cloud_id == 0) {
ALARM_CTRL_FAIL("缺少闹钟id");
}
result_text[0] = '\0';
// 执行闹钟指令
if (params->action && strcmp(params->action, "CREATE") == 0) {
alarm_object_t alarm_obj;
memset(&alarm_obj, 0, sizeof(alarm_obj));
if (build_alarm_by_params(params, &alarm_obj, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
alarm_obj_print(&alarm_obj);
if (service_alarm_create_obj(&alarm_obj, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
} else if (params->action && strcmp(params->action, "DELETE") == 0) {
if (service_alarm_delete_by_cloud_id(params->cloud_id, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
} else if (params->action && strcmp(params->action, "UPDATE") == 0) {
if (service_alarm_delete_by_cloud_id(params->cloud_id, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
alarm_object_t new_alarm;
memset(&new_alarm, 0, sizeof(new_alarm));
if (build_alarm_by_params(params, &new_alarm, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
alarm_obj_print(&new_alarm);
if (service_alarm_create_obj(&new_alarm, result_text, text_len) != 0) {
ALARM_CTRL_FAIL(result_text);
}
} else if (params->action && strcmp(params->action, "QUERY") == 0) {
const alarm_object_t *target = service_alarm_find_by_cloud_id(params->cloud_id);
if (!target) {
ALARM_CTRL_FAIL("未找到对应闹钟");
}
snprintf(result_text, text_len, "查询成功, id=%llu, timestamp=%llu, text=%s",
(unsigned long long)target->cloud_id,
(unsigned long long)target->alarm_id,
target->text);
} else {
ALARM_CTRL_FAIL("不支持的操作类型");
}
return 0;
}
static cJSON *alarm_control_call(const char *id, const char *name, cJSON *args)
{
(void)id;
alarm_control_params_t params;
alarm_control_params_init(&params);
alarm_control_params_parse(args, &params);
char result_text[512] = {0};
int exec_ret = alarm_control_execute(&params, result_text, sizeof(result_text));
if (result_text[0] == '\0') {
snprintf(result_text, sizeof(result_text), "%s", (exec_ret == 0) ? "闹钟操作成功" : "闹钟操作失败");
}
cJSON *result = mcp_tool_call_result_create(name);
if (!result) {
return NULL;
}
cJSON_AddStringToObject(result, "content", result_text);
cJSON_AddBoolToObject(result, "isError", exec_ret != 0);
return result;
}
static cJSON *alarm_control_list(const char *name)
{
cJSON *tool = cJSON_CreateObject();
if (!tool) {
return NULL;
}
cJSON_AddStringToObject(tool, "name", name);
cJSON_AddStringToObject(tool, "description",
"智能闹钟与提醒管理系统。支持公历/农历、固定周期及动态节假日。核心准则1.【禁止猜测时间】若用户仅提供下午早晨待会以后等模糊时段或未提及具体时间严禁私自编造时间点此时必须放弃调用函数转而向用户询问具体几点几分。2. 识别到特定节日必须填充 holiday_name3. 只要存在提醒内容(text),就必须同步提取搜索关键词(search_keywords)以供后续索引。");
cJSON *schema = cJSON_CreateObject();
if (!schema) {
cJSON_Delete(tool);
return NULL;
}
cJSON_AddStringToObject(schema, "type", "object");
cJSON *properties = cJSON_CreateObject();
if (!properties) {
cJSON_Delete(schema);
cJSON_Delete(tool);
return NULL;
}
cJSON *action_prop = cJSON_CreateObject();
cJSON_AddStringToObject(action_prop, "type", "string");
cJSON *action_enum = cJSON_CreateArray();
cJSON_AddItemToArray(action_enum, cJSON_CreateString("CREATE"));
cJSON_AddItemToArray(action_enum, cJSON_CreateString("DELETE"));
cJSON_AddItemToArray(action_enum, cJSON_CreateString("UPDATE"));
cJSON_AddItemToArray(action_enum, cJSON_CreateString("QUERY"));
cJSON_AddItemToObject(action_prop, "enum", action_enum);
cJSON_AddStringToObject(action_prop, "description", "操作类型CREATE(新建), DELETE(删除), UPDATE(修改), QUERY(查询)。");
cJSON_AddItemToObject(properties, "action", action_prop);
cJSON *alarm_type_prop = cJSON_CreateObject();
cJSON_AddStringToObject(alarm_type_prop, "type", "string");
cJSON *alarm_type_enum = cJSON_CreateArray();
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("ONCE"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("DAILY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("WEEKLY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("WORKDAY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("WEEKEND"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("MONTHLY"));
cJSON_AddItemToArray(alarm_type_enum, cJSON_CreateString("YEARLY"));
cJSON_AddItemToObject(alarm_type_prop, "enum", alarm_type_enum);
cJSON_AddStringToObject(alarm_type_prop, "description", "循环策略。节日提醒固定使用 YEARLY。");
cJSON_AddItemToObject(properties, "alarm_type", alarm_type_prop);
cJSON *holiday_name_prop = cJSON_CreateObject();
cJSON_AddStringToObject(holiday_name_prop, "type", "string");
cJSON_AddStringToObject(holiday_name_prop, "description", "节日名称(如'中秋节'、'母亲节')。若用户提及节日,此项必填。");
cJSON_AddItemToObject(properties, "holiday_name", holiday_name_prop);
cJSON *calendar_type_prop = cJSON_CreateObject();
cJSON_AddStringToObject(calendar_type_prop, "type", "string");
cJSON *calendar_type_enum = cJSON_CreateArray();
cJSON_AddItemToArray(calendar_type_enum, cJSON_CreateString("GREGORIAN"));
cJSON_AddItemToArray(calendar_type_enum, cJSON_CreateString("LUNAR"));
cJSON_AddItemToObject(calendar_type_prop, "enum", calendar_type_enum);
cJSON_AddStringToObject(calendar_type_prop, "default", "GREGORIAN");
cJSON_AddStringToObject(calendar_type_prop, "description", "历法GREGORIAN(公历), LUNAR(农历)。");
cJSON_AddItemToObject(properties, "calendar_type", calendar_type_prop);
cJSON *time_prop = cJSON_CreateObject();
cJSON_AddStringToObject(time_prop, "type", "string");
cJSON_AddStringToObject(time_prop, "description", "24小时制时间 (HH:mm:ss)。严禁推测,未提及则反问用户。");
cJSON_AddItemToObject(properties, "time", time_prop);
cJSON *date_prop = cJSON_CreateObject();
cJSON_AddStringToObject(date_prop, "type", "string");
cJSON_AddStringToObject(date_prop, "description", "具体日期 (YYYY-MM-DD)。仅在 ONCE 模式下使用。");
cJSON_AddItemToObject(properties, "date", date_prop);
cJSON *lunar_date_prop = cJSON_CreateObject();
cJSON_AddStringToObject(lunar_date_prop, "type", "string");
cJSON_AddStringToObject(lunar_date_prop, "description",
"具体日期 (MM-DD)。仅在 calendar_type 为 LUNAR 模式下使用。需要输出农历对应的数字月日格式正月初三输出01-03");
cJSON_AddItemToObject(properties, "lunar_date", lunar_date_prop);
cJSON *day_of_week_prop = cJSON_CreateObject();
cJSON_AddStringToObject(day_of_week_prop, "type", "integer");
cJSON_AddNumberToObject(day_of_week_prop, "minimum", 1);
cJSON_AddNumberToObject(day_of_week_prop, "maximum", 7);
cJSON_AddStringToObject(day_of_week_prop, "description", "周几1-71代表周一。仅在 WEEKLY 模式有效。");
cJSON_AddItemToObject(properties, "day_of_week", day_of_week_prop);
cJSON *day_of_month_prop = cJSON_CreateObject();
cJSON_AddStringToObject(day_of_month_prop, "type", "integer");
cJSON_AddNumberToObject(day_of_month_prop, "minimum", 1);
cJSON_AddNumberToObject(day_of_month_prop, "maximum", 31);
cJSON_AddStringToObject(day_of_month_prop, "description", "每月几号。仅在 MONTHLY 或无节日的 YEARLY 模式有效。");
cJSON_AddItemToObject(properties, "day_of_month", day_of_month_prop);
cJSON *month_of_year_prop = cJSON_CreateObject();
cJSON_AddStringToObject(month_of_year_prop, "type", "integer");
cJSON_AddNumberToObject(month_of_year_prop, "minimum", 1);
cJSON_AddNumberToObject(month_of_year_prop, "maximum", 12);
cJSON_AddStringToObject(month_of_year_prop, "description", "每年几月。仅在无节日的 YEARLY 模式有效。");
cJSON_AddItemToObject(properties, "month_of_year", month_of_year_prop);
cJSON *text_prop = cJSON_CreateObject();
cJSON_AddStringToObject(text_prop, "type", "string");
cJSON_AddStringToObject(text_prop, "description", "提醒的完整文本内容。例如:'给妈妈打电话庆祝生日'。");
cJSON_AddItemToObject(properties, "text", text_prop);
cJSON *search_keywords_prop = cJSON_CreateObject();
cJSON_AddStringToObject(search_keywords_prop, "type", "array");
cJSON *keywords_items = cJSON_CreateObject();
cJSON_AddStringToObject(keywords_items, "type", "string");
cJSON_AddItemToObject(search_keywords_prop, "items", keywords_items);
cJSON_AddStringToObject(search_keywords_prop, "description", "【强制要求】从 text 中提取的 1-3 个核心动词或名词。若 text 存在,此项禁止为空。例如 text 为'记得下午三点喝水',关键词为 ['喝水']。");
cJSON_AddItemToObject(properties, "search_keywords", search_keywords_prop);
cJSON_AddItemToObject(schema, "properties", properties);
cJSON *required = cJSON_CreateArray();
cJSON_AddItemToArray(required, cJSON_CreateString("action"));
cJSON_AddItemToObject(schema, "required", required);
cJSON_AddItemToObject(tool, "inputSchema", schema);
return tool;
}
MCP_TOOL_DEFINE(ls.built_in.alarm_clock, alarm_control_list, alarm_control_call);