Skip to content

Commit

Permalink
Revert "chore: remove rai_hmi"
Browse files Browse the repository at this point in the history
This reverts commit b580439.
  • Loading branch information
maciejmajek committed Mar 7, 2025
1 parent 69b3805 commit 58f0789
Show file tree
Hide file tree
Showing 16 changed files with 1,294 additions and 0 deletions.
45 changes: 45 additions & 0 deletions src/rai_hmi/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# RAI HMI

The RAI HMI (Human-Machine Interface) allows users to converse with the robot and order new tasks to be added to the queue. Communication topics use plaintext, but can be connected from ASR (Automatic Speech Recognition) and to TTS (Text To Speech) nodes to enable a voice interface.

> **NOTE:** Currently the node is tailored for the Husarion ROSBot XL demo use case. It is expected that it can be generalized to other use cases when `rai_whoami` package is fully developed and integrated.
## ROS 2 Interface

### Subscribed Topics

- **`from_human`** (`std_msgs/String`): Incoming plaintext messages from the user.

### Published Topics

- **`to_human`** (`std_msgs/String`): Outgoing plaintext messages for the user.
- **`task_addition_requests`** (`std_msgs/String`): Tasks to be added to the queue, in JSON format.

## Task JSON Schema

Tasks published on the `task_addition_request` follow the schema below:

```json
{
"$schema": "http://json-schema.org/draft-04/schema#",
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"priority": {
"type": "string",
"enum": ["highest", "high", "medium", "low", "lowest"]
},
"robot": {
"type": "string"
}
},
"required": ["name", "description", "priority"]
}
```

Optional field `"robot"` is currently not implemented and is intended to be used in a fleet setting, where a specific robot can be requested to perform the task in question.
15 changes: 15 additions & 0 deletions src/rai_hmi/package.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>rai_hmi</name>
<version>0.1.0</version>
<description>A Human-Machine Interface (HMI) to converse with the robot.</description>
<maintainer email="[email protected]">Adam Gotlib</maintainer>
<license>Apache-2.0</license>

<test_depend>python3-pytest</test_depend>

<export>
<build_type>ament_python</build_type>
</export>
</package>
13 changes: 13 additions & 0 deletions src/rai_hmi/rai_hmi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Copyright (C) 2024 Robotec.AI
#
# 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.
64 changes: 64 additions & 0 deletions src/rai_hmi/rai_hmi/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Copyright (C) 2024 Robotec.AI
#
# 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.


import uuid
from typing import List

from langchain.tools import tool
from rai.agents.conversational_agent import create_conversational_agent
from rai.node import RaiBaseNode
from rai.tools.ros.native import GetCameraImage, Ros2GetRobotInterfaces
from rai.utils.model_initialization import get_llm_model

from rai_hmi.base import BaseHMINode
from rai_hmi.chat_msgs import MissionMessage
from rai_hmi.task import Task, TaskInput
from rai_hmi.text_hmi_utils import Memory


def initialize_agent(hmi_node: BaseHMINode, rai_node: RaiBaseNode, memory: Memory):
llm = get_llm_model(model_type="complex_model")

@tool
def get_mission_memory(uid: str) -> List[MissionMessage]:
"""List mission memory. It contains the information about running tasks. Mission uid is required."""
return memory.get_mission_memory(uid)

@tool
def submit_mission_to_the_robot(task: TaskInput):
"""Use this tool submit the task to the robot. The task will be handled by the executor part of your system."""

uid = uuid.uuid4()
hmi_node.execute_mission(
Task(
name=task.name,
description=task.description,
priority=task.priority,
uid=str(uid),
)
)
return f"UID={uid} | Task added to the queue: {task.model_dump_json()}"

node_tools = tools = [
Ros2GetRobotInterfaces(node=rai_node),
GetCameraImage(node=rai_node),
]
task_tools = [submit_mission_to_the_robot, get_mission_memory]
tools = hmi_node.tools + node_tools + task_tools

agent = create_conversational_agent(
llm, tools, hmi_node.system_prompt, logger=hmi_node.get_logger()
)
return agent
63 changes: 63 additions & 0 deletions src/rai_hmi/rai_hmi/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright (C) 2024 Robotec.AI
#
# 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.

import re
from abc import abstractmethod
from queue import Queue

from rclpy.callback_groups import ReentrantCallbackGroup
from rclpy.qos import DurabilityPolicy, HistoryPolicy, QoSProfile, ReliabilityPolicy
from std_msgs.msg import String

from rai_hmi.base import BaseHMINode


def split_message(message: str):
sentences = re.split(r"(?<=\.)\s|[:!]", message)
for sentence in sentences:
if sentence:
yield sentence


class GenericVoiceNode(BaseHMINode):
def __init__(self, node_name: str, queue: Queue, robot_description_package: str):
super().__init__(node_name, queue, robot_description_package)

self.callback_group = ReentrantCallbackGroup()
reliable_qos = QoSProfile(
reliability=ReliabilityPolicy.RELIABLE,
durability=DurabilityPolicy.TRANSIENT_LOCAL,
history=HistoryPolicy.KEEP_ALL,
)
self.hmi_subscription = self.create_subscription(
String,
"/from_human",
self.handle_human_message,
qos_profile=reliable_qos,
)
self.hmi_publisher = self.create_publisher(
String,
"/to_human",
qos_profile=reliable_qos,
callback_group=self.callback_group,
)

@abstractmethod
def _handle_human_message(self, msg: String):
pass

def handle_human_message(self, msg: String):
self.processing = True
self._handle_human_message(msg)
self.processing = False
Loading

0 comments on commit 58f0789

Please sign in to comment.