mirror of
https://github.com/dptech-corp/Uni-Lab-OS.git
synced 2025-12-17 21:11:12 +00:00
Closes #3. Closes #12. * Update README and MQTTClient for installation instructions and code improvements * feat: 支持local_config启动 add: 增加对crt path的说明,为传入config.py的相对路径 move: web component * add: registry description * feat: node_info_update srv fix: OTDeck cant create * close #12 feat: slave node registry * feat: show machine name fix: host node registry not uploaded * feat: add hplc registry * feat: add hplc registry * fix: hplc status typo * fix: devices/ * fix: device.class possible null * fix: HPLC additions with online service * fix: slave mode spin not working * fix: slave mode spin not working * feat: 多ProtocolNode 允许子设备ID相同 feat: 上报发现的ActionClient feat: Host重启动,通过discover机制要求slaveNode重新注册,实现信息及时上报 --------- Co-authored-by: Harvey Que <Q-Query@outlook.com>
This commit is contained in:
@@ -58,6 +58,18 @@ def parse_args():
|
||||
default=None,
|
||||
help="配置文件路径,支持.py格式的Python配置文件",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=int,
|
||||
default=8002,
|
||||
help="信息页web服务的启动端口",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--open_browser",
|
||||
type=bool,
|
||||
default=True,
|
||||
help="是否在启动时打开信息页",
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
@@ -84,6 +96,9 @@ def main():
|
||||
# 设置BasicConfig参数
|
||||
BasicConfig.is_host_mode = not args_dict.get("without_host", False)
|
||||
BasicConfig.slave_no_host = args_dict.get("slave_no_host", False)
|
||||
machine_name = os.popen("hostname").read().strip()
|
||||
machine_name = "".join([c if c.isalnum() or c == "_" else "_" for c in machine_name])
|
||||
BasicConfig.machine_name = machine_name
|
||||
|
||||
from unilabos.resources.graphio import (
|
||||
read_node_link_json,
|
||||
@@ -151,7 +166,7 @@ def main():
|
||||
mqtt_client.start()
|
||||
|
||||
start_backend(**args_dict)
|
||||
start_server()
|
||||
start_server(port=args_dict.get("port", 8002), open_browser=args_dict.get("open_browser", False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -146,7 +146,7 @@ class MQTTClient:
|
||||
if self.mqtt_disable:
|
||||
return
|
||||
status = {"data": device_status.get(device_id, {}), "device_id": device_id}
|
||||
address = f"labs/{MQConfig.lab_id}/devices"
|
||||
address = f"labs/{MQConfig.lab_id}/devices/"
|
||||
self.client.publish(address, json.dumps(status), qos=2)
|
||||
logger.critical(f"Device status published: address: {address}, {status}")
|
||||
|
||||
@@ -168,11 +168,8 @@ class MQTTClient:
|
||||
if self.mqtt_disable:
|
||||
return
|
||||
address = f"labs/{MQConfig.lab_id}/actions/"
|
||||
action_type_name = action_info["title"]
|
||||
action_info["title"] = action_id
|
||||
action_data = json.dumps({action_type_name: action_info}, ensure_ascii=False)
|
||||
self.client.publish(address, action_data, qos=2)
|
||||
logger.debug(f"Action data published: address: {address}, {action_data}")
|
||||
self.client.publish(address, json.dumps(action_info), qos=2)
|
||||
logger.debug(f"Action data published: address: {address}, {action_id}, {action_info}")
|
||||
|
||||
|
||||
mqtt_client = MQTTClient()
|
||||
|
||||
@@ -92,19 +92,7 @@ def setup_web_pages(router: APIRouter) -> None:
|
||||
|
||||
# 获取已加载的设备
|
||||
if lab_registry:
|
||||
# 设备类型
|
||||
for device_id, device_info in lab_registry.device_type_registry.items():
|
||||
msg = {
|
||||
"id": device_id,
|
||||
"name": device_info.get("name", "未命名"),
|
||||
"file_path": device_info.get("file_path", ""),
|
||||
"class_json": json.dumps(
|
||||
device_info.get("class", {}), indent=4, ensure_ascii=False, cls=TypeEncoder
|
||||
),
|
||||
}
|
||||
mqtt_client.publish_registry(device_id, device_info)
|
||||
devices.append(msg)
|
||||
|
||||
devices = json.loads(json.dumps(lab_registry.obtain_registry_device_info(), ensure_ascii=False, cls=TypeEncoder))
|
||||
# 资源类型
|
||||
for resource_id, resource_info in lab_registry.resource_type_registry.items():
|
||||
resources.append(
|
||||
|
||||
@@ -96,17 +96,19 @@
|
||||
<tr>
|
||||
<th>设备ID</th>
|
||||
<th>命名空间</th>
|
||||
<th>机器名称</th>
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
{% for device_id, device_info in host_node_info.devices.items() %}
|
||||
<tr>
|
||||
<td>{{ device_id }}</td>
|
||||
<td>{{ device_info.namespace }}</td>
|
||||
<td>{{ device_info.machine_name }}</td>
|
||||
<td><span class="status-badge online">{{ "在线" if device_info.is_online else "离线" }}</span></td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr>
|
||||
<td colspan="3" class="empty-state">没有发现已管理的设备</td>
|
||||
<td colspan="4" class="empty-state">没有发现已管理的设备</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
@@ -218,6 +220,7 @@
|
||||
<th>Device ID</th>
|
||||
<th>节点名称</th>
|
||||
<th>命名空间</th>
|
||||
<th>机器名称</th>
|
||||
<th>状态项</th>
|
||||
<th>动作数</th>
|
||||
</tr>
|
||||
@@ -227,6 +230,7 @@
|
||||
<td>{{ device_id }}</td>
|
||||
<td>{{ device_info.node_name }}</td>
|
||||
<td>{{ device_info.namespace }}</td>
|
||||
<td>{{ device_info.machine_name|default("本地") }}</td>
|
||||
<td>{{ ros_node_info.device_topics.get(device_id, {})|length }}</td>
|
||||
<td>{{ ros_node_info.device_actions.get(device_id, {})|length }} <span class="toggle-indicator">▼</span></td>
|
||||
</tr>
|
||||
@@ -329,8 +333,13 @@
|
||||
<tr id="device-info-{{ loop.index }}" class="detail-row" style="display: none;">
|
||||
<td colspan="5">
|
||||
<div class="content-full">
|
||||
<pre>{{ device.class_json }}</pre>
|
||||
|
||||
{% if device.class %}
|
||||
<pre>{{ device.class | tojson(indent=4) }}</pre>
|
||||
{% else %}
|
||||
<!-- 这里可以放占位内容,比如 -->
|
||||
<pre>// No data</pre>
|
||||
{% endif %}
|
||||
|
||||
{% if device.is_online %}
|
||||
<div class="status-badge"><span class="online-status">在线</span></div>
|
||||
{% endif %}
|
||||
@@ -362,7 +371,12 @@
|
||||
<button class="copy-btn" onclick="copyToClipboard(this.previousElementSibling.textContent, event)">复制</button>
|
||||
<button class="debug-btn" onclick="toggleDebugInfo(this, event)">调试</button>
|
||||
<div class="debug-info" style="display:none;">
|
||||
<pre>{{ action_info|tojson(indent=2) }}</pre>
|
||||
{% if action_info %}
|
||||
<pre>{{ action_info | tojson(indent=4) }}</pre>
|
||||
{% else %}
|
||||
<!-- 这里可以放占位内容,比如 -->
|
||||
<pre>// No data</pre>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -30,20 +30,19 @@ def get_host_node_info() -> Dict[str, Any]:
|
||||
return host_info
|
||||
host_info["available"] = True
|
||||
host_info["devices"] = {
|
||||
device_id: {
|
||||
edge_device_id: {
|
||||
"namespace": namespace,
|
||||
"is_online": f"{namespace}/{device_id}" in host_node._online_devices,
|
||||
"key": f"{namespace}/{device_id}" if namespace.startswith("/") else f"/{namespace}/{device_id}",
|
||||
"is_online": f"{namespace}/{edge_device_id}" in host_node._online_devices,
|
||||
"key": f"{namespace}/{edge_device_id}" if namespace.startswith("/") else f"/{namespace}/{edge_device_id}",
|
||||
"machine_name": host_node.device_machine_names.get(edge_device_id, "未知"),
|
||||
}
|
||||
for device_id, namespace in host_node.devices_names.items()
|
||||
for edge_device_id, namespace in host_node.devices_names.items()
|
||||
}
|
||||
# 获取已订阅的主题
|
||||
host_info["subscribed_topics"] = sorted(list(host_node._subscribed_topics))
|
||||
# 获取动作客户端信息
|
||||
for action_id, client in host_node._action_clients.items():
|
||||
host_info["action_clients"] = {
|
||||
action_id: get_action_info(client, full_name=action_id)
|
||||
}
|
||||
host_info["action_clients"] = {action_id: get_action_info(client, full_name=action_id)}
|
||||
|
||||
# 获取设备状态
|
||||
host_info["device_status"] = host_node.device_status
|
||||
|
||||
@@ -12,6 +12,7 @@ from unilabos.app.web.utils.action_utils import get_action_info
|
||||
# 存储 ROS 节点信息的全局变量
|
||||
ros_node_info = {"online_devices": {}, "device_topics": {}, "device_actions": {}}
|
||||
|
||||
|
||||
def get_ros_node_info() -> Dict[str, Any]:
|
||||
"""获取 ROS 节点信息,包括设备节点、发布的状态和动作
|
||||
|
||||
@@ -35,6 +36,13 @@ def update_ros_node_info() -> Dict[str, Any]:
|
||||
|
||||
try:
|
||||
from unilabos.ros.nodes.base_device_node import registered_devices
|
||||
from unilabos.ros.nodes.presets.host_node import HostNode
|
||||
|
||||
# 尝试获取主机节点实例
|
||||
host_node = HostNode.get_instance(0)
|
||||
device_machine_names = {}
|
||||
if host_node:
|
||||
device_machine_names = host_node.device_machine_names
|
||||
|
||||
for device_id, device_info in registered_devices.items():
|
||||
# 设备基本信息
|
||||
@@ -42,6 +50,7 @@ def update_ros_node_info() -> Dict[str, Any]:
|
||||
"node_name": device_info["node_name"],
|
||||
"namespace": device_info["namespace"],
|
||||
"uuid": device_info["uuid"],
|
||||
"machine_name": device_machine_names.get(device_id, "本地"),
|
||||
}
|
||||
|
||||
# 设备话题(状态)信息
|
||||
@@ -55,10 +64,7 @@ def update_ros_node_info() -> Dict[str, Any]:
|
||||
}
|
||||
|
||||
# 设备动作信息
|
||||
result["device_actions"][device_id] = {
|
||||
k: get_action_info(v, k)
|
||||
for k, v in device_info["actions"].items()
|
||||
}
|
||||
result["device_actions"][device_id] = {k: get_action_info(v, k) for k, v in device_info["actions"].items()}
|
||||
# 更新全局变量
|
||||
ros_node_info = result
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user