Initial project setup with core files including .gitignore, LICENSE, pyproject.toml, and main application structure. Added MQTT handling and navigation server functionality for robot control.
This commit is contained in:
170
temi_mcpserver/.gitignore
vendored
Normal file
170
temi_mcpserver/.gitignore
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# UV
|
||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
#uv.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
1
temi_mcpserver/.python-version
Normal file
1
temi_mcpserver/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.10
|
||||
73
temi_mcpserver/LICENSE
Normal file
73
temi_mcpserver/LICENSE
Normal file
@@ -0,0 +1,73 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
|
||||
|
||||
Copyright 2025 lzwc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
26
temi_mcpserver/pyproject.toml
Normal file
26
temi_mcpserver/pyproject.toml
Normal file
@@ -0,0 +1,26 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "temi_Robot"
|
||||
version = "0.1.15"
|
||||
description = "MQTT-based navigation server for robot"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"fastapi>=0.95.0",
|
||||
"uvicorn>=0.21.0",
|
||||
"paho-mqtt>=2.0.0",
|
||||
"pydantic>=1.10.0",
|
||||
"python-dotenv>=0.21.0",
|
||||
"mcp[cli]>=1.6.0",
|
||||
"requests>=2.25.0"
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
temi_Robot = "temiRobot.temi_mcpserver:main"
|
||||
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["temiRobot"]
|
||||
|
||||
0
temi_mcpserver/temiRobot/__init__.py
Normal file
0
temi_mcpserver/temiRobot/__init__.py
Normal file
113
temi_mcpserver/temiRobot/mcp_mqtt.py
Normal file
113
temi_mcpserver/temiRobot/mcp_mqtt.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import paho.mqtt.client as mqtt
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
from typing import Optional
|
||||
import uuid
|
||||
import threading
|
||||
import requests
|
||||
from os import getenv
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
MQTT_CLIENT_ID = f"MCPMQTT-{uuid.uuid4().hex[:8]}"
|
||||
|
||||
def getConfig_url(array):
|
||||
url = "http://lzwcai-demp-corp-manager:8086/system/config/getConfig"
|
||||
data = [array]
|
||||
try:
|
||||
response = requests.post(url, json=data, timeout=5)
|
||||
response.raise_for_status()
|
||||
data = response.json()['data']
|
||||
return data[0]['configValue']
|
||||
except Exception as e:
|
||||
print(f"Error fetching config for {array}: {e}")
|
||||
return None
|
||||
|
||||
class MQTTHandler:
|
||||
def __init__(self):
|
||||
self.client = mqtt.Client()
|
||||
self.tasks = {} # {task_id: task_data}
|
||||
self._lock = threading.Lock()
|
||||
|
||||
# 获取MQTT配置,提供默认值
|
||||
self.mqtt_username = getenv('MQTT_USERNAME') or 'lzwc'
|
||||
self.mqtt_password = getenv('MQTT_PASSWORD') or 'Lzwc@4187.'
|
||||
mqtt_broker_raw = getenv('MQTT_BROKER') or 'tcp://dempmana.lzwcai.com'
|
||||
# 移除协议前缀,只保留主机名
|
||||
self.mqtt_broker = mqtt_broker_raw.replace('tcp://', '').replace('mqtt://', '')
|
||||
mqtt_port_str = getenv('MQTT_PORT')
|
||||
self.mqtt_port = int(mqtt_port_str) if mqtt_port_str else 1883
|
||||
|
||||
# 记录配置信息
|
||||
logger.info(f"MQTT配置 - Broker: {self.mqtt_broker}, Port: {self.mqtt_port}, Username: {self.mqtt_username}")
|
||||
if not getenv('MQTT_BROKER'):
|
||||
logger.warning("MQTT_BROKER环境变量未设置,使用默认值")
|
||||
if not mqtt_port_str:
|
||||
logger.warning("MQTT_PORT环境变量未设置,使用默认端口1883")
|
||||
|
||||
self.client.username_pw_set(self.mqtt_username, self.mqtt_password)
|
||||
self.client.on_connect = self._on_connect
|
||||
self.client.on_message = self._on_message
|
||||
self.client.on_disconnect = self._on_disconnect
|
||||
try:
|
||||
logger.info(f"正在连接 MQTT 代理: {self.mqtt_broker}:{self.mqtt_port}")
|
||||
self.client.connect(self.mqtt_broker, self.mqtt_port, 60)
|
||||
self.client.loop_start()
|
||||
except Exception as e:
|
||||
logger.error(f"连接 MQTT 失败: {e}")
|
||||
|
||||
|
||||
def _on_connect(self, client, userdata, flags, rc):
|
||||
"""MQTT 连接回调"""
|
||||
if rc == 0:
|
||||
logger.info("已成功连接到 MQTT 代理服务器")
|
||||
else:
|
||||
logger.error(f"连接 MQTT 代理服务器失败,返回码: {rc}")
|
||||
|
||||
def get_status(self, task_id: str = None):
|
||||
"""获取任务状态
|
||||
Args:
|
||||
task_id: 可选参数,指定任务ID。如果为None则返回所有任务状态
|
||||
Returns:
|
||||
如果指定task_id则返回该任务的状态,否则返回所有任务状态
|
||||
"""
|
||||
with self._lock:
|
||||
if task_id:
|
||||
return self.tasks.get(task_id, {}).copy()
|
||||
return {k: v.copy() for k, v in self.tasks.items()}
|
||||
|
||||
def _on_message(self, client, userdata, msg):
|
||||
try:
|
||||
payload = json.loads(msg.payload.decode())
|
||||
logger.info(f"[MQTT] topic={msg.topic} msg: {payload}")
|
||||
|
||||
# task_id = payload.get('task_id')
|
||||
# if task_id and task_id in self.tasks:
|
||||
# with self._lock:
|
||||
# self.tasks[task_id].update({
|
||||
# 'status': payload.get('status', 'UNKNOWN'),
|
||||
# 'description': payload.get('description', '')
|
||||
# })
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"[错误] 处理消息失败: {e}")
|
||||
|
||||
def _on_disconnect(self, client, userdata, rc):
|
||||
"""MQTT 断开连接回调"""
|
||||
if rc != 0:
|
||||
logger.warning("意外断开与 MQTT 代理服务器的连接。")
|
||||
try:
|
||||
logger.info("尝试重新连接 MQTT...")
|
||||
client.reconnect()
|
||||
except Exception as e:
|
||||
logger.error(f"重新连接 MQTT 失败: {e}")
|
||||
|
||||
# 单例模式
|
||||
_instance: Optional[MQTTHandler] = None
|
||||
|
||||
def get_mcpmqtt_handler() -> MQTTHandler:
|
||||
"""获取单例"""
|
||||
global _instance
|
||||
if _instance is None:
|
||||
_instance = MQTTHandler()
|
||||
return _instance
|
||||
489
temi_mcpserver/temiRobot/temi_mcpserver.py
Normal file
489
temi_mcpserver/temiRobot/temi_mcpserver.py
Normal file
@@ -0,0 +1,489 @@
|
||||
from typing import Optional, Sequence
|
||||
import logging
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from mcp.server import Server
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.types import Tool, TextContent
|
||||
import asyncio
|
||||
import json
|
||||
import time
|
||||
import uuid
|
||||
from .mcp_mqtt import get_mcpmqtt_handler
|
||||
|
||||
# 配置日志
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class TourStop(BaseModel):
|
||||
name: str
|
||||
text: str
|
||||
|
||||
|
||||
class NavServer:
|
||||
def __init__(self, mmhandler=None):
|
||||
self.mmhandler = mmhandler or get_mcpmqtt_handler()
|
||||
|
||||
async def publish_Cmd(self, device_id: str, cmd: str, params: dict):
|
||||
try:
|
||||
payload = {
|
||||
"device_id": device_id,
|
||||
"cmd": cmd,
|
||||
"data": params["data"]
|
||||
}
|
||||
self.mmhandler.client.publish("robot/cmd", json.dumps(payload), qos=2)
|
||||
return f"{cmd}工具执行成功"
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to publish {cmd} command: {str(e)}", exc_info=True)
|
||||
raise
|
||||
|
||||
async def nav_to(self, location: str):
|
||||
"""
|
||||
机器人导航去某个地方。
|
||||
Args:
|
||||
- location (str): 目标地点名称
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
"""
|
||||
if location:
|
||||
try:
|
||||
if location == "充电桩":
|
||||
location = "home base"
|
||||
params = {
|
||||
"data": {"location": location}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "nav", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call navigation mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def speak(self, speech: str):
|
||||
"""
|
||||
机器人说某些话。
|
||||
Args:
|
||||
- speech (str): 说话内容
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
"""
|
||||
if speech:
|
||||
try:
|
||||
params = {
|
||||
"data": {"speech": speech}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "speak", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call speak mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def reception(self, location: str):
|
||||
"""
|
||||
机器人接待客人。
|
||||
Args:
|
||||
- location (str): 机器人去这个位置接待客人
|
||||
- task_id (Optional[str]): 任务id,不传则自动生成
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
"""
|
||||
if location :
|
||||
try:
|
||||
params = {
|
||||
"data": {
|
||||
"location": location
|
||||
}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "reception", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call reception mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def notification(self, location: str, text: str):
|
||||
"""
|
||||
机器人通知事情。
|
||||
Args:
|
||||
- location (str): 机器人去这个位置通知事情
|
||||
- text (str): 通知事情的内容
|
||||
- task_id (Optional[str]): 任务id,不传则自动生成
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
"""
|
||||
if location :
|
||||
try:
|
||||
params = {
|
||||
"data": {
|
||||
"location": location,
|
||||
"text": text
|
||||
}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "notification", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call notification mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
async def repose(self):
|
||||
"""
|
||||
机器人重新定位。
|
||||
"""
|
||||
try:
|
||||
params = {
|
||||
"data": {
|
||||
"action": "repose"
|
||||
}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "map", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call repose mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def delivery(self, first_location: str, next_location: str):
|
||||
"""
|
||||
机器人运送物品。
|
||||
Args:
|
||||
- first_location (str): 机器人去这个位置取物品
|
||||
- next_location (str): 机器人将物品送到这个位置
|
||||
- task_id (Optional[str]): 任务id,不传则自动生成
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
|
||||
"""
|
||||
if first_location and next_location:
|
||||
try:
|
||||
params = {
|
||||
"data": {
|
||||
"first_location": first_location,
|
||||
"next_location": next_location
|
||||
}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "delivery", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call delivery mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def startFaceRecognize(self, device_id: str):
|
||||
"""
|
||||
机器人进行人脸识别。
|
||||
Args:
|
||||
- device_id (str): 设备id
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
"""
|
||||
if device_id:
|
||||
try:
|
||||
params = {
|
||||
"data": {}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "face", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call face recognize mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def scanQRCode(self, device_id: str):
|
||||
"""
|
||||
机器人进行二维码扫描。
|
||||
Args:
|
||||
- device_id (str): 设备id
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
"""
|
||||
if device_id:
|
||||
try:
|
||||
params = {
|
||||
"data": {}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "qrcode", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call QR code scan mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def save_position(self, device_id: str, location_name: str):
|
||||
"""
|
||||
机器人保存具体地点。
|
||||
Args:
|
||||
- device_id (str): 设备id
|
||||
- location_name (str): 地点名称
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
|
||||
"""
|
||||
if device_id and location_name:
|
||||
try:
|
||||
params = {
|
||||
"data": {"location_name": location_name}
|
||||
}
|
||||
return await self.publish_Cmd("123456", "saveLocation", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call save_position mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
|
||||
async def guide(self):
|
||||
"""
|
||||
机器人导览。
|
||||
Args:
|
||||
Returns:
|
||||
str: 命令执行结果消息
|
||||
|
||||
Raises:
|
||||
ValueError: 如果参数无效或为空
|
||||
Exception: 如果MQTT发布失败
|
||||
"""
|
||||
try:
|
||||
datas = [
|
||||
TourStop(name="前台", text="欢迎来到我们的场所,这是入口区域"),
|
||||
TourStop(name="灵泽万川展厅", text="这是我们的主大厅,提供接待服务"),
|
||||
TourStop(name="爱易拍展厅", text="这里展示我们的主要产品和成果")
|
||||
]
|
||||
params = {
|
||||
"data": [data.model_dump() for data in datas]
|
||||
}
|
||||
return await self.publish_Cmd("123456", "guide", params)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to call guide mcp-tool: {str(e)} ", exc_info=True)
|
||||
raise
|
||||
|
||||
async def serve() -> None:
|
||||
server = Server("temi_mcp")
|
||||
mmhandler = get_mcpmqtt_handler()
|
||||
nav_server = NavServer(mmhandler)
|
||||
|
||||
@server.list_tools()
|
||||
async def list_tools() -> list[Tool]:
|
||||
"""列出所有工具"""
|
||||
return [
|
||||
Tool(
|
||||
name="nav_to",
|
||||
description="数字员工、终端、具身机器人、前台机器人、助手、机器人导航带路到指定地点:带我去、去XX、请引路、导航去、我想去、能带我去吗、带路、送我去、在哪里、前往XX、去XX位置、带我过去、请带我到、我想前往、去那个地方、我要去、能不能去、带我导航、这个地方怎么走、我想找XX、导航带我去、请带路到、我要去那边、走一趟去、去下XX、到XX怎么走",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "目标地点名称",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="speak",
|
||||
description="数字员工、终端、具身机器人、前台机器人、助手、机器人进行语音播报:告诉、提醒、告知、提示",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"speech": {
|
||||
"type": "string",
|
||||
"description": "要播报的语音内容",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["speech"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="reception",
|
||||
description="数字员工、终端、具身机器人、前台机器人、助手、机器人去接待客人:去接人、请迎接客人、去接待、迎接一下、带人过来、把客人带过来、去门口接人、去接一下、请接人、接客人、帮我迎接、去把人接过来、接待来访者、请去接一下客人、送客人过来、从XX接到YY、引导访客、带访客过来、请引领客人、去门口把人接进来、从入口接人、去接待处接人、带客人进来",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "引导接待客人到这个位置",
|
||||
"minLength": 1
|
||||
},
|
||||
},
|
||||
"required": ["location"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="notification",
|
||||
description="数字员工、终端、具身机器人、前台机器人、助手、机器人去通知事情:通知、告诉、提醒、告知、提示",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"location": {
|
||||
"type": "string",
|
||||
"description": "去这个位置通知事情",
|
||||
"minLength": 1
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"description": "通知事情的内容",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["location","text"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="repose",
|
||||
description="数字员工、终端、具身机器人、前台机器人、助手、机器人去重新定位",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="delivery",
|
||||
description="数字员工、终端、具身机器人、前台机器人、助手、机器人去运输物品:帮我送东西、去送一下、去送物品、帮我拿过去、送这个过去、去送快递、运送一下、帮我带东西、把东西送过去、物品配送、拿去给XX、去取个东西、取完送过去、帮我捎个东西、把这个拿去、请运过去、送过去给谁、请配送一下、帮我转交、帮我送到、从XX拿到YY、物品搬运",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"first_location": {
|
||||
"type": "string",
|
||||
"description": "去这个位置取物品",
|
||||
"minLength": 1
|
||||
},
|
||||
"next_location": {
|
||||
"type": "string",
|
||||
"description": "将物品运输、送到这个位置",
|
||||
"minLength": 1
|
||||
}
|
||||
},
|
||||
"required": ["first_location","next_location"]
|
||||
}
|
||||
),
|
||||
# Tool(
|
||||
# name="guide",
|
||||
# description="数字员工、终端、具身机器人、前台机器人、助手、机器人进行导览服务:导览、介绍、参观、带领参观、导游、讲解、展示、演示、介绍场所、带我看看",
|
||||
# inputSchema={
|
||||
# "type": "object",
|
||||
# "properties": {
|
||||
# },
|
||||
# "required": []
|
||||
# }
|
||||
# )
|
||||
# Tool(
|
||||
# name="face",
|
||||
# description="数字员工、终端、具身机器人、前台机器人、助手、机器人进行人脸识别:识别人脸、人脸识别、脸部识别、面部识别、认脸、看看是谁、识别一下、看看这个人、辨识人脸、扫脸、人脸检测、看脸识别、脸部检测、识别面孔、看人脸、人脸扫描、脸识别、面孔识别、认人、识别身份、看看这是谁、人脸认证、脸部扫描、面部扫描",
|
||||
# inputSchema={
|
||||
# "type": "object",
|
||||
# "properties": {
|
||||
# "device_id": {
|
||||
# "type": "string",
|
||||
# "description": "设备ID",
|
||||
# "minLength": 1
|
||||
# },
|
||||
# },
|
||||
# "required": ["device_id"]
|
||||
# }
|
||||
# ),
|
||||
# Tool(
|
||||
# name="qrcode",
|
||||
# description="数字员工、终端、具身机器人、前台机器人、助手、机器人进行二维码扫描:扫码、扫二维码、扫QR码、二维码识别、码扫描、扫一下码、读取二维码、识别二维码、二维码扫描、QR扫描、扫描条码、扫描编码、读码、解析二维码、扫描识别、码识别、条码扫描、扫描二维码、读取码、扫描条形码、二维码读取、码解析",
|
||||
# inputSchema={
|
||||
# "type": "object",
|
||||
# "properties": {
|
||||
# "device_id": {
|
||||
# "type": "string",
|
||||
# "description": "设备ID",
|
||||
# "minLength": 1
|
||||
# },
|
||||
# "task_id": {
|
||||
# "type": "string",
|
||||
# "description": "任务ID"
|
||||
# }
|
||||
# },
|
||||
# "required": ["device_id"]
|
||||
# }
|
||||
# )
|
||||
]
|
||||
|
||||
@server.call_tool()
|
||||
async def call_tool(name: str, arguments: dict) -> Sequence[TextContent]:
|
||||
"""处理工具调用"""
|
||||
try:
|
||||
result = ""
|
||||
if name == "nav_to":
|
||||
if "location" not in arguments:
|
||||
raise ValueError("缺少必要参数: location")
|
||||
result = await nav_server.nav_to(
|
||||
location=arguments["location"]
|
||||
)
|
||||
elif name == "speak":
|
||||
if "speech" not in arguments:
|
||||
raise ValueError("缺少必要参数: speech")
|
||||
result = await nav_server.speak(
|
||||
speech=arguments["speech"]
|
||||
)
|
||||
elif name == "reception":
|
||||
if "location" not in arguments:
|
||||
raise ValueError("缺少必要参数: location")
|
||||
result = await nav_server.reception(
|
||||
location=arguments["location"],
|
||||
)
|
||||
elif name == "notification":
|
||||
if "location" not in arguments or "text" not in arguments:
|
||||
raise ValueError("缺少必要参数: location或text")
|
||||
result = await nav_server.notification(
|
||||
location=arguments["location"],
|
||||
text=arguments["text"]
|
||||
)
|
||||
elif name == "repose":
|
||||
result = await nav_server.repose()
|
||||
elif name == "delivery":
|
||||
if "first_location" not in arguments or "next_location" not in arguments:
|
||||
raise ValueError("缺少必要参数: first_location或next_location")
|
||||
result = await nav_server.delivery(
|
||||
first_location=arguments["first_location"],
|
||||
next_location=arguments["next_location"]
|
||||
)
|
||||
# elif name == "face":
|
||||
# if "device_id" not in arguments:
|
||||
# raise ValueError("缺少必要参数: device_id")
|
||||
# result = await nav_server.startFaceRecognize(
|
||||
# device_id=arguments["device_id"],
|
||||
# )
|
||||
# elif name == "qrcode":
|
||||
# if "device_id" not in arguments:
|
||||
# raise ValueError("缺少必要参数: device_id")
|
||||
# result = await nav_server.scanQRCode(
|
||||
# device_id=arguments["device_id"],
|
||||
# )
|
||||
else:
|
||||
raise ValueError(f"未知工具: {name}")
|
||||
return [TextContent(type="text", text=result)]
|
||||
except Exception as e:
|
||||
logger.error(f"工具调用失败: {str(e)}")
|
||||
raise ValueError(f"执行失败: {str(e)}")
|
||||
|
||||
options = server.create_initialization_options()
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await server.run(read_stream, write_stream, options)
|
||||
|
||||
def main():
|
||||
asyncio.run(serve())
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user