Tbot 3D 내비게이션 샤시 SDK 개발 문서
rosbridge WebSocket 기반으로 ROS 환경 없이도 크로스 언어로 샤시를 제어할 수 있습니다. 본 문서는 모든 Topic / Service의 요청 필드와 응답 필드(타입 + 설명)를 빠짐없이 정리합니다.
연결:
공통 응답 패킷:모든 Service 호출 후 rosbridge는 service_response 한 건을 회신합니다. 아래 표 "응답"의 JSON이 곧 values 내용입니다.
ws://192.168.1.151:9090공통 응답 패킷:모든 Service 호출 후 rosbridge는 service_response 한 건을 회신합니다. 아래 표 "응답"의 JSON이 곧 values 내용입니다.
개발자 리소스
1. 프로토콜 개요
| 방향 | 수량 | 이름 |
|---|---|---|
| 구독 Topic (샤시 → 앱) | 5 | /fast_lio_localization、/aft_pgo_path、/map_list_kitty、/ebot_goal_status_local_planner、/ebot_work_state |
| 호출 Service (앱 → 샤시) | 9 | /start_mapping_kitty、/save_map_kitty、/select_map_and_start_kitty、/delete_map_kitty、/docking_kitty、/navigate_to_pose(내비게이션 + 취소)、/add_goal_kitty、/delete_goal_kitty |
2. Topic 구독 (샤시 → 앱)
구독. 수신 메시지는 msg 필드에 있습니다.
3. Service 요약표
| Service | 서비스 유형 | 주요 필드 |
|---|---|---|
| /start_mapping_kitty | std_srvs/Trigger | 빈 요청 → success,message |
| /save_map_kitty | kitty_runtime_manager/SaveMap | filepath → success,message |
| /select_map_and_start_kitty | kitty_runtime_manager/SaveMap | filepath → success,message |
| /delete_map_kitty | kitty_runtime_manager/DeleteMap | map_name → success,message |
| /docking_kitty | std_srvs/Trigger | 빈 요청 → success,message |
| /navigate_to_pose | nav_msgs/GetPlan | goal.pose.position.x/y/z → plan.poses[] |
| /add_goal_kitty | kitty_runtime_manager/AddGoal | map_name,goal_name,x,y,theta → success,message |
| /delete_goal_kitty | kitty_runtime_manager/DeleteGoal | map_name,goal_name → success,message |
2.1 로봇 위치 nav_msgs/Odometry
샤시가 현재 자세를 주기적으로 전송합니다.
수신 msg:
{
"pose": { "pose": {
"position": {"x": 1.23, "y": 4.56, "z": 0.0},
"orientation": {"x": 0, "y": 0, "z": 0.5, "w": 0.866}
}}
}| 필드 | 유형 | 설명 |
|---|---|---|
| pose.pose.position.x | float64 | 로봇 x 좌표 (미터) |
| pose.pose.position.y | float64 | 로봇 y 좌표 (미터) |
| pose.pose.position.z | float64 | 로봇 z 좌표 (미터, 샤시 고정 0) |
| pose.pose.orientation.x/y/z/w | float64 | 쿼터니언 방향; 프론트엔드 yaw = atan2(2*(w*z+x*y), 1-2*(z*z+y*y)) 라디안 각도 변환 |
2.2 전역 경로 nav_msgs/Path
내비게이션 시작 후 약 5초에 1회, 총 5회 발송 (약 30초).
수신 msg:
{ "poses": [
{"pose":{"position":{"x":0,"y":0,"z":0}}},
{"pose":{"position":{"x":1,"y":2,"z":0}}}
]}| 필드 | 유형 | 설명 |
|---|---|---|
| poses | array[PoseStamped] | 경로 점 배열 |
| poses[].pose.position.x | float64 | 경로 점 x 좌표 (미터) |
| poses[].pose.position.y | float64 | 경로 점 y 좌표 (미터) |
| poses[].pose.position.z | float64 | 경로 점 z 좌표 (샤시 항상 0) |
2.3 맵 목록 kitty_runtime_manager/MapList
구독 시 모든 맵과 저장된 목표점을 반환합니다.
수신 msg:
{ "maps": [
{"map_name":"Lobby","map_path":"/maps/floor1",
"goals":[
{"name":"Reception","x":1.0,"y":2.0,"theta":0.5},
{"name":"Elevator","x":3.0,"y":4.0,"theta":1.2}
]}
]}| 필드 | 유형 | 설명 |
|---|---|---|
| maps | array[Map] | 맵 배열 |
| maps[].map_name | string | 맵 표시 이름 |
| maps[].map_path | string | 맵 파일 경로 (내부용) |
| maps[].goals | array[Goal] | 해당 맵의 저장 목표점 |
| maps[].goals[].name | string | 목표점 이름 |
| maps[].goals[].x | float64 | 목표점 x (미터) |
| maps[].goals[].y | float64 | 목표점 y (미터) |
| maps[].goals[].theta | float64 | 목표점 방향 (라디안) |
2.4 내비게이션 상태 std_msgs/Float64MultiArray
Topic: /ebot_goal_status_local_planner.
수신 msg:
{"data": [1]}| 필드 | 유형 | 설명 |
|---|---|---|
| data[0] | float64 | 1 = 목표 지점 도착; 0 = 미도착. 도킹 시나리오에서는 실제 충전이 완료되어야만 1을 발행합니다 |
2.5 배터리·충전 상태 std_msgs/Float64MultiArray
Topic: /ebot_work_state.
수신 msg:
{"data": [2, 75.5]}| 필드 | 유형 | 설명 |
|---|---|---|
| data[0] | float64 | 충전 상태: 1=충전 2=미충전 |
| data[1] | float64 | 배터리 % (0~100) |
3.1 맵 구축 시작 std_srvs/Trigger
Service:/start_mapping_kitty
요청:
{}(빈 요청)응답 values:
{ "success": true, "message": "Mapping started" }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| success | bool | 응답 | 작업 성공 여부 |
| message | string | 응답 | 결과 정보 (실패 시 사유) |
3.2 맵 저장 kitty_runtime_manager/SaveMap
Service:/save_map_kitty
요청:
{ "filepath": "map_name" }응답 values:
{ "success": true, "message": "Map saved" }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| filepath | string | 요청 | map_name (경로 포함 금지) |
| success | bool | 응답 | 작업 성공 여부 |
| message | string | 응답 | 결과 정보 |
3.3 맵 선택 후 내비게이션 시작 kitty_runtime_manager/SaveMap
Service:/select_map_and_start_kitty
요청:
{ "filepath": "map_name" }응답 values:
{ "success": true, "message": "Map selected and navigation started" }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| filepath | string | 요청 | map_name (경로 포함 금지) |
| success | bool | 응답 | 작업 성공 여부 |
| message | string | 응답 | 결과 정보 |
3.4 맵 삭제 kitty_runtime_manager/DeleteMap
Service:/delete_map_kitty
요청:
{ "map_name": "map_name" }응답 values:
{ "success": true, "message": "Deleted successfully" }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| map_name | string | 요청 | 삭제할 map_name |
| success | bool | 응답 | 작업 성공 여부 |
| message | string | 응답 | 결과 정보 |
3.5 충전 스테이션 복귀 std_srvs/Trigger
Service:/docking_kitty
요청:
{}응답 values:
{ "success": true, "message": "Docking started" }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| success | bool | 응답 | 작업 성공 여부 (Docking 프로세스 시작만 의미, 충전 완료는 아님) |
| message | string | 응답 | 결과 정보 |
3.6 목표점으로 내비게이션 nav_msgs/GetPlan
Service:/navigate_to_pose
요청:
{
"goal": { "pose": { "position": { "x": 1.0, "y": 2.0, "z": 0.0 } } }
}응답 values:
{ "plan": { "poses": [
{"pose":{"position":{"x":0,"y":0,"z":0}}},
{"pose":{"position":{"x":1,"y":2,"z":0}}}
]}}| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| goal.pose.position.x | float64 | 요청 | 목표점 x (미터) |
| goal.pose.position.y | float64 | 요청 | 목표점 y (미터) |
| goal.pose.position.z | float64 | 요청 | 목표점 z (샤시 항상 0 전송) |
| plan.poses | array[PoseStamped] | 응답 | 계획 경로 점 배열 (프론트엔드 경로 표시 가능) |
주의:실제 도착 여부는 본 응답이 아니라 ebot_goal_status 구독 시 data[0]==1 수신으로 확인합니다.
3.7 내비게이션 취소 nav_msgs/GetPlan
Service:/navigate_to_pose((내비게이션과 같은 서비스, 특수 좌표로 구분))
요청:
{
"goal": { "pose": { "position": { "x": 999999, "y": 888888, "z": 0 } } }
}응답 values:
{ "plan": { "poses": [] } }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| goal.pose.position.x | float64 | 요청 | 고정 999999(취소 플래그) |
| goal.pose.position.y | float64 | 요청 | 고정 888888(취소 플래그) |
| goal.pose.position.z | float64 | 요청 | 고정 0 |
| plan.poses | array | 응답 | 빈 또는 잘린 경로 |
3.8 목표점 추가 kitty_runtime_manager/AddGoal
Service:/add_goal_kitty
요청:
{
"map_name": "Lobby",
"goal_name": "Reception",
"x": 1.0, "y": 2.0, "theta": 0.5
}응답 values:
{ "success": true, "message": "Added successfully" }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| map_name | string | 요청 | map_name |
| goal_name | string | 요청 | 목표점 이름 |
| x | float64 | 요청 | 목표점 x (미터) |
| y | float64 | 요청 | 목표점 y (미터) |
| theta | float64 | 요청 | 방향 각 (라디안) |
| success | bool | 응답 | 작업 성공 여부 |
| message | string | 응답 | 결과 정보 |
3.9 목표점 삭제 kitty_runtime_manager/DeleteGoal
Service:/delete_goal_kitty
요청:
{ "map_name": "Lobby", "goal_name": "Reception" }응답 values:
{ "success": true, "message": "Deleted successfully" }| 필드 | 유형 | 방향 | 설명 |
|---|---|---|---|
| map_name | string | 요청 | map_name |
| goal_name | string | 요청 | 목표점 이름 |
| success | bool | 응답 | 작업 성공 여부 |
| message | string | 응답 | 결과 정보 |
4. 다국어 개발 예제
각 언어별로 빠른 예제(연결 + 배터리 구독 + 도킹 복귀)를 기본 표시하며, "전체 코드 보기"를 누르면 5개 Topic + 9개 Service의 전체 데모를 펼칠 수 있습니다.
4.1 Python(roslibpy)
의존:pip install roslibpy
import roslibpy, time
client = roslibpy.Ros(host='192.168.1.151', port=9090)
client.run()
# Subscribe to battery state
roslibpy.Topic(client,'/ebot_work_state','std_msgs/Float64MultiArray').subscribe(
lambda m: print(f"Battery {m['data'][1]}% charging={m['data'][0]==1}"))
# Dock to charger
roslibpy.Service(client,'/docking_kitty','std_srvs/Trigger').call(roslibpy.ServiceRequest(),
callback=lambda r: print("Docking:", r['message']))
try:
while True: time.sleep(1)
except KeyboardInterrupt: client.terminate()
import roslibpy, time, math
client = roslibpy.Ros(host='192.168.1.151', port=9090)
client.run()
# ===== Subscribe to all 5 Topics =====
# 1) Localization
def on_odom(m):
p = m['pose']['pose']['position']; o = m['pose']['pose']['orientation']
yaw = math.atan2(2*(o['w']*o['z']+o['x']*o['y']), 1-2*(o['z']**2+o['y']**2))
print(f"[Pose] x={p['x']:.2f} y={p['y']:.2f} yaw={math.degrees(yaw):.1f}°")
roslibpy.Topic(client,'/fast_lio_localization','nav_msgs/Odometry').subscribe(on_odom)
# 2) Global path
roslibpy.Topic(client,'/aft_pgo_path','nav_msgs/Path').subscribe(
lambda m: print(f"[Path] {len(m['poses'])} points"))
# 3) Map list
roslibpy.Topic(client,'/map_list_kitty','kitty_runtime_manager/MapList').subscribe(
lambda m: [print(f"[Map] {mp['map_name']} goals:{[g['name'] for g in mp['goals']]}") for mp in m['maps']])
# 4) Navigation status
roslibpy.Topic(client,'/ebot_goal_status_local_planner','std_msgs/Float64MultiArray').subscribe(
lambda m: m['data'][0]==1 and print("✅ Arrived"))
# 5) Battery
roslibpy.Topic(client,'/ebot_work_state','std_msgs/Float64MultiArray').subscribe(
lambda m: print(f"[Battery] {m['data'][1]:.0f}% {'Charging' if m['data'][0]==1 else 'Not charging'}"))
# ===== Call all 9 Services =====
def call(svc, args, srv):
r = roslibpy.Service(client, svc, srv).call(roslibpy.ServiceRequest(args))
print(f"[SVC] {svc} → {dict(r)}")
time.sleep(1)
call('/start_mapping_kitty', {}, 'std_srvs/Trigger')
call('/save_map_kitty', {'filepath':'test_map'}, 'kitty_runtime_manager/SaveMap')
call('/select_map_and_start_kitty', {'filepath':'test_map'}, 'kitty_runtime_manager/SaveMap')
call('/navigate_to_pose', {'goal':{'pose':{'position':{'x':1.0,'y':2.0,'z':0.0}}}}, 'nav_msgs/GetPlan')
call('/navigate_to_pose', {'goal':{'pose':{'position':{'x':999999,'y':888888,'z':0}}}}, 'nav_msgs/GetPlan')
call('/add_goal_kitty', {'map_name':'test_map','goal_name':'Reception','x':1.0,'y':2.0,'theta':0.5}, 'kitty_runtime_manager/AddGoal')
call('/delete_goal_kitty', {'map_name':'test_map','goal_name':'Reception'}, 'kitty_runtime_manager/DeleteGoal')
call('/docking_kitty', {}, 'std_srvs/Trigger')
call('/delete_map_kitty', {'map_name':'test_map'}, 'kitty_runtime_manager/DeleteMap')
try:
while True: time.sleep(1)
except KeyboardInterrupt: client.terminate()
4.2 JavaScript / HTML
의존성 설치 불필요, HTML
