From b032548d55ceba7d471a6e4108b1cd4fc5fb295a Mon Sep 17 00:00:00 2001 From: ChenXL97 <908926798@qq.com> Date: Thu, 16 Nov 2023 15:04:27 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=9C=A8tool=5Fapi=5Fmulti=5Fround.py?= =?UTF-8?q?=E4=B8=AD=E5=8F=AF=E4=BB=A5=E8=BF=9B=E8=A1=8C=E5=A4=9A=E8=BD=AE?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- robowaiter/llm_client/tool_api.py | 1 + robowaiter/llm_client/tool_api_multi_round.py | 78 +++++++++++++++++++ robowaiter/llm_client/tool_api_test.py | 74 ++++++++++++++++++ robowaiter/llm_client/tool_register.py | 15 ++-- robowaiter/scene/tasks/Auto_tasks.py | 5 +- robowaiter/scene/tasks/GQA.py | 4 +- robowaiter/scene/tasks/Open_tasks.py | 4 +- robowaiter/scene/tasks/Open_tasks_test.py | 13 +--- 8 files changed, 171 insertions(+), 23 deletions(-) create mode 100644 robowaiter/llm_client/tool_api_multi_round.py create mode 100644 robowaiter/llm_client/tool_api_test.py diff --git a/robowaiter/llm_client/tool_api.py b/robowaiter/llm_client/tool_api.py index e8cc481..3b32e63 100644 --- a/robowaiter/llm_client/tool_api.py +++ b/robowaiter/llm_client/tool_api.py @@ -32,6 +32,7 @@ functions = get_tools() def run_conversation(query: str, stream=False, max_retry=5): params = dict(model="chatglm3", messages=[{"role": "user", "content": query}], stream=stream) params["functions"] = functions + print(params) response = get_response(**params) for _ in range(max_retry): diff --git a/robowaiter/llm_client/tool_api_multi_round.py b/robowaiter/llm_client/tool_api_multi_round.py new file mode 100644 index 0000000..0df90d9 --- /dev/null +++ b/robowaiter/llm_client/tool_api_multi_round.py @@ -0,0 +1,78 @@ +import json + +import openai +from colorama import init, Fore +from loguru import logger +import json +from robowaiter.llm_client.tool_register import get_tools, dispatch_tool +import requests +import json + +import urllib3 +init(autoreset=True) + +######################################## +# 该文件实现了与大模型的通信以及工具调用 +######################################## + +# 忽略https的安全性警告 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +base_url = "https://45.125.46.134:25344" # 本地部署的地址,或者使用你访问模型的API地址 + +def get_response(**kwargs): + data = kwargs + + response = requests.post(f"{base_url}/v1/chat/completions", json=data, stream=data["stream"], verify=False) + decoded_line = response.json() + return decoded_line + +functions = get_tools() + + + + +if __name__ == "__main__": + question = input("\n顾客:") + data_memory = [{ + "role": "system", + "content": "你是RoboWaiter,一个由HPCL团队开发的机器人服务员,你在咖啡厅工作。接受顾客的指令并调用工具函数来完成各种服务任务。", + },] + n = 1 + max_retry = 5 + params = dict(model="RoboWaiter",messages=data_memory, stream=False) + params["functions"] = functions + + while question != 'end': + user_dict = {"role": "user", "content": question} + params["messages"].append(user_dict) + + # print(data_memory) + response = get_response(**params) + for _ in range(max_retry): + if response["choices"][0]["message"].get("function_call"): + function_call = response["choices"][0]["message"]["function_call"] + logger.info(f"Function Call Response: {function_call}") + + function_args = json.loads(function_call["arguments"]) + tool_response = dispatch_tool(function_call["name"], function_args) + logger.info(f"Tool Call Response: {tool_response}") + + return_message = response["choices"][0]["message"] + params["messages"].append(return_message) + t = { + "role": "function", + "name": function_call["name"], + "content": tool_response, # 调用函数返回结果 + } + params["messages"].append(t) + response = get_response(**params) + + else: + return_message = response["choices"][0]["message"] + reply = return_message["content"] + params["messages"].append(return_message) + logger.info(f"Final Reply: \n{reply}") + break + + question = input("\n顾客:") diff --git a/robowaiter/llm_client/tool_api_test.py b/robowaiter/llm_client/tool_api_test.py new file mode 100644 index 0000000..e8cc481 --- /dev/null +++ b/robowaiter/llm_client/tool_api_test.py @@ -0,0 +1,74 @@ +import json + +import openai +from colorama import init, Fore +from loguru import logger +import json +from robowaiter.llm_client.tool_register import get_tools, dispatch_tool +import requests +import json + +import urllib3 +init(autoreset=True) + +######################################## +# 该文件实现了与大模型的通信以及工具调用 +######################################## + +# 忽略https的安全性警告 +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + +base_url = "https://45.125.46.134:25344" # 本地部署的地址,或者使用你访问模型的API地址 + +def get_response(**kwargs): + data = kwargs + + response = requests.post(f"{base_url}/v1/chat/completions", json=data, stream=data["stream"], verify=False) + decoded_line = response.json() + return decoded_line + +functions = get_tools() + +def run_conversation(query: str, stream=False, max_retry=5): + params = dict(model="chatglm3", messages=[{"role": "user", "content": query}], stream=stream) + params["functions"] = functions + response = get_response(**params) + + for _ in range(max_retry): + if response["choices"][0]["message"].get("function_call"): + function_call = response["choices"][0]["message"]["function_call"] + logger.info(f"Function Call Response: {function_call}") + if "sub_task" in function_call["name"]: + return { + "Answer": "好的", + "Goal": json.loads(function_call["arguments"])["goal"] + } + + function_args = json.loads(function_call["arguments"]) + tool_response = dispatch_tool(function_call["name"], function_args) + logger.info(f"Tool Call Response: {tool_response}") + + params["messages"].append(response["choices"][0]["message"]) + params["messages"].append( + { + "role": "function", + "name": function_call["name"], + "content": tool_response, # 调用函数返回结果 + } + ) + else: + reply = response["choices"][0]["message"]["content"] + return { + "Answer": reply, + "Goal": None + } + logger.info(f"Final Reply: \n{reply}") + return + + response = get_response(**params) + + + +if __name__ == "__main__": + query = "可以带我去吗" + print(run_conversation(query, stream=False)) diff --git a/robowaiter/llm_client/tool_register.py b/robowaiter/llm_client/tool_register.py index 2bdcff9..4360677 100644 --- a/robowaiter/llm_client/tool_register.py +++ b/robowaiter/llm_client/tool_register.py @@ -132,8 +132,13 @@ def create_sub_task( 当需要完成具身任务(如做咖啡,拿放物体,扫地,前往某位置)时,调用该函数,根据用户的提示进行意图理解,生成子任务的目标状态集合 `goal`(以一阶逻辑的形式表示),用户意图 做一杯咖啡,则该函数的参数为 "On(Coffee,Bar)", 前往一号桌,则该函数的参数为 "At(Robot,Table1)", - 打开空调,则该函数的参数为 "Is(AC,On)",。 - 关空调,则该函数的参数为 "Is(AC,Off)",。 + 前往二号桌,则该函数的参数为 "At(Robot,Table2)", + 打开空调,则该函数的参数为 "Is(AC,On)", + 关空调,则该函数的参数为 "Is(AC,Off)", + 打开窗帘,则该函数的参数为 "Is(Curtain,On)", + 关闭窗帘,则该函数的参数为 "Is(Curtain,Off)", + 拖地,则该函数的参数为 "Is(Floor,Clean)", + 打开大厅灯,则该函数的参数为 "Is(HallLight,On)", """ return goal @@ -143,9 +148,9 @@ def get_object_info( obj: Annotated[str, '需要获取信息的物体名称', True] ) -> str: """ - 获取场景中指定物体 `object` 在哪里, - 如果`object` 是一个地点,例如洗手间,地方,则输出。 - 如果`object`是一个咖啡,则输出。 + 获取场景中指定物体 `object` 在哪里,不涉及到具体的执行任务 + 如果`object` 是一个地点,例如洗手间,则输出大门。 + 如果`object`是咖啡,则输出桌子,咖啡在桌子上。 如果`object` 是空桌子,则输出一号桌 """ near_object = None diff --git a/robowaiter/scene/tasks/Auto_tasks.py b/robowaiter/scene/tasks/Auto_tasks.py index b926cf2..89b9e6b 100644 --- a/robowaiter/scene/tasks/Auto_tasks.py +++ b/robowaiter/scene/tasks/Auto_tasks.py @@ -16,8 +16,9 @@ class SceneAT(Scene): super().__init__(robot) def _reset(self): - self.add_walker(1085, 2630, 220) - self.control_walker([self.walker_control_generator(0, False, 100, 755, 1900, 180)]) + # self.add_walker(1085, 2630, 220) + # self.control_walker([self.walker_control_generator(0, False, 100, 755, 1900, 180)]) + pass def _run(self): diff --git a/robowaiter/scene/tasks/GQA.py b/robowaiter/scene/tasks/GQA.py index 2fc50bf..577d9b6 100644 --- a/robowaiter/scene/tasks/GQA.py +++ b/robowaiter/scene/tasks/GQA.py @@ -22,8 +22,8 @@ class SceneGQA(Scene): def _reset(self): # self.clean_walker() - self.add_walkers() - + # self.add_walkers([[50, 500,90]]) + pass # self.walker_bubble("洗手间在哪里") # self.control_walker([self.walker_control_generator(0, False, 100, 755, 1900, 180)]) diff --git a/robowaiter/scene/tasks/Open_tasks.py b/robowaiter/scene/tasks/Open_tasks.py index 1a70218..afe5818 100644 --- a/robowaiter/scene/tasks/Open_tasks.py +++ b/robowaiter/scene/tasks/Open_tasks.py @@ -19,8 +19,7 @@ class SceneOT(Scene): super().__init__(robot) # 在这里加入场景中发生的事件 self.event_list = [ - # (5,self.create_chat_event("给我一杯咖啡")) # (事件发生的时间,事件函数) - (5, self.create_chat_event("我有点热,能开个空调吗?")) # (事件发生的时间,事件函数) + (5, self.create_chat_event("我有点热,能开个空调吗?")), ] def _reset(self): @@ -30,7 +29,6 @@ class SceneOT(Scene): print("scene.walkers:",scene.walkers) cont = scene.walkers[0].name+":我有点热,能开个空调吗?" self.control_robot_action(0,3,cont) - # self.control_walker([self.walker_control_generator(0, False, 100, 755, 1900, 180)]) pass def _run(self): diff --git a/robowaiter/scene/tasks/Open_tasks_test.py b/robowaiter/scene/tasks/Open_tasks_test.py index e251d79..604d569 100644 --- a/robowaiter/scene/tasks/Open_tasks_test.py +++ b/robowaiter/scene/tasks/Open_tasks_test.py @@ -18,20 +18,11 @@ class SceneOT(Scene): super().__init__(robot) # 在这里加入场景中发生的事件 self.event_list = [ - # (5,self.create_chat_event("做一杯咖啡")), - (5,self.create_chat_event("感觉有点冷,可以关一下空调吗")), + (5, self.create_chat_event("给我一杯咖啡")) ] def _reset(self): - scene = self.add_walkers([[50, 300, 0]]) - # time.sleep(2.0) - # print("我有点热,能开个空调吗?") - print("scene.walkers:",scene.walkers) - cont = scene.walkers[0].name+":我有点热,能开个空调吗?" - self.control_robot_action(0,3,cont) - - # self.add_walker(1085, 2630, 220) - # self.control_walker([self.walker_control_generator(0, False, 100, 755, 1900, 180)]) + pass def _run(self): From c379f15a5cd65cf0327e46b304cc745ea44abf02 Mon Sep 17 00:00:00 2001 From: Caiyishuai <39987654+Caiyishuai@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:28:08 +0800 Subject: [PATCH 2/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BA=86=20=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E7=AE=97=E6=B3=95=20=E5=92=8C=20=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- robowaiter/algos/explore/scene.py | 48 +++--- robowaiter/algos/navigate/discretize_map.py | 59 ------- robowaiter/algos/navigate/navigate.py | 158 ------------------ robowaiter/algos/navigate/readme.md | 52 ------ .../algos/{navigate => navigator}/__init__.py | 0 robowaiter/algos/navigator/costMap_3.pkl | Bin 0 -> 1557411 bytes robowaiter/algos/navigator/costMap_4.pkl | Bin 0 -> 876114 bytes robowaiter/algos/navigator/costMap_5.pkl | Bin 0 -> 562562 bytes robowaiter/algos/navigator/discretize_map.py | 90 ++++++++++ .../{navigate => navigator}/dstar_lite.py | 136 +++++++-------- robowaiter/algos/navigator/mag_5.png | Bin 0 -> 25292 bytes robowaiter/algos/navigator/map_3.pkl | Bin 0 -> 1557411 bytes robowaiter/algos/navigator/map_4.pkl | Bin 0 -> 876114 bytes .../algos/{navigate => navigator}/map_5.pkl | Bin robowaiter/algos/navigator/navigate.py | 115 +++++++++++++ robowaiter/algos/navigator/readme.md | 128 ++++++++++++++ .../algos/{navigate => navigator}/test.py | 38 +++-- robowaiter/behavior_lib/_base/Behavior.py | 11 +- robowaiter/behavior_lib/act/Make.py | 38 +++-- robowaiter/behavior_lib/act/MoveTo.py | 36 ++-- robowaiter/behavior_lib/act/PickUp.py | 34 +++- robowaiter/behavior_lib/act/PutDown.py | 1 + .../obtea/OptimalBTExpansionAlgorithm.py | 12 +- .../behavior_tree/obtea/opt_bt_exp_main.py | 8 +- robowaiter/robot/robot.py | 2 +- robowaiter/scene/scene.py | 21 ++- robowaiter/scene/tasks/VLM.py | 44 ++--- robowaiter/scene/tasks/VLN.py | 8 +- 28 files changed, 593 insertions(+), 446 deletions(-) delete mode 100644 robowaiter/algos/navigate/discretize_map.py delete mode 100644 robowaiter/algos/navigate/navigate.py delete mode 100644 robowaiter/algos/navigate/readme.md rename robowaiter/algos/{navigate => navigator}/__init__.py (100%) create mode 100644 robowaiter/algos/navigator/costMap_3.pkl create mode 100644 robowaiter/algos/navigator/costMap_4.pkl create mode 100644 robowaiter/algos/navigator/costMap_5.pkl create mode 100644 robowaiter/algos/navigator/discretize_map.py rename robowaiter/algos/{navigate => navigator}/dstar_lite.py (82%) create mode 100644 robowaiter/algos/navigator/mag_5.png create mode 100644 robowaiter/algos/navigator/map_3.pkl create mode 100644 robowaiter/algos/navigator/map_4.pkl rename robowaiter/algos/{navigate => navigator}/map_5.pkl (100%) create mode 100644 robowaiter/algos/navigator/navigate.py create mode 100644 robowaiter/algos/navigator/readme.md rename robowaiter/algos/{navigate => navigator}/test.py (72%) diff --git a/robowaiter/algos/explore/scene.py b/robowaiter/algos/explore/scene.py index 1113d5b..f975604 100644 --- a/robowaiter/algos/explore/scene.py +++ b/robowaiter/algos/explore/scene.py @@ -1,4 +1,5 @@ import time +import math import grpc import numpy as np @@ -358,29 +359,29 @@ class Scene: temp = stub.GetIKControlInfos(GrabSim_pb2.HandPostureInfos(scene=self.sceneID, handPostureObjects=HandPostureObject)) # 移动到进行操作任务的指定地点 - def move_task_area(self,op_type): - if op_type==11 or op_type==12: # 开关窗帘不需要移动 - return - scene = stub.Observe(GrabSim_pb2.SceneID(value=self.sceneID)) - walk_value = [scene.location.X, scene.location.Y, scene.rotation.Yaw] - - if op_type < 8: - v_list = self.op_v_list[op_type] - if op_type>=8 and op_type<=10: # 控灯 - v_list = self.op_v_list[6] - if op_type in [13,14,15]: # 空调 - v_list = [[240, -140.0]] # KongTiao [300.5, -140.0] # 250 - - print("------------------move_task_area----------------------") - print("Current Position:", walk_value,"开始任务:",self.op_dialog[op_type]) - for walk_v in v_list: - walk_v = walk_v + [scene.rotation.Yaw, 180, 0] - walk_v[2] = 0 if (op_type in [13,14,15]) else scene.rotation.Yaw # 空调操作朝向墙面 - action = GrabSim_pb2.Action( - scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v - ) - scene = stub.Do(action) - print("After Walk Position:",[scene.location.X, scene.location.Y, scene.rotation.Yaw]) + # def move_task_area(self,op_type): + # if op_type==11 or op_type==12: # 开关窗帘不需要移动 + # return + # scene = stub.Observe(GrabSim_pb2.SceneID(value=self.sceneID)) + # walk_value = [scene.location.X, scene.location.Y, scene.rotation.Yaw] + # + # if op_type < 8: + # v_list = self.op_v_list[op_type] + # if op_type>=8 and op_type<=10: # 控灯 + # v_list = self.op_v_list[6] + # if op_type in [13,14,15]: # 空调 + # v_list = [[240, -140.0]] # KongTiao [300.5, -140.0] # 250 + # print("------------------error version----------------------") + # print("------------------move_task_area----------------------") + # print("Current Position:", walk_value,"开始任务:",self.op_dialog[op_type]) + # for walk_v in v_list: + # walk_v = walk_v + [scene.rotation.Yaw, 180, 0] + # walk_v[2] = 0 if (op_type in [13,14,15]) else scene.rotation.Yaw # 空调操作朝向墙面 + # action = GrabSim_pb2.Action( + # scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v + # ) + # scene = stub.Do(action) + # print("After Walk Position:",[scene.location.X, scene.location.Y, scene.rotation.Yaw]) # 相应的行动,由主办方封装 def control_robot_action(self, type=0, action=0, message="你好"): @@ -512,3 +513,4 @@ class Scene: print(scene.info) + diff --git a/robowaiter/algos/navigate/discretize_map.py b/robowaiter/algos/navigate/discretize_map.py deleted file mode 100644 index f16dd92..0000000 --- a/robowaiter/algos/navigate/discretize_map.py +++ /dev/null @@ -1,59 +0,0 @@ -# !/usr/bin/env python3 -# -*- encoding: utf-8 -*- - - -import matplotlib.pyplot as plt -import numpy as np -import pickle -import os - - - - - -def draw_grid_map(grid_map): - # 生成新的地图图像 - plt.imshow(grid_map, cmap='binary', alpha=0.5, origin='lower') # 黑白网格 - - # 绘制坐标轴 - plt.xlabel('y', loc='right') - plt.ylabel('x', loc='top') - - # 显示网格线 - plt.grid(color='black', linestyle='-', linewidth=0.5) - - # 显示图像 - plt.show() - #plt.pause(0.01) - - - - -if __name__ == '__main__': - # control.init_world(scene_num=1, mapID=3) - # scene = control.Scene(sceneID=0) - # - # X = int(950/5) # 采点数量 - # Y = int(1850/5) - # map = np.zeros((X, Y)) - # - # for x in range(X): - # for y in range(Y): - # if not scene.reachable_check(x*5-350, y*5-400, Yaw=0): - # map[x, y] = 1 - # print(x, y) - # - # - # file_name = 'map_5.pkl' - # if not os.path.exists(file_name): - # open(file_name, 'w').close() - # with open(file_name, 'wb') as file: - # pickle.dump(map, file) - # print('保存成功') - - - file_name = 'map_5.pkl' - if os.path.exists(file_name): - with open(file_name, 'rb') as file: - map = pickle.load(file) - draw_grid_map(map) \ No newline at end of file diff --git a/robowaiter/algos/navigate/navigate.py b/robowaiter/algos/navigate/navigate.py deleted file mode 100644 index 9059c61..0000000 --- a/robowaiter/algos/navigate/navigate.py +++ /dev/null @@ -1,158 +0,0 @@ -#!/usr/bin/env python3 -# -*- encoding: utf-8 -*- -import math -import sys -import time - -import matplotlib.pyplot as plt -import numpy as np -from mpl_toolkits.axes_grid1 import make_axes_locatable -# from scene import control - -# from rrt import RRT -# from rrt_star import RRTStar -# from apf import APF -from robowaiter.algos.navigate.dstar_lite import DStarLite, euclidean_distance - -class Navigator: - ''' - 导航类 - ''' - - def __init__(self, scene, area_range, map, scale_ratio=5, step_length=150, velocity=150, react_radius=300): - self.scene = scene - self.area_range = area_range # 地图实际坐标范围 xmin, xmax, ymin, ymax - self.map = map # 缩放并离散化的地图 array(X,Y) - self.scale_ratio = scale_ratio # 地图缩放率 - self.step_length = step_length # 步长(单次移动) - self.step_num = self.step_length // self.scale_ratio # 单次移动地图格数 - self.v = velocity # 速度 - self.react_radius = react_radius # robot反应半径 - - # self.planner = RRTStar(rand_area=area_range, map=map, scale_ratio=scale_ratio, max_iter=400, search_until_max_iter=True) - self.planner = DStarLite(area_range=area_range, map=map, scale_ratio=scale_ratio) - - @staticmethod - def is_reached(pos: (float, float), goal: (float, float), dis_limit=30): - ''' - 判断是否到达目标 - ''' - dis = math.hypot(pos[0]-goal[0], pos[1]-goal[1]) - # dis = np.linalg.norm(pos - goal) - return dis < dis_limit - - @staticmethod - def get_yaw(pos: (float, float), goal: (float, float)): - ''' - 得到移动方向 - ''' - return math.degrees(math.atan2((goal[1] - pos[1]), (goal[0] - pos[0]))) - - def legalize_goal(self, goal: (float, float)): - ''' - TODO: 处理非法目标 - 目标在障碍物上:从目标开始方形向外扩展,直到找到可行点 - 目标在地图外面:起点和目标连线最靠近目标的可行点 - ''' - return goal - - def navigate(self, goal: (float, float), animation=True): - ''' - 单次导航,直到到达目标 - ''' - if not self.scene.reachable_check(goal[0], goal[1], 0): - goal = self.legalize_goal(goal) - - pos = (self.scene.status.location.X, self.scene.status.location.Y) # 机器人当前: 位置 和 朝向 - print('------------------navigation_start----------------------') - while not self.is_reached(pos, goal): - dyna_obs = [(walker.pose.X, walker.pose.Y) for walker in self.scene.status.walkers] # 动态障碍物(顾客)位置列表 - dyna_obs = [obs for obs in dyna_obs if euclidean_distance(obs, pos) < self.react_radius] # 过滤观测范围外的dyna_obs - # 周围有dyna_obs则步长减半 - if dyna_obs: - step_num = self.step_num // 2 - else: - step_num = self.step_num - path = self.planner.planning(pos, goal, dyna_obs) - if path: - if animation: - self.planner.draw_graph(step_num) # 画出搜索路径 - next_step = min(step_num, len(path)) - next_pos = path[next_step - 1] - # print('plan pos:', next_pos, end=' ') - yaw = self.get_yaw(pos, next_pos) - # print("yaw:",yaw) - self.scene.walk_to(next_pos[0], next_pos[1], Yaw=yaw, velocity=self.v, dis_limit=10) - # pos = (self.scene.status.location.X, self.scene.status.location.Y) - # if self.is_reached(pos, next_pos): - self.planner.path = self.planner.path[next_step - 1:] # 去除已走过的路径 - pos = (self.scene.status.location.X, self.scene.status.location.Y) - # print('reach pos:', pos) - - self.planner.reset() # 完成一轮导航,重置变量 - - if self.is_reached(pos, goal): - print('The robot has achieved goal !!') - - - - - - - - - - - # def navigate(self, goal: (float, float), path_smoothing=True, animation=True): - # pos = np.array((self.scene.status.location.X, self.scene.status.location.Y)) # 机器人当前: 位置 和 朝向 - # yaw = self.scene.status.rotation.Yaw - # print('------------------navigation_start----------------------') - # - # path = self.planner.planning(pos, goal, path_smoothing, animation) - # if path: - # self.planner.draw_graph(final_path=path) # 画出探索过程 - # for (x, y) in path: - # self.scene.walk_to(x, y, yaw, velocity=self.v) - # time.sleep(self.step_time) - # pos = np.array((self.scene.status.location.X, self.scene.status.location.Y)) - # - # self.planner.reset() - # - # if self.is_reached(pos, goal): - # print('The robot has achieved goal !!') - - '''APF势场法暂不可用''' - # while not self.is_reached(pos, goal): - # # 1. 路径规划 - # path = self.planner.planning(pos, goal, path_smoothing, animation) - # self.planner.draw_graph(final_path=path) # 画出探索过程 - # - # # 2. 使用APF导航到路径中的每个waypoint - # traj = [(pos[0], pos[1])] - # #self.planner.draw_graph(final_path=traj) # 画出探索过程 - # for i, waypoint in enumerate(path[1:]): - # print('waypoint [', i, ']:', waypoint) - # # if (not self.scene.reachable_check(waypoint[0], waypoint[1], yaw)) and self.map[self.planner.real2map(waypoint[0], waypoint[1])] == 0: - # # print('error') - # while not self.is_reached(pos, waypoint): - # # 2.1 计算next_step - # pos = np.array((self.scene.status.location.X, self.scene.status.location.Y)) - # Pobs = [] # 障碍物(顾客)位置数组 - # for walker in self.scene.status.walkers: - # Pobs.append((walker.pose.X, walker.pose.Y)) - # next_step, _ = APF(Pi=pos, Pg=waypoint, Pobs=Pobs, step_length=self.step_length) - # traj.append((next_step[0], next_step[1])) - # #self.planner.draw_graph(final_path=traj) # 画出探索过程 - # while not self.scene.reachable_check(next_step[0], next_step[1], yaw): # 取中点直到next_step可达 - # traj.pop() - # next_step = (pos + next_step) / 2 - # traj.append((next_step[0], next_step[1])) - # #self.planner.draw_graph(final_path=traj) # 画出探索过程 - # # 2.2 移动robot - # self.scene.walk_to(next_step[0], next_step[1], yaw, velocity=self.v) - # # print(self.scene.status.info) # print navigation info - # # print(self.scene.status.collision) - # time.sleep(self.step_time) - # # print(self.scene.status.info) # print navigation info - # # print(self.scene.status.collision) - # self.planner.reset() diff --git a/robowaiter/algos/navigate/readme.md b/robowaiter/algos/navigate/readme.md deleted file mode 100644 index 585e602..0000000 --- a/robowaiter/algos/navigate/readme.md +++ /dev/null @@ -1,52 +0,0 @@ -## 默认使用RRTStar+路径平滑 - -### apf.py: 势场法实现 - -### discretize_map.py: 地图离散化并压缩 - -### map_5.pkl: 地图文件(5倍压缩) - -### navigate.py: 导航类 - -### pathsmoothing.py: 路径平滑 - -### rrt.py: RRT实现 - -### rrt_star.py: RRTStar 实现 - -### test.py: 测试文件 - - - - -## TODO - -### 目标不合法 -#### 初始目标不合法 -目标在障碍物上:从目标开始方形向外扩展,直到找到可行点 -目标在地图外面:起点和目标连线最靠近目标的可行点 -对不合法的目标做单独处理,生成新的目标 - -#### 在移动过程中目标被占据 -给出目标但不会行移动,程序会继续运行,重新计算规划路径,给出新目标 - - -### 规划中断情况 - - -### 计算转向角 -`完成` - - -### 有些本来可达的位置却无法走到(系统bug?) -例如从初始位置(247, 500) 移动到(115, -10),无法到达 -`(已解决)将dis_limit设为小值5而非0` -#### 构型空间膨胀?? -`不需要` - -### 只考虑一定范围内的行人 -观测范围 / 反应半径 - -`完成` -#### 观测范围内有行人步长要减小 -`完成` diff --git a/robowaiter/algos/navigate/__init__.py b/robowaiter/algos/navigator/__init__.py similarity index 100% rename from robowaiter/algos/navigate/__init__.py rename to robowaiter/algos/navigator/__init__.py diff --git a/robowaiter/algos/navigator/costMap_3.pkl b/robowaiter/algos/navigator/costMap_3.pkl new file mode 100644 index 0000000000000000000000000000000000000000..cb70debc8e7582677956151cb7eb8e080d88276d GIT binary patch literal 1557411 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 literal 0 HcmV?d00001 diff --git a/robowaiter/algos/navigator/discretize_map.py b/robowaiter/algos/navigator/discretize_map.py new file mode 100644 index 0000000..06c602b --- /dev/null +++ b/robowaiter/algos/navigator/discretize_map.py @@ -0,0 +1,90 @@ +# !/usr/bin/env python3 +# -*- encoding: utf-8 -*- + + +import matplotlib.pyplot as plt +import numpy as np +import pickle +import os + +from scipy.ndimage import binary_dilation + +from scene import scene + + +def draw_grid_map(grid_map): + # 生成新的地图图像 + plt.imshow(grid_map, cmap='binary', alpha=0.5, origin='lower') # 黑白网格 + + # 绘制坐标轴 + plt.xlabel('y', loc='right') + plt.ylabel('x', loc='top') + + # 显示网格线 + plt.grid(color='black', linestyle='-', linewidth=0.5) + + # 显示图像 + plt.show() + #plt.pause(0.01) + + +def discretize_map(scene, scale_ratio): + X = int(950 / scale_ratio) # 采点数量 + Y = int(1850 / scale_ratio) + map = np.zeros((X, Y)) + + for x in range(X): + for y in range(Y): + if not scene.reachable_check(x * scale_ratio - 350, y * scale_ratio - 400, Yaw=0): + map[x, y] = 1 + print(x, y) + + file_name = 'map_'+str(scale_ratio)+'.pkl' + if not os.path.exists(file_name): + open(file_name, 'w').close() + with open(file_name, 'wb') as file: + pickle.dump(map, file) + print('保存成功') + + +def expand_obstacles(scale_ratio, expand_range=1): + ''' + 障碍物边沿扩展 + TODO: 扩展后的地图不可用!!! + ''' + file_name = 'map_'+str(scale_ratio)+'.pkl' + dilated_file_name = 'map_'+str(scale_ratio)+'_e'+str(expand_range)+'.pkl' + + if os.path.exists(file_name): + with open(file_name, 'rb') as file: + map = pickle.load(file) + + dilated_map = binary_dilation(map, iterations=expand_range) + + if not os.path.exists(dilated_file_name): + open(dilated_file_name, 'w').close() + with open(dilated_file_name, 'wb') as file: + pickle.dump(dilated_map, file) + print('保存成功') + + +def show_map(file_name): + if os.path.exists(file_name): + with open(file_name, 'rb') as file: + map = pickle.load(file) + draw_grid_map(map) + + +if __name__ == '__main__': + # scene.init_world(scene_num=1, mapID=11) + # scene = scene.Scene(sceneID=0) + # + # # 离散化地图 + # discretize_map(scene, scale_ratio=4) + + # # 扩张构型空间 + # expand_obstacles(scale_ratio=3, expand_range=1) + + # 展示离散化地图 + file_name = 'costMap_4.pkl' + show_map(file_name) diff --git a/robowaiter/algos/navigate/dstar_lite.py b/robowaiter/algos/navigator/dstar_lite.py similarity index 82% rename from robowaiter/algos/navigate/dstar_lite.py rename to robowaiter/algos/navigator/dstar_lite.py index 9cb67c4..4d7edf4 100644 --- a/robowaiter/algos/navigate/dstar_lite.py +++ b/robowaiter/algos/navigator/dstar_lite.py @@ -5,8 +5,8 @@ ''' import math -import queue -from functools import partial +import os +import pickle import numpy as np import heapq @@ -115,13 +115,12 @@ class PriorityQueue: class DStarLite: def __init__(self, - map: np.array([int, int]), # [X, Y] - area_range, # [x_min, x_max, y_min, y_max] 实际坐标范围 - scale_ratio=5, # 地图缩放率 - dyna_obs_radius=30, # dyna_obs实际身位半径 + map: np.array([int, int]), + area_range, # [x_min, x_max, y_min, y_max] 实际坐标范围 + scale_ratio=5, # 地图缩放率 + dyna_obs_radius=36, # dyna_obs实际身位半径 ): - # self.area_bounds = area self.map = map self.background = map.copy() self.X = map.shape[0] @@ -145,9 +144,13 @@ class DStarLite: "obstacle": float('inf'), "dynamic obstacle": 100 } - self.cost_map = np.zeros_like(self.map) + + file_name = 'costMap_'+str(self.scale_ratio)+'.pkl' + if os.path.exists(file_name): + with open(file_name, 'rb') as file: + cost_map = pickle.load(file) + self.cost_map = cost_map self.cost_background = self.cost_map.copy() - self.compute_cost_map() self.s_start = None # (int,int) 必须是元组(元组可以直接当作矩阵索引) self.s_goal = None # (int,int) @@ -163,9 +166,11 @@ class DStarLite: # 设置map 和 cost_map # ''' # self.map = map_ + # self.background = map_.copy() # self.X = map_.shape[0] # self.Y = map_.shape[1] # self.compute_cost_map() + # self.cost_background = self.cost_map.copy() def reset(self): ''' @@ -299,11 +304,6 @@ class DStarLite: self.compute_shortest_path() self.path = self.get_path() return self.path - # TODO: 误差抖动使robot没有到达路径上的点,导致新起点的rhs=∞,可能导致get_path失败 ( 当前版本没有该问题 ) - # assert (self.rhs[self.s_start] != float('inf')), "There is no known path!" - # # debug - # if debug: - # pass def planning(self, s_start, s_goal, dyna_obs, debug=False): ''' @@ -311,7 +311,10 @@ class DStarLite: ''' # 实际坐标 -> 地图坐标 s_start = self.real2map(s_start) - s_goal = self.real2map(s_goal) + if self.s_goal is None: + s_goal = self.real2map(s_goal) + else: + s_goal = self.s_goal dyna_obs = [self.real2map(obs, reachable_assurance=False) for obs in dyna_obs] self._planning(s_start, s_goal, dyna_obs, debug) @@ -336,13 +339,6 @@ class DStarLite: succ = [s_ for s_ in self.get_neighbors(cur) if s_ not in path] # 避免抖动 (不可走重复的点) cur = succ[np.argmin([self.c(cur, s_) + self.g[s_] for s_ in succ])] path.append(cur) - # else: - # for i in range(step_num): - # if cur == self.s_goal: - # break - # succ = self.get_neighbors(cur) - # cur = succ[np.argmin([self.c(cur, s_) + self.g[s_] for s_ in succ])] - # path.append(cur) return path def in_bounds_without_obstacle(self, pos): @@ -357,8 +353,6 @@ class DStarLite: 获取邻居节点, 地图范围内 ''' (x_, y_) = pos - # results = [(x_+1,y_), (x_-1,y_), (x_, y_+1), (x_,y_-1)] - # if mode == 8: neighbors = [(x_ + 1, y_), (x_ - 1, y_), (x_, y_ + 1), (x_, y_ - 1), (x_ + 1, y_ + 1), (x_ - 1, y_ + 1), (x_ + 1, y_ - 1), (x_ - 1, y_ - 1)] neighbors = filter(self.in_bounds_without_obstacle, neighbors) # 确保位置在地图范围内 且 不是静态障碍物 @@ -366,17 +360,26 @@ class DStarLite: def compute_cost_map(self): # 计算当前地图的cost_map + self.cost_map = np.zeros_like(self.map) for idx, obj in self.idx_to_object.items(): self.cost_map[self.map == idx] = self.object_to_cost[obj] - # # TODO - # for x in range(self.X): - # for y in range(self.Y): - # if self.cost_map[x, y] > 0: - # neighbors = self.get_neighbors((x, y)) - # for (x_, y_) in neighbors: - # self.cost_map[x_, y_] = max(self.cost_map[x_, y_], self.cost_map[x, y] - 10) - + # 扩张静态障碍物影响范围 + obs_pos = np.where(self.map == self.object_to_idx['obstacle']) # 静态障碍物位置列表 + for (x, y) in zip(obs_pos[0], obs_pos[1]): + start_x, end_x = max(x - 1, 0), min(x + 1, self.X - 1) + start_y, end_y = max(y - 1, 0), min(y + 1, self.Y - 1) + for cost in range(9, 0, -3): + for x_ in range(start_x, end_x + 1): + self.cost_map[x_, start_y] = max(self.cost_map[x_, start_y], cost) + for y_ in range(start_y + 1, end_y + 1): + self.cost_map[end_x, y_] = max(self.cost_map[end_x, y_], cost) + for x_ in range(end_x - 1, start_x - 1, -1): + self.cost_map[x_, end_y] = max(self.cost_map[x_, end_y], cost) + for y_ in range(end_y - 1, start_y, -1): + self.cost_map[start_x, y_] = max(self.cost_map[start_x, y_], cost) + start_x, end_x = max(start_x - 1, 0), min(end_x + 1, self.X - 1) + start_y, end_y = max(start_y - 1, 0), min(end_y + 1, self.Y - 1) self.cost_background = self.cost_map.copy() @@ -388,8 +391,8 @@ class DStarLite: return: update_obj: 改变的位置列表 [(x, y, obj_idx, obj_idx_old), ...] ''' - # dyna_obs没有变化 (集合set可以忽略元素在列表中的位置) - if set(dyna_obs) == set(self.dyna_obs_list): + # dyna_obs没有变化 (集合set可以忽略元素在列表中的位置) 且 robot未在dyna_obs占用位置中 + if set(dyna_obs) == set(self.dyna_obs_list) and self.s_start not in self.dyna_obs_occupy: return [] # 当前dyna_obs占用位置列表 @@ -397,24 +400,10 @@ class DStarLite: for pos in dyna_obs: dyna_obs_occupy.extend(self.get_occupy_pos(pos)) dyna_obs_occupy = [pos for i, pos in enumerate(dyna_obs_occupy) if pos not in dyna_obs_occupy[:i]] # 去除重复位置 - # 转变为free 和 转变为obs的位置列表 + # 转变为free 和 转变为dyna_obs的位置列表 changed_free = [pos for pos in self.dyna_obs_occupy if pos not in dyna_obs_occupy] changed_obs = [pos for pos in dyna_obs_occupy if pos not in self.dyna_obs_occupy] - # # 新旧dyna_obs占用位置列表 - # old_obs_occupy = [] - # new_obs_occupy = [] - # for pos in self.dyna_obs_list: - # old_obs_occupy.extend(self.get_occupy_pos(pos)) - # for pos in dyna_obs: - # new_obs_occupy.extend(self.get_occupy_pos(pos)) - # old_obs_occupy = [pos for i, pos in enumerate(old_obs_occupy) if pos not in old_obs_occupy[:i]] # 去除重复位置 - # new_obs_occupy = [pos for i, pos in enumerate(new_obs_occupy) if pos not in new_obs_occupy[:i]] # 去除重复位置 - # - # # 转变为free 和 转变为obs的位置列表 - # changed_free = [pos for pos in old_obs_occupy if pos not in new_obs_occupy] - # changed_obs = [pos for pos in new_obs_occupy if pos not in old_obs_occupy] - # 更新地图,计算changed_pos changed_pos = [] for (x, y) in changed_free: @@ -430,21 +419,15 @@ class DStarLite: return changed_pos - - def get_occupy_pos(self, obs_pos): ''' 根据dyna_obs中心位置,计算其占用的所有网格位置 ''' (x, y) = obs_pos occupy_radius = min(self.dyna_obs_radius, int(euclidean_distance(obs_pos, self.s_start) - 1)) # 避免robot被dyna_obs的占用区域包裹住 - # for i in range(x - self.dyna_obs_radius, x + self.dyna_obs_radius + 1): # 方形区域 - # for j in range(y - self.dyna_obs_radius, y + self.dyna_obs_radius + 1): - # occupy_pos.append((i, j)) occupy_pos = [(i, j) for i in range(x - occupy_radius, x + occupy_radius + 1) # 圆形区域 for j in range(y - occupy_radius, y + occupy_radius + 1) if euclidean_distance((i, j), obs_pos) < occupy_radius] - occupy_pos = filter(self.in_bounds_without_obstacle, occupy_pos) # 确保位置在地图范围内 且 不是静态障碍物 return list(occupy_pos) @@ -497,21 +480,40 @@ class DStarLite: ''' x = round((pos[0] - self.x_min) / self.scale_ratio) y = round((pos[1] - self.y_min) / self.scale_ratio) - # 需要确保点可达 - if reachable_assurance and self.idx_to_object[self.map[x, y]] != 'free': - print('1') - x_ = math.floor((pos[0] - self.x_min) / self.scale_ratio) - y_ = math.floor((pos[1] - self.y_min) / self.scale_ratio) - candidates = [(x_, y_), (x_ + 1, y_), (x_, y_ + 1), (x_ + 1, y_ + 1)] - for (x, y) in candidates: - print(self.idx_to_object[self.map[x, y]]) - if self.idx_to_object[self.map[x, y]] == 'free': - print((x,y)) - return tuple((x, y)) - raise Exception('error') + # 确保点不在静态障碍物上,否则就不断向外圈扩展直到找到非静态障碍物位置 + if reachable_assurance: + return self.validate_pos((x, y)) else: return tuple((x, y)) + def validate_pos(self, pos): + ''' + 对于不合法的pos,找到周围距离最近的合法坐标 + ''' + (x, y) = pos + x = max(0, min(x, self.X - 1)) + y = max(0, min(y, self.Y - 1)) + if self.idx_to_object[self.map[x, y]] == 'obstacle': + start_x, end_x = max(x - 1, 0), min(x + 1, self.X - 1) + start_y, end_y = max(y - 1, 0), min(y + 1, self.Y - 1) + while True: + for x_ in range(start_x, end_x + 1): + if self.idx_to_object[self.map[x_, start_y]] != 'obstacle': + return tuple((x_, start_y)) + for y_ in range(start_y + 1, end_y + 1): + if self.idx_to_object[self.map[end_x, y_]] != 'obstacle': + return tuple((end_x, y_)) + for x_ in range(end_x - 1, start_x - 1, -1): + if self.idx_to_object[self.map[x_, end_y]] != 'obstacle': + return tuple((x_, end_y)) + for y_ in range(end_y - 1, start_y, -1): + if self.idx_to_object[self.map[start_x, y_]] != 'obstacle': + return tuple((start_x, y_)) + start_x, end_x = max(start_x - 1, 0), min(end_x + 1, self.X - 1) + start_y, end_y = max(start_y - 1, 0), min(end_y + 1, self.Y - 1) + # raise Exception('invalid pos!') + return tuple((x, y)) + def draw_graph(self, step_num): # 清空当前figure内容,保留figure对象 plt.clf() diff --git a/robowaiter/algos/navigator/mag_5.png b/robowaiter/algos/navigator/mag_5.png new file mode 100644 index 0000000000000000000000000000000000000000..2fcac52d13c08339cdc297229d5c47237d4829e6 GIT binary patch literal 25292 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 literal 0 HcmV?d00001 diff --git a/robowaiter/algos/navigator/map_4.pkl b/robowaiter/algos/navigator/map_4.pkl new file mode 100644 index 0000000000000000000000000000000000000000..54890954e6185ee05c32644808895301f6ae1c65 GIT binary patch literal 876114 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 h_sY@Vuf^Fp2z*Rn^tsuu|2N0;&>8>$ literal 0 HcmV?d00001 diff --git a/robowaiter/algos/navigate/map_5.pkl b/robowaiter/algos/navigator/map_5.pkl similarity index 100% rename from robowaiter/algos/navigate/map_5.pkl rename to robowaiter/algos/navigator/map_5.pkl diff --git a/robowaiter/algos/navigator/navigate.py b/robowaiter/algos/navigator/navigate.py new file mode 100644 index 0000000..a9d37b4 --- /dev/null +++ b/robowaiter/algos/navigator/navigate.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 +# -*- encoding: utf-8 -*- +import math +import os +import pickle + + +import matplotlib.pyplot as plt +import numpy as np +from robowaiter.scene import scene + +from robowaiter.algos.navigator.dstar_lite import DStarLite, euclidean_distance + + + +class Navigator: + ''' + 导航类 + ''' + + def __init__(self, scene, area_range, map, scale_ratio=5, step_length=150, velocity=150, react_radius=300): + self.scene = scene + self.area_range = area_range # 地图实际坐标范围 xmin, xmax, ymin, ymax + self.map = map # 缩放并离散化的地图 array(X,Y) + self.scale_ratio = scale_ratio # 地图缩放率 + self.step_length = step_length # 步长(单次移动) + self.step_num = self.step_length // self.scale_ratio # 单次移动地图格数 + self.v = velocity # 速度 + self.react_radius = react_radius # robot反应半径 + + self.planner = DStarLite(area_range=area_range, map=map, scale_ratio=scale_ratio) + + def validate_goal(self, goal): + ''' + 目标合法化 + ''' + return self.planner.map2real(self.planner.real2map(goal)) + + @staticmethod + def is_reached(pos: (float, float), goal: (float, float), dis_limit=50): + ''' + 判断是否到达目标 + ''' + dis = math.hypot(pos[0]-goal[0], pos[1]-goal[1]) + # dis = np.linalg.norm(pos - goal) + return dis < dis_limit + + @staticmethod + def get_yaw(pos: (float, float), goal: (float, float)): + ''' + 得到移动方向 + ''' + # return math.degrees(math.atan2(goal[0] - pos[0], -(goal[1] - pos[1]))) + return math.degrees(math.atan2((goal[1] - pos[1]), (goal[0] - pos[0]))) + + def navigate(self, goal: (float, float), animation=True): + ''' + 单次导航,直到到达目标 + ''' + goal = self.validate_goal(goal) # 目标合法化 + pos = (self.scene.status.location.X, self.scene.status.location.Y) # 机器人当前: 位置 和 朝向 + print('------------------navigation_start----------------------') + while not self.is_reached(pos, goal): + dyna_obs = [(walker.pose.X, walker.pose.Y) for walker in self.scene.status.walkers] # 动态障碍物(顾客)位置列表 + dyna_obs = [obs for obs in dyna_obs if euclidean_distance(obs, pos) < self.react_radius] # 过滤观测范围外的dyna_obs + # 周围有dyna_obs则步长减半 + if dyna_obs: + step_num = self.step_num // 2 + else: + step_num = self.step_num + path = self.planner.planning(pos, goal, dyna_obs) + if path: + if animation: + self.planner.draw_graph(step_num) # 画出搜索路径 + next_step = min(step_num, len(path)) + next_pos = path[next_step - 1] + print('plan pos:', next_pos, end=' ') + yaw = self.get_yaw(pos, next_pos) + self.scene.walk_to(next_pos[0], next_pos[1], yaw, velocity=self.v, dis_limit=10) + self.planner.path = self.planner.path[next_step - 1:] # 去除已走过的路径 + pos = (self.scene.status.location.X, self.scene.status.location.Y) + print('reach pos:', pos) + + self.planner.reset() # 完成一轮导航,重置变量 + + if self.is_reached(pos, goal): + print('The robot has achieved goal !!') + + + + + + +if __name__ == '__main__': + + # 根据map计算并保存cost_map + + file_name = 'map_4.pkl' + if os.path.exists(file_name): + with open(file_name, 'rb') as file: + map = pickle.load(file) + + scene.init_world(1, 11) + scene = scene.Scene(sceneID=0) + + navigator = Navigator(scene=scene, area_range=[-350, 600, -400, 1450], map=map, scale_ratio=4) + + navigator.planner.compute_cost_map() + + file_name = 'costMap_4.pkl' + if not os.path.exists(file_name): + open(file_name, 'w').close() + with open(file_name, 'wb') as file: + pickle.dump(navigator.planner.cost_map, file) + print('保存成功') \ No newline at end of file diff --git a/robowaiter/algos/navigator/readme.md b/robowaiter/algos/navigator/readme.md new file mode 100644 index 0000000..8a7a61e --- /dev/null +++ b/robowaiter/algos/navigator/readme.md @@ -0,0 +1,128 @@ +# D_star Lite 机器人任务规划 +## 目录结构 +### 坐标离散化 +`discretize_map.py` + +### 地图文件(选择缩放倍率) +`map_3.pkl` +`map_4.pkl` +`map_5.pkl` + + ### 导航类 +`navigate.py` + +### D_star Lite 算法实现 +`dstar_lite.py` + +### 测试文件 +`test.py` + +--- + +## 世界地图 + +### 实际坐标范围 + +`X: -350 ~ 600` + +`Y: -400 ~ 1450` + +### 5倍缩放后坐标范围 + +`X: -70 ~ 120` + +`Y: -80 ~ 290` + +### 网格地图 + +| Idx | Obj | +|-----|------------------| +| 0 | free | +| 1 | obstacle | +| 2 | dynamic obstacle | + +### 代价地图 +| Cost | Obj | +|---------|-----------------| +| 0 | free | +| 15-10-5 | obs周围3格 | +| inf | obstacle | +| 100 | dynamic obstacle | + + +--- + + +## 参数 +`机器人步长`: 150 + +`机器人速度`: 150 + +`机器人观测范围`: 300 + +`行人半径`: 36 + +`目标判达距离`: 50 + +`机器人移动dis_limit`: 10 + +--- + +## 使用方法 +```python +# 选择缩放合适的地图:3、4、5 +file_name = 'map_4.pkl' +if os.path.exists(file_name): + with open(file_name, 'rb') as file: + map = pickle.load(file) + +# 初始化场景 +scene.init_world(1, 11) +scene = scene.Scene(sceneID=0) + +# 舒适化导航类 +# (需要传入:场景、实际地图范围、离散化地图、缩放比例) +navigator = Navigator(scene=scene, area_range=[-350, 600, -400, 1450], map=map, scale_ratio=4) + +# 设置目标 +goal = (0, 0) + +# 导航 +# (animation: 选择是否画出导航过程) +navigator.navigate(goal, animation=False) + +``` + + +--- + +## 可靠性保证 + +`目标合法性保证`: + +- 目标在地图外:重置目标为最近的地图内位置 +- 目标在静态障碍物:从当前位置不断向外圈扩展直到找到合法位置,重置目标为该位置 + +`规划sbgoal合法性保证`: + +- 规划subgoal被动态障碍物占据:重新规划路径 + + +`起点合法性保证`: + +- 起点在静态障碍物:从当前位置不断向外圈扩展直到找到合法位置,重置起点为该位置 +- 起点在动态障碍物范围内:缩小动态障碍物半径,保证起点位置为空闲 + + +`机器人朝向保证`: + +- 机器人始终朝向每一步的移动方向 + + +`规划抖动解决`: + +- 规划路径不允许有重复点 + +`避免机器人沿障碍物行走`: + +- 障碍物扩张:在代价地图`cost_map`中,静态障碍物周围的空位也会受到影响,并产生cost diff --git a/robowaiter/algos/navigate/test.py b/robowaiter/algos/navigator/test.py similarity index 72% rename from robowaiter/algos/navigate/test.py rename to robowaiter/algos/navigator/test.py index c27eb1b..92c3883 100644 --- a/robowaiter/algos/navigate/test.py +++ b/robowaiter/algos/navigator/test.py @@ -1,22 +1,19 @@ import os import pickle -import time -import random -import math import matplotlib.pyplot as plt import numpy as np from robowaiter.scene import scene -# from navigate import Navigator -from robowaiter.algos.navigate.navigate import Navigator +from robowaiter.algos.navigator.navigate import Navigator + if __name__ == '__main__': -# def navigate_test(scene): - file_name = 'map_5.pkl' + # 选择缩放合适的地图:3、4、5 + file_name = 'map_4.pkl' if os.path.exists(file_name): with open(file_name, 'rb') as file: map = pickle.load(file) @@ -24,21 +21,25 @@ if __name__ == '__main__': scene.init_world(1, 11) scene = scene.Scene(sceneID=0) - navigator = Navigator(scene=scene, area_range=[-350, 600, -400, 1450], map=map) + navigator = Navigator(scene=scene, area_range=[-350, 600, -400, 1450], map=map, scale_ratio=4) '''场景1: 无行人环境 robot到达指定目标''' - # goal = np.array((-100, 700)) + # goal = (-100, 700) + # goal = (590, 1370) + # goal = (290, -240) + # goal = (-200, -200) + # goal = (-200, -50) '''场景2: 静止行人环境 robot到达指定目标''' # scene.clean_walker() # scene.add_walker(50, 300, 0) # scene.add_walker(-50, 500, 0) - # goal = np.array((-100, 700)) + # goal = (-100, 700) '''场景3: 移动行人环境 robot到达指定目标''' - scene.walk_to(100, 0, -90, dis_limit=10) + scene.walk_to(100, 0, -90, dis_limit=5) scene.clean_walker() - scene.add_walkers([[50, 300],[-50, 500],[0, 700]]) + scene.add_walkers([[50, 300], [-50, 500], [0, 700]]) # scene.add_walker(50, 300, 0) # scene.add_walker(-50, 500, 0) # scene.add_walker(0, 700, 0) @@ -47,12 +48,13 @@ if __name__ == '__main__': scene.control_walker([scene.walker_control_generator(walkerID=2, autowalk=False, speed=50, X=0, Y=0, Yaw=0)]) # goal = (-100, 700) - # goal = (-300) - # goal = (340.0, 900.0) + # goal = (-200, 700) # 目标在障碍物测试 + # goal = (-400, 700) # 目标在地图外测试 + goal = (10000, 10000) # 目标在地图外测试 + # goal = (-220, 300) + # goal = (-280, 400) + # goal = (-230, 600) - # goal = (240.0, 1000.0) - # goal = (340.0, 900.0) - goal = (240.0, 1160.0) '''场景4: 行人自由移动 robot到达指定目标''' # # TODO: autowalk=True仿真器会闪退 ??? @@ -62,7 +64,7 @@ if __name__ == '__main__': # scene.control_walker([scene.walker_control_generator(walkerID=0, autowalk=True, speed=20, X=0, Y=0, Yaw=0)]) # scene.control_walker([scene.walker_control_generator(walkerID=1, autowalk=True, speed=20, X=0, Y=0, Yaw=0)]) # time.sleep(5) - # goal = np.array((-100, 700)) + # goal = (-100, 700) navigator.navigate(goal, animation=True) diff --git a/robowaiter/behavior_lib/_base/Behavior.py b/robowaiter/behavior_lib/_base/Behavior.py index ccf6c26..1bd0182 100644 --- a/robowaiter/behavior_lib/_base/Behavior.py +++ b/robowaiter/behavior_lib/_base/Behavior.py @@ -13,12 +13,12 @@ class Bahavior(ptree.behaviour.Behaviour): ''' scene = None print_name_prefix = "" - all_place = {'Bar', 'Bar2', 'WaterTable', 'CoffeeTable', 'Table1', 'Table2', 'Table3'} + # all_place = {'Bar', 'Bar2', 'WaterTable', 'CoffeeTable', 'Table1', 'Table2', 'Table3'} # all_object = {'Coffee', 'Water', 'Dessert', 'Softdrink', 'BottledDrink', 'Yogurt', 'ADMilk', 'MilkDrink', 'Milk', # 'VacuumCup'} - # all_place = {'Bar', 'WaterTable', 'CoffeeTable'} + all_place = {'Bar', 'WaterTable', 'CoffeeTable'} # all_object = {'Coffee', 'Water', 'Dessert', 'Softdrink', 'Yogurt'} - all_object = {'Coffee'} + all_object = {'Coffee', 'Water'} place_xyz_dic={ 'Bar': (247.0, 520.0, 100.0), 'Bar2': (240.0, 40.0, 70.0), @@ -28,6 +28,11 @@ class Bahavior(ptree.behaviour.Behaviour): 'Table2': (-55.0, 0.0, 107), 'Table3':(-55.0, 150.0, 107) } + container_dic={ + 'Coffee':'CoffeeCup', + 'Water': 'Glass', + 'Dessert':'Plate' + } @classmethod def get_ins_name(cls,*args): diff --git a/robowaiter/behavior_lib/act/Make.py b/robowaiter/behavior_lib/act/Make.py index bc58583..a730820 100644 --- a/robowaiter/behavior_lib/act/Make.py +++ b/robowaiter/behavior_lib/act/Make.py @@ -14,11 +14,11 @@ class Make(Act): super().__init__(*args) self.target_obj = self.args[0] self.op_type = 1 - if self.target_obj=="Coffee": + if self.target_obj==self.valid_args[0]: self.op_type = 1 - elif self.target_obj=="Water": + elif self.target_obj==self.valid_args[1]: self.op_type = 2 - elif self.target_obj=="Dessert": + elif self.target_obj==self.valid_args[2]: self.op_type = 3 @@ -28,12 +28,12 @@ class Make(Act): info["pre"]= {f'Holding(Nothing)'} info['del_set'] = set() info['add'] = {f'Exist({arg})'} - if arg == "Coffee": - info["add"] |= {f'On(Coffee,CoffeeTable)'} - elif arg == "Water": - info["add"] |= {f'On(Water,WaterTable)'} - elif arg == "Dessert": - info["add"] |= {f'On(Dessert,Bar)'} + if arg == cls.valid_args[0]: + info["add"] |= {f'On({arg},CoffeeTable)'} + elif arg == cls.valid_args[1]: + info["add"] |= {f'On({arg},WaterTable)'} + elif arg == cls.valid_args[2]: + info["add"] |= {f'On({arg},Bar)'} return info def _update(self) -> ptree.common.Status: @@ -43,16 +43,20 @@ class Make(Act): # self.scene.gen_obj(type=40) - obj_dict = self.scene.status.objects - if len(obj_dict) != 0: - # 获取obj_id - for id, obj in enumerate(obj_dict): - if obj.name == "Coffee": - obj_info = obj_dict[id] - obj_x, obj_y, obj_z = obj_info.location.X, obj_info.location.Y, obj_info.location.Z - print(id,obj.name,obj_x,obj_y,obj_z) + # obj_dict = self.scene.status.objects + # if len(obj_dict) != 0: + # # 获取obj_id + # for id, obj in enumerate(obj_dict): + # print("id:",id,"obj",obj.name) + + # if obj.name == "Coffee": + # obj_info = obj_dict[id] + # obj_x, obj_y, obj_z = obj_info.location.X, obj_info.location.Y, obj_info.location.Z + # print(id,obj.name,obj_x,obj_y,obj_z) self.scene.state["condition_set"] |= (self.info["add"]) self.scene.state["condition_set"] -= self.info["del_set"] + print("condition_set:",self.scene.state["condition_set"]) + return Status.RUNNING \ No newline at end of file diff --git a/robowaiter/behavior_lib/act/MoveTo.py b/robowaiter/behavior_lib/act/MoveTo.py index db1cb7f..fec8526 100644 --- a/robowaiter/behavior_lib/act/MoveTo.py +++ b/robowaiter/behavior_lib/act/MoveTo.py @@ -1,6 +1,6 @@ import py_trees as ptree from robowaiter.behavior_lib._base.Act import Act -from robowaiter.algos.navigate.navigate import Navigator +from robowaiter.algos.navigate_old.navigate import Navigator class MoveTo(Act): can_be_expanded = True @@ -21,6 +21,7 @@ class MoveTo(Act): info['pre'] |= {f'Exist({arg})'} info["add"] = {f'At(Robot,{arg})'} info["del_set"] = {f'At(Robot,{place})' for place in cls.valid_args if place != arg} + info['cost']=5 return info @@ -29,30 +30,41 @@ class MoveTo(Act): # navigator = Navigator(scene=self.scene, area_range=[-350, 600, -400, 1450], map=self.scene.state["map"]["2d"]) # goal = self.scene.state['map']['obj_pos'][self.args[0]] - # navigator.navigate(goal, animation=False) + # navigator.navigate_old(goal, animation=False) # 走到固定的地点 if self.target_place in Act.place_xyz_dic: goal = Act.place_xyz_dic[self.target_place] - self.scene.walk_to(goal[0],goal[1]) - else: # 走到物品边上 + self.scene.walk_to(goal[0]+1,goal[1]) + # 走到物品边上 + else: + # 是否用容器装好 + if self.target_place in Act.container_dic: + target_name = Act.container_dic[self.target_place] + else: + target_name = self.target_place + # 根据物体名字找到最近的这类物体对应的位置 obj_id = -1 min_dis = float('inf') obj_dict = self.scene.status.objects if len(obj_dict)!=0: # 获取obj_id for id,obj in enumerate(obj_dict): - if obj.name == self.target_place: - obj_id = id - # obj_info = obj_dict[id] - # obj_x, obj_y, obj_z = obj_info.location.X, obj_info.location.Y, obj_info.location.Z - # ginger_x,ginger_y,ginger_z = [int(self.scene.location.X), int(self.scene.location.Y), int(self.scene.rotation.Yaw)] - break - if self.target_place == "CoffeeCup": - obj_id = 273 + if obj.name == target_name: + obj_info = obj_dict[id] + dis = self.scene.cal_distance_to_robot(obj_info.location.X, obj_info.location.Y, obj_info.location.Z) + if id==275: + print("275'dis:",dis) + if dis ptree.common.Status: # self.scene.test_move() - op_type=16 - obj_id = 0 + # op_type=16 + # 遍历场景里的所有物品,根据名字匹配位置最近的 obj-id + # 是否用容器装好 + if self.target_obj in Act.container_dic: + target_name = Act.container_dic[self.target_obj] + else: + target_name = self.target_obj + # 根据物体名字找到最近的这类物体对应的位置 + obj_id = -1 + min_dis = float('inf') + obj_dict = self.scene.status.objects + if len(obj_dict) != 0: + # 获取obj_id + for id, obj in enumerate(obj_dict): + if obj.name == target_name: + obj_info = obj_dict[id] + dis = self.scene.cal_distance_to_robot(obj_info.location.X, obj_info.location.Y, + obj_info.location.Z) + if dis < min_dis: + min_dis = dis + obj_id = id + # if self.target_place == "CoffeeCup": + # # obj_id = 273 + # obj_id = 275 + if obj_id == -1: + return ptree.common.Status.FAILURE - if self.args=="Coffee": - obj_id = 273 - - self.scene.op_task_execute(op_type, obj_id=obj_id) + self.scene.move_task_area(op_type=16, obj_id=obj_id) + self.scene.op_task_execute(op_type=16, obj_id=obj_id) self.scene.state["condition_set"] |= (self.info["add"]) self.scene.state["condition_set"] -= self.info["del_set"] diff --git a/robowaiter/behavior_lib/act/PutDown.py b/robowaiter/behavior_lib/act/PutDown.py index a7da726..6457868 100644 --- a/robowaiter/behavior_lib/act/PutDown.py +++ b/robowaiter/behavior_lib/act/PutDown.py @@ -31,6 +31,7 @@ class PutDown(Act): release_pos = list(Act.place_xyz_dic[self.target_place]) # # 原始吧台处:[247.0, 520.0, 100.0], 空调开关旁吧台:[240.0, 40.0, 70.0], 水杯桌:[-70.0, 500.0, 107] # # 桌子2:[-55.0, 0.0, 107],桌子3:[-55.0, 150.0, 107] + self.scene.move_task_area(op_type, release_pos=release_pos) self.scene.op_task_execute(op_type, release_pos=release_pos) self.scene.state["condition_set"] |= (self.info["add"]) diff --git a/robowaiter/behavior_tree/obtea/OptimalBTExpansionAlgorithm.py b/robowaiter/behavior_tree/obtea/OptimalBTExpansionAlgorithm.py index c0212df..6a69783 100644 --- a/robowaiter/behavior_tree/obtea/OptimalBTExpansionAlgorithm.py +++ b/robowaiter/behavior_tree/obtea/OptimalBTExpansionAlgorithm.py @@ -2,6 +2,8 @@ import copy import random from robowaiter.behavior_tree.obtea.BehaviorTree import Leaf,ControlBT + + class CondActPair: def __init__(self, cond_leaf,act_leaf): self.cond_leaf = cond_leaf @@ -54,7 +56,10 @@ class OptBTExpAlgorithm: self.conditions_index = [] #运行规划算法,从初始状态、目标状态和可用行动,计算行为树self.bt - def run_algorithm(self,goal,actions): + def run_algorithm(self,goal,actions,scene): + + self.scene = scene + if self.verbose: print("\n算法开始!") @@ -99,8 +104,13 @@ class OptBTExpAlgorithm: sequence_structure.add_child( [copy.deepcopy(pair_node.cond_leaf), copy.deepcopy(pair_node.act_leaf)]) subtree.add_child([copy.deepcopy(sequence_structure)]) # subtree 是回不断变化的,它的父亲是self.bt + # 增加实时条件判断,满足条件就不再扩展 + if c <= self.scene.state["condition_set"]: + return True else: subtree.add_child([copy.deepcopy(pair_node.act_leaf)]) + + if self.verbose: print("完成扩展 a_node= %s,对应的新条件 c_attr= %s,mincost=%d" \ % (cond_anc_pair.act_leaf.content.name, cond_anc_pair.cond_leaf.content, diff --git a/robowaiter/behavior_tree/obtea/opt_bt_exp_main.py b/robowaiter/behavior_tree/obtea/opt_bt_exp_main.py index ca40d53..aa07b5d 100644 --- a/robowaiter/behavior_tree/obtea/opt_bt_exp_main.py +++ b/robowaiter/behavior_tree/obtea/opt_bt_exp_main.py @@ -6,7 +6,7 @@ from robowaiter.behavior_tree.obtea.examples import * # 封装好的主接口 class BTOptExpInterface: - def __init__(self, action_list): + def __init__(self, action_list,scene): """ Initialize the BTOptExpansion with a list of actions. :param action_list: A list of actions to be used in the behavior tree. @@ -22,6 +22,8 @@ class BTOptExpInterface: self.actions = action_list self.has_processed = False + self.scene = scene + def process(self, goal): """ Process the input sets and return a string result. @@ -29,9 +31,9 @@ class BTOptExpInterface: :return: A PTML string representing the outcome of the behavior tree. """ self.goal = goal - self.algo = OptBTExpAlgorithm(verbose=False) + self.algo = OptBTExpAlgorithm(verbose=True) self.algo.clear() - self.algo.run_algorithm(self.goal, self.actions) # 调用算法得到行为树保存至 algo.bt + self.algo.run_algorithm(self.goal, self.actions,self.scene) # 调用算法得到行为树保存至 algo.bt self.ptml_string = self.algo.get_ptml() self.has_processed = True # algo.print_solution() # print behavior tree diff --git a/robowaiter/robot/robot.py b/robowaiter/robot/robot.py index 6a1ae38..bcbce01 100644 --- a/robowaiter/robot/robot.py +++ b/robowaiter/robot/robot.py @@ -62,7 +62,7 @@ class Robot(object): print("--------------------\n") - algo = BTOptExpInterface(self.action_list) + algo = BTOptExpInterface(self.action_list,self.scene) ptml_string = algo.process(goal) diff --git a/robowaiter/scene/scene.py b/robowaiter/scene/scene.py index 13e20ef..2873bd2 100644 --- a/robowaiter/scene/scene.py +++ b/robowaiter/scene/scene.py @@ -397,8 +397,12 @@ class Scene: walk_v = [obj_x + 40, obj_y - 35, 130, 180, 0] obj_x += 3 obj_y += 2.5 + walk_v[0]+=1 + print("walk:",walk_v) action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) scene = stub.Do(action) + print("After Walk Position:", [scene.location.X, scene.location.Y, scene.rotation.Yaw]) + # 移动到进行操作任务的指定地点 def move_task_area(self,op_type,obj_id=0, release_pos=[247.0, 520.0, 100.0]): @@ -428,7 +432,7 @@ class Scene: walk_v = release_pos[:-1] + [180, 180, 0] if release_pos == [340.0, 900.0, 99.0]: walk_v[2] = 130 - + print("walk_v:",walk_v) action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) scene = stub.Do(action) print("After Walk Position:", [scene.location.X, scene.location.Y, scene.rotation.Yaw]) @@ -527,7 +531,7 @@ class Scene: return True # 执行过程:输出"开始(任务名)" -> 按步骤数执行任务 -> Robot输出成功或失败的对话 - def op_task_execute(self,op_type,obj_id=0,release_pos=[240,-140]): + def op_task_execute(self,op_type,obj_id=0,release_pos=[247.0, 520.0, 100.0]): self.control_robot_action(0, 1, "开始"+self.op_dialog[op_type]) # 开始制作咖啡 if op_type<8: result = self.control_robot_action(op_type, 1) if op_type>=8 and op_type<=12: result = self.control_robot_action(self.op_typeToAct[op_type][0], self.op_typeToAct[op_type][1]) @@ -633,3 +637,16 @@ class Scene: return False + def cal_distance_to_robot(self,objx,objy,objz): + scene = self.status + ginger_x, ginger_y, ginger_z = [int(scene.location.X), int(scene.location.Y),100] + return math.sqrt((ginger_x - objx) ** 2 + (ginger_y - objy) ** 2 + (ginger_z - objz) ** 2) + + def test(self): + walk_v = [247.0, 480.0, 180.0, 180, 0] + action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) + scene = stub.Do(action) + time.sleep(4) + walk_v = [247.0, 500.0, 0.0, 180, 0] + action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) + scene = stub.Do(action) \ No newline at end of file diff --git a/robowaiter/scene/tasks/VLM.py b/robowaiter/scene/tasks/VLM.py index aba572c..be4c993 100644 --- a/robowaiter/scene/tasks/VLM.py +++ b/robowaiter/scene/tasks/VLM.py @@ -35,6 +35,7 @@ class SceneVLM(Scene): def _run(self, op_type=10): + # 共17个操作 # "制作咖啡","倒水","夹点心","拖地","擦桌子","开筒灯","搬椅子", # 1-7 # "关筒灯","开大厅灯","关大厅灯","关闭窗帘","打开窗帘", # 8-12 @@ -58,27 +59,28 @@ class SceneVLM(Scene): # 流程测试 # 抓握放置:抓吧台前生成的酸奶,放到抹布桌上 self.gen_obj() - self.move_task_area(16, obj_id=0) - self.op_task_execute(16, obj_id=0) - pos = [340.0, 900.0, 99.0] - self.move_task_area(17, release_pos=pos) - self.op_task_execute(17, release_pos=pos) + # self.move_task_area(16, obj_id=0) + # self.op_task_execute(16, obj_id=0) + # pos = [340.0, 900.0, 99.0] + # self.move_task_area(17, release_pos=pos) + # self.op_task_execute(17, release_pos=pos) + # + # # 做咖啡:做完的咖啡放到水杯桌上 + # self.move_task_area(1) + # self.op_task_execute(1) + # + # self.find_obj("CoffeeCup") + # + # self.move_task_area(16, obj_id=275) + # self.op_task_execute(16, obj_id=275) + # pos = [-70.0, 500.0, 107] + # self.move_task_area(17, release_pos=pos) + # self.op_task_execute(17, release_pos=pos) + # + # # 倒水:倒完的水放到旁边桌子上 + # self.move_task_area(2) + # self.op_task_execute(2) - # 做咖啡:做完的咖啡放到水杯桌上 - self.move_task_area(1) - self.op_task_execute(1) - - self.find_obj("CoffeeCup") - - self.move_task_area(16, obj_id=275) - self.op_task_execute(16, obj_id=275) - pos = [-70.0, 500.0, 107] - self.move_task_area(17, release_pos=pos) - self.op_task_execute(17, release_pos=pos) - - # 倒水:倒完的水放到旁边桌子上 - self.move_task_area(2) - self.op_task_execute(2) # # self.move_task_area(16, obj_id=190) # self.op_task_execute(16, obj_id=190) @@ -86,6 +88,8 @@ class SceneVLM(Scene): # self.move_task_area(17, release_pos=pos) # self.op_task_execute(17, release_pos=pos) + # self.test() + pass def _step(self): diff --git a/robowaiter/scene/tasks/VLN.py b/robowaiter/scene/tasks/VLN.py index 448fecf..ce59d79 100644 --- a/robowaiter/scene/tasks/VLN.py +++ b/robowaiter/scene/tasks/VLN.py @@ -16,8 +16,8 @@ from robowaiter.scene.scene import Scene,init_world # TODO: 文件名改成Scen from robowaiter.scene.scene import Scene from robowaiter.utils import get_root_path -from robowaiter.algos.navigate.navigate import Navigator -from robowaiter.algos.navigate import test +from robowaiter.algos.navigator.navigate import Navigator +from robowaiter.algos.navigator import test class SceneVLN(Scene): def __init__(self, robot): @@ -29,7 +29,7 @@ class SceneVLN(Scene): def _reset(self): root_path = get_root_path() - file_name = os.path.join(root_path,'robowaiter/algos/navigate/map_5.pkl') + file_name = os.path.join(root_path,'robowaiter/algos/navigator/map_5.pkl') with open(file_name, 'rb') as file: map = pickle.load(file) @@ -37,7 +37,7 @@ class SceneVLN(Scene): self.state['map']['obj_pos']['Table'] = np.array((-100, 700)) def _run(self): - file_name = '../../algos/navigate/map_5.pkl' + file_name = '../../algos/navigator/map_5.pkl' if os.path.exists(file_name): with open(file_name, 'rb') as file: map = pickle.load(file) From d1e2049ecc8a1b9c63b81ad067ee4387715f58ef Mon Sep 17 00:00:00 2001 From: Caiyishuai <39987654+Caiyishuai@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:29:56 +0800 Subject: [PATCH 3/4] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E5=8A=A8=E4=BD=9C=E8=8A=82=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- robowaiter/behavior_lib/act/MoveTo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/robowaiter/behavior_lib/act/MoveTo.py b/robowaiter/behavior_lib/act/MoveTo.py index fec8526..43cef8b 100644 --- a/robowaiter/behavior_lib/act/MoveTo.py +++ b/robowaiter/behavior_lib/act/MoveTo.py @@ -1,6 +1,6 @@ import py_trees as ptree from robowaiter.behavior_lib._base.Act import Act -from robowaiter.algos.navigate_old.navigate import Navigator +from robowaiter.algos.navigator.navigate import Navigator class MoveTo(Act): can_be_expanded = True From 311a0cc2b97cb98aad63581c0f34144d4f914e33 Mon Sep 17 00:00:00 2001 From: Caiyishuai <39987654+Caiyishuai@users.noreply.github.com> Date: Thu, 16 Nov 2023 15:31:38 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E5=88=A0=E9=99=A4=E8=BD=AC=E5=90=91?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=9A=84=E9=97=AE=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- robowaiter/scene/scene.py | 16 ++++++++-------- robowaiter/scene/tasks/VLM.py | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/robowaiter/scene/scene.py b/robowaiter/scene/scene.py index 2873bd2..0333473 100644 --- a/robowaiter/scene/scene.py +++ b/robowaiter/scene/scene.py @@ -642,11 +642,11 @@ class Scene: ginger_x, ginger_y, ginger_z = [int(scene.location.X), int(scene.location.Y),100] return math.sqrt((ginger_x - objx) ** 2 + (ginger_y - objy) ** 2 + (ginger_z - objz) ** 2) - def test(self): - walk_v = [247.0, 480.0, 180.0, 180, 0] - action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) - scene = stub.Do(action) - time.sleep(4) - walk_v = [247.0, 500.0, 0.0, 180, 0] - action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) - scene = stub.Do(action) \ No newline at end of file + # def test_yaw(self): + # walk_v = [247.0, 480.0, 180.0, 180, 0] + # action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) + # scene = stub.Do(action) + # time.sleep(4) + # walk_v = [247.0, 500.0, 0.0, 180, 0] + # action = GrabSim_pb2.Action(scene=self.sceneID, action=GrabSim_pb2.Action.ActionType.WalkTo, values=walk_v) + # scene = stub.Do(action) \ No newline at end of file diff --git a/robowaiter/scene/tasks/VLM.py b/robowaiter/scene/tasks/VLM.py index be4c993..23cd3af 100644 --- a/robowaiter/scene/tasks/VLM.py +++ b/robowaiter/scene/tasks/VLM.py @@ -88,7 +88,7 @@ class SceneVLM(Scene): # self.move_task_area(17, release_pos=pos) # self.op_task_execute(17, release_pos=pos) - # self.test() + # self.test_yaw() pass