diff --git a/src/mi_fitness_mcp/adapters/mi_fitness_cloud.py b/src/mi_fitness_mcp/adapters/mi_fitness_cloud.py index 9831666..7b22a01 100644 --- a/src/mi_fitness_mcp/adapters/mi_fitness_cloud.py +++ b/src/mi_fitness_mcp/adapters/mi_fitness_cloud.py @@ -244,11 +244,10 @@ class MiFitnessCloudAdapter(DataAdapter): return preferred_region async def _discover_data_types(self) -> list[str]: - # The cloud API does not provide a reliable capability endpoint. The - # previous implementation probed a fixed historical range, which made - # automatic sync silently skip most metrics when that range had no data. - # Return all data types supported by this adapter; syncing a type with no - # records is safe and preferable to missing recent data. + # 小米健康云没有可靠的能力发现接口。旧实现用固定历史区间探测, + # 当该区间无数据时会导致自动同步静默跳过大部分指标。 + # 这里直接返回本适配器支持的全部数据类型;某类型无记录时同步 0 条即可, + # 比漏掉近期数据更安全。 return [ "daily_activity", "heart_rate", diff --git a/src/mi_fitness_mcp/main.py b/src/mi_fitness_mcp/main.py index 92a4f33..efa378e 100644 --- a/src/mi_fitness_mcp/main.py +++ b/src/mi_fitness_mcp/main.py @@ -26,104 +26,104 @@ async def _check_adapter_health(adapter) -> tuple[bool, list[str], str | None]: def cmd_setup(args): if args.mode == "mi_fitness_cloud" and args.user_id and args.pass_token: save_mi_fitness_token(args.user_id, args.pass_token) - config = Config(mode="mi_fitness_cloud", region=args.region or "ru") + config = Config(mode="mi_fitness_cloud", region=args.region or "cn") save_config(config) - print("✅ Конфигурация сохранена (mi_fitness_cloud)") + print("✅ 配置已保存(mi_fitness_cloud)") print(f" User ID: {args.user_id}") print(f" Region: {config.region}") return - print(f"{PROGRAM_NAME} - Setup Wizard") + print(f"{PROGRAM_NAME} - 配置向导") print("=" * 50) print() - user_id = input("Mi Fitness user_id: ").strip() - pass_token = input("Mi Fitness passToken: ").strip() - region = input("Region [ru]: ").strip() or "ru" + user_id = input("Mi Fitness user_id: ").strip() + pass_token = input("Mi Fitness passToken: ").strip() + region = input("区域 [cn]: ").strip() or "cn" if not user_id or not pass_token: - print("❌ Требуются user_id и passToken") + print("❌ 必须提供 user_id 和 passToken") sys.exit(1) save_mi_fitness_token(user_id, pass_token) config = Config(mode="mi_fitness_cloud", region=region) save_config(config) - print("✅ Mi Fitness конфигурация сохранена!") + print("✅ Mi Fitness 配置已保存!") def cmd_doctor(args): - print(f"{PROGRAM_NAME} - Doctor") + print(f"{PROGRAM_NAME} - 诊断") print("=" * 50) print() config_path = get_config_path() - print(f"Конфигурация: {config_path}") + print(f"配置文件: {config_path}") if not config_path.exists(): - print("❌ Конфигурация не найдена") - print(f" Запустите: {PROGRAM_NAME} setup") + print("❌ 未找到配置文件") + print(f" 请运行: {PROGRAM_NAME} setup") sys.exit(1) try: config = load_config() - print("✅ Конфигурация загружена") - print(f" Режим: {config.mode}") + print("✅ 配置已加载") + print(f" 模式: {config.mode}") user_id, pass_token = load_mi_fitness_token() if user_id and pass_token: - print("✅ Mi Fitness credentials найдены") + print("✅ 已找到 Mi Fitness 凭据") print(f" User ID: {user_id}") print(f" Region: {config.region}") adapter = MiFitnessCloudAdapter( user_id=user_id, pass_token=pass_token, region=config.region ) connected, data_types, region = asyncio.run(_check_adapter_health(adapter)) - print(f" Подключение: {'✅' if connected else '❌'}") + print(f" 连接状态: {'✅' if connected else '❌'}") if connected: - print(f" Определённый регион: {region}") - print(f" Типы данных: {', '.join(data_types)}") + print(f" 识别到的区域: {region}") + print(f" 数据类型: {', '.join(data_types)}") else: - print("❌ Mi Fitness credentials не найдены") - print(f" Запустите: {PROGRAM_NAME} setup") + print("❌ 未找到 Mi Fitness 凭据") + print(f" 请运行: {PROGRAM_NAME} setup") print() - print(f"База данных: {config.database_path}") + print(f"数据库: {config.database_path}") if config.database_path.exists(): Database(config.database_path) - print("✅ База данных доступна") + print("✅ 数据库可用") else: - print("ℹ️ База данных будет создана при первом запуске") + print("ℹ️ 数据库将在首次运行时创建") except Exception as e: - print(f"❌ Ошибка загрузки конфигурации: {e}") + print(f"❌ 加载配置失败: {e}") sys.exit(1) async def cmd_sync_async(args): - print(f"{PROGRAM_NAME} - Sync") + print(f"{PROGRAM_NAME} - 同步") print("=" * 50) print() try: config = load_config() except Exception as e: - print(f"❌ Ошибка загрузки конфигурации: {e}") + print(f"❌ 加载配置失败: {e}") sys.exit(1) if config.mode == "not_configured": - print("❌ Сервер не настроен") - print(f" Запустите: {PROGRAM_NAME} setup") + print("❌ 服务尚未配置") + print(f" 请运行: {PROGRAM_NAME} setup") sys.exit(1) user_id, pass_token = load_mi_fitness_token() if not user_id or not pass_token: - print("❌ Mi Fitness credentials не найдены") - print(f" Запустите: {PROGRAM_NAME} setup") + print("❌ 未找到 Mi Fitness 凭据") + print(f" 请运行: {PROGRAM_NAME} setup") sys.exit(1) db = Database(config.database_path) adapter = MiFitnessCloudAdapter(user_id=user_id, pass_token=pass_token, region=config.region) if not await adapter.connect(): - print("❌ Не удалось подключиться к Mi Fitness API") + print("❌ 无法连接到 Mi Fitness API") sys.exit(1) sync_service = SyncService(adapter, db) data_types = [args.type] if args.type else adapter.get_available_data_types() - print(f"Синхронизация {len(data_types)} типов данных...") + print(f"正在同步 {len(data_types)} 种数据类型...") print() for data_type in data_types: try: @@ -132,12 +132,12 @@ async def cmd_sync_async(args): start_date=args.start_date, end_date=args.end_date, ) - print(f"✅ {data_type}: {result['added']} добавлено, {result['updated']} обновлено") + print(f"✅ {data_type}: 新增 {result['added']} 条,更新 {result['updated']} 条") except Exception as e: print(f"❌ {data_type}: {e}") print() - print("Синхронизация завершена!") + print("同步完成!") def cmd_sync(args): @@ -146,20 +146,20 @@ def cmd_sync(args): def main(): parser = argparse.ArgumentParser( - prog=PROGRAM_NAME, description="MCP server for Mi Fitness data" + prog=PROGRAM_NAME, description="小米运动健康数据 MCP Server" ) - subparsers = parser.add_subparsers(dest="command", help="Available commands") - subparsers.add_parser("serve", help="Run MCP server") + subparsers = parser.add_subparsers(dest="command", help="可用命令") + subparsers.add_parser("serve", help="运行 MCP Server") - setup_parser = subparsers.add_parser("setup", help="Configure the server") - setup_parser.add_argument("--mode", choices=["mi_fitness_cloud"], help="Setup mode") + setup_parser = subparsers.add_parser("setup", help="配置服务") + setup_parser.add_argument("--mode", choices=["mi_fitness_cloud"], help="配置模式") setup_parser.add_argument("--user-id", help="Mi Fitness user ID") setup_parser.add_argument("--pass-token", help="Mi Fitness passToken") - setup_parser.add_argument("--region", help="Cloud region") + setup_parser.add_argument("--region", help="云端区域") - subparsers.add_parser("doctor", help="Check configuration and diagnose issues") + subparsers.add_parser("doctor", help="检查配置并诊断问题") - sync_parser = subparsers.add_parser("sync", help="Sync data from source") + sync_parser = subparsers.add_parser("sync", help="从数据源同步数据") sync_parser.add_argument( "--type", choices=[ @@ -172,10 +172,10 @@ def main(): "stress", "abnormal_heart_beat", ], - help="Type of data to sync", + help="要同步的数据类型", ) - sync_parser.add_argument("--start-date", help="Start date (YYYY-MM-DD)") - sync_parser.add_argument("--end-date", help="End date (YYYY-MM-DD)") + sync_parser.add_argument("--start-date", help="开始日期(YYYY-MM-DD)") + sync_parser.add_argument("--end-date", help="结束日期(YYYY-MM-DD)") args = parser.parse_args() if args.command == "serve" or args.command is None: diff --git a/src/mi_fitness_mcp/services/query_service.py b/src/mi_fitness_mcp/services/query_service.py index 77a2d49..249e376 100644 --- a/src/mi_fitness_mcp/services/query_service.py +++ b/src/mi_fitness_mcp/services/query_service.py @@ -24,7 +24,7 @@ class QueryService: """Get daily activity summaries for date range.""" records = self.db.query_daily_activity(self.user_id, start_date, end_date) - # Group by date and aggregate + # 按日期分组并聚合 summaries = {} for record in records: date = record["date"] @@ -73,7 +73,7 @@ class QueryService: } ) - # Apply aggregation if needed + # 按需执行周/月聚合 if granularity == "week": series = self._aggregate_by_week(series, aggregation) elif granularity == "month": @@ -91,7 +91,7 @@ class QueryService: for item in series: date = datetime.strptime(item["date"], "%Y-%m-%d") - # Get week start (Monday) + # 计算周起始日(周一) week_start = date - timedelta(days=date.weekday()) week_key = week_start.strftime("%Y-%m-%d") @@ -160,7 +160,7 @@ class QueryService: sessions = [] for record in records: - # Skip naps if not included + # 不包含小睡时跳过 nap 记录 if not include_naps and record.get("is_nap"): continue @@ -175,7 +175,7 @@ class QueryService: "is_nap": record.get("is_nap", False), } - # Parse stages if available + # 如有睡眠阶段信息则解析 if record.get("stages"): import json @@ -199,7 +199,7 @@ class QueryService: workouts = [] for record in records: - # Apply filters + # 应用过滤条件 if activity_types and record["activity_type"].lower() not in [ t.lower() for t in activity_types ]: @@ -248,7 +248,7 @@ class QueryService: "weight_kg": record["weight_kg"], } - # Add optional metrics + # 添加可选指标 optional_fields = [ "bmi", "body_fat_pct", @@ -264,7 +264,7 @@ class QueryService: if record.get(field) is not None: measurement[field] = record[field] - # Filter metrics if specified + # 如指定指标列表则过滤字段 if metrics: filtered = {"timestamp": measurement["timestamp"]} for metric in metrics: diff --git a/src/mi_fitness_mcp/services/sync_service.py b/src/mi_fitness_mcp/services/sync_service.py index 686a270..5be98b7 100644 --- a/src/mi_fitness_mcp/services/sync_service.py +++ b/src/mi_fitness_mcp/services/sync_service.py @@ -55,7 +55,7 @@ class SyncService: if not self.adapter.is_connected(): raise RuntimeError("Adapter not connected") - # Get last sync state for incremental sync + # 获取上次同步状态,用于增量同步 last_record_ts = None if not force_full: state = self.db.get_sync_state(data_type) @@ -72,7 +72,7 @@ class SyncService: skipped = 0 last_ts = None - # Sync based on data type + # 按数据类型执行同步 if data_type == "daily_activity": records = self.adapter.iter_daily_activity(start_date, end_date) async for activity in self._iterate_records(records): @@ -156,7 +156,7 @@ class SyncService: else: raise ValueError(f"Unknown data type: {data_type}") - # Update sync state + # 更新同步状态 if last_ts: self.db.update_sync_state(data_type, last_ts) diff --git a/src/mi_fitness_mcp/storage/__init__.py b/src/mi_fitness_mcp/storage/__init__.py index f91ae13..85c2a09 100644 --- a/src/mi_fitness_mcp/storage/__init__.py +++ b/src/mi_fitness_mcp/storage/__init__.py @@ -36,7 +36,7 @@ class Database: self.db_path.parent.mkdir(parents=True, exist_ok=True) with self._get_connection() as conn: - # Daily activity table + # 日常活动表 conn.execute(""" CREATE TABLE IF NOT EXISTS daily_activity ( id TEXT PRIMARY KEY, @@ -60,7 +60,7 @@ class Database: ) """) - # Sleep sessions table + # 睡眠记录表 conn.execute(""" CREATE TABLE IF NOT EXISTS sleep_sessions ( id TEXT PRIMARY KEY, @@ -86,7 +86,7 @@ class Database: ) """) - # Workouts table + # 运动记录表 conn.execute(""" CREATE TABLE IF NOT EXISTS workouts ( id TEXT PRIMARY KEY, @@ -115,7 +115,7 @@ class Database: ) """) - # Body measurements table + # 身体测量表 conn.execute(""" CREATE TABLE IF NOT EXISTS body_measurements ( id TEXT PRIMARY KEY, @@ -218,7 +218,7 @@ class Database: ) """) - # Sync state table + # 同步状态表 conn.execute(""" CREATE TABLE IF NOT EXISTS sync_state ( id INTEGER PRIMARY KEY AUTOINCREMENT, @@ -229,7 +229,7 @@ class Database: ) """) - # Create indexes for better query performance + # 创建索引以提升查询性能 conn.execute(""" CREATE INDEX IF NOT EXISTS idx_activity_user_date ON daily_activity(user_id, date) @@ -758,7 +758,7 @@ class Database: with self._get_connection() as conn: results = [] - # Activity coverage + # 日常活动覆盖范围 row = conn.execute( """ SELECT @@ -778,7 +778,7 @@ class Database: } ) - # Sleep coverage + # 睡眠覆盖范围 row = conn.execute( """ SELECT @@ -798,7 +798,7 @@ class Database: } ) - # Workouts coverage + # 运动记录覆盖范围 row = conn.execute( """ SELECT @@ -818,7 +818,7 @@ class Database: } ) - # Body measurements coverage + # 身体测量覆盖范围 row = conn.execute( """ SELECT