NPC Ghost Mode
This page describes the NPC Ghost Mode function. The user can implement this feature via two interfaces: UDP and ROS protocol messages. Read along the rest of the documentation for more details.
Note: This feature is currently only available for ROS applications
NPC Ghost Mode
Ghost Mode is a feature that gives full control of an NPC vehicle’s location and orientation to the user. A ROS message containing the NPC vehicle’s positional data can be broadcast to the simulator, which will then place a vehicle at the received coordinates. The name is inspired by time trial “ghost” vehicles from racing games and simulations.
Access the NPC Ghost Mode from the main menu bar. Go to PlayMode >> Ghost >> NPC Ghost Mode to activate.
Once activated, a window prompting for connection details will appear.
Fill out the necessary IP addresses according to your simulation environment setup, and press Apply. A new /NpcGhost_Topic should be published over ROS, and a corresponding vehicle should be generated within MORAI SIM.
Interface Details
Assuming the ROS and UDP environment were set up correctly according to the ROS section of the manual, and UDP section of the manual, no additional setup should be necessary.
ROS Protocol Messages
To support custom ROS messages, the message source code must be built within the Linux development environment. MORAI SIM specific messages are distributed at the following link:
NPC Ghost Controller
morai_msgs/NpcGhostCmd.msg at master · morai-developergroup/morai_msgs
NPC Ghost Controller
Message Type : morai_msgs/NpcGhostCmd
Default Topic : /NpcGhost_Topic
Type Description : message creating Npc Vehicle in Npc Ghost mode.
No | Name | Type | Unit | Remarks |
---|---|---|---|---|
1 | header | Header | - |
|
2 | npc_list | - | Npc Ghost Vehicle Information |
UDP Protocol Messages
NPC Ghost Controller
Type Description
Following messages describe the characteristics of Npc Vehicle in Ghost Mode.
It could express the number of Npc vehicles up to 20.
If you want to create less than 20 Npc vehicles, specify the unique_id for unnecessary vehicle information as 0 and fill it with any arbitrary data.
Communication Protocol
Total Packet Size: 1031 Bytes
Data Size: 1000 Bytes (50 Bytes * 20)
unique_id (1byte / int8)
Npc Ghost Vehicle’s unique_id value (e.g. 2,3,4..)
car_name (25byte / string)
Npc Ghost Vehicle’s model name
The entering data size is 25byte. If it is less than 25byte, then, you should fill the rest of the space with extra spacing. If it is longer than 25 byte, you should reduce it to 25byte.
e.g.
"2016_Kia_Niro(HEV) "
x_position (4byte / float)
Npc Ghost Vehicle’s x position value (m)
y_position (4byte / float)
Npc Ghost Vehicle’s y position value (m)
z_position (4byte / float)
Npc Ghost Vehicle’s z position value (m)
roll_rotation (4byte / float)
Npc Ghost Vehicle’s roll rotation value (deg)
pitch_rotation (4byte / float)
Npc Ghost Vehicle’s pitch rotation value (deg)
yaw_rotation (4byte / float)
Npc Ghost Vehicle’s yaw rotation value (deg)
NPC Ghost Mode function
Following functions describe how the NPC vehicle in Ghost Mode communicates with the simulator via UDP protocols and command line input. These functions contain the basic parameters and environment set up configurations for the stable connection. Read through the code at your best convenience to understand more details.
NPC Ghost Cmd.py
from lib.morai_udp_parser import udp_parser,udp_sender
from lib.utils import pathReader,findLocalPath,purePursuit,Point,cruiseControl,vaildObject,velocityPlanning,pidController
import time
import threading
from math import cos,sin,sqrt,pow,atan2,pi
import os,json
path = os.path.dirname( os.path.abspath( __file__ ) )
with open(os.path.join(path,("params.json")),'r') as fp :
params = json.load(fp)
params=params["params"]
user_ip = params["user_ip"]
host_ip = params["host_ip"]
npc_ghost_data_port = params["npc_ghost_host_port"]
class npc_ghost_host_port :
def __init__(self):
self.npc_ghost_host_port=udp_sender(user_ip,npc_ghost_data_port,'npc_ghost_cmd')
self.emptyNpcData = []
#self.main_loop()
self.timer=threading.Timer(0.1,self.main_loop)
self.npc_ghost_data=[]
self.emptyNpcData.append(0)
self.emptyNpcData.append(bytes([0]*25))
for i in range(6):
self.emptyNpcData.append(0)
tmpNpcData = []
npc_0=2
modelName = "2017_Kia_Sorento"
tmpNameLen = len(modelName)
npc_1 = bytearray(modelName, "utf-8") + bytearray([0]* (25 - tmpNameLen))
#npc_1=bytearray("2017_Kia_Niro(HEV)","utf-8")+(bytearray([0]*6))
npc_2=20.0
npc_3=1100.0
npc_4=0.7
npc_5=0
npc_6=0
npc_7=0
tmpNpcData.append(npc_0)
tmpNpcData.append(npc_1)
tmpNpcData.append(npc_2)
tmpNpcData.append(npc_3)
tmpNpcData.append(npc_4)
tmpNpcData.append(npc_5)
tmpNpcData.append(npc_6)
tmpNpcData.append(npc_7)
self.npc_ghost_data.append(tmpNpcData)
for j in range(19):
self.npc_ghost_data.append(self.emptyNpcData)
self.timer.start()
def main_loop(self):
# unique_id, pose X, pose Y, pose Z, roll, Pitch, Yaw
#while True:
self.npc_ghost_host_port.send_data(self.npc_ghost_data)
###################
if __name__ == "__main__":
npc_ghost_host_port=npc_ghost_host_port()
while True :
time.sleep(1)
pass
MORAI UDP Parser.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import socket
import threading
import time
import struct
class udp_parser :
def __init__(self,ip,port,data_type):
print("ip",ip)
print("port",port)
self.data_type=data_type
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
recv_address = (ip,port)
self.sock.bind(recv_address)
self.data_size=65535
self.parsed_data=[]
thread = threading.Thread(target=self.recv_udp_data)
thread.daemon = True
thread.start()
def recv_udp_data(self):
while True :
raw_data, sender = self.sock.recvfrom(self.data_size)
self.data_parsing(raw_data)
def data_parsing(self,raw_data) :
if self.data_type == 'status' :
header=raw_data[0:11].decode()
data_length=struct.unpack('i',raw_data[11:15])
if header == '#MoraiInfo$' : # and data_length[0] ==32:
vgen_ctrl_cmd = struct.unpack('b',raw_data[15:16])
vgen_gear = struct.unpack('b',raw_data[16:17])
unpacked_data_1 = struct.unpack('fi',raw_data[17:25])
unpacked_data_2 = struct.unpack('ffffffff',raw_data[27:59])
unpacked_data = vgen_ctrl_cmd + vgen_gear + unpacked_data_1 + unpacked_data_2
# unpacked_data=struct.unpack('ffffffff',raw_data[27:59])
self.parsed_data=list(unpacked_data)
def get_data(self) :
return self.parsed_data
def __del__(self):
self.sock.close()
print('del')
class udp_sender :
def __init__(self,ip,port,data_type):
print("ip : ", ip)
print("port : ", port)
print("data_type : ", data_type)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.ip=ip
self.port=port
self.data_type=data_type
if self.data_type=='ctrl_cmd':
header='#MoraiCtrlCmd$'.encode()
data_length=struct.pack('i',12)
# aux_data=struct.pack('iii',0,0,0)
self.upper=header+data_length # +aux_data
self.tail='\r\n'.encode()
################## npc_ghost_cmd ##################
elif self.data_type == 'npc_ghost_cmd':
header='#NpcGhostCmd$'.encode()
data_length=struct.pack('<i',1000)
aux_data=struct.pack('iii',0,0,0)
self.upper=header+data_length+aux_data
self.tail='\r\n'.encode()
def send_data(self,data):
print('1')
if self.data_type=='ctrl_cmd':
packed_mode=struct.pack('b',data[0])
packed_gear=struct.pack('b',data[1])
aux_data1=struct.pack('h',0)
aux_data2=struct.pack('ii',0,0)
packed_accel=struct.pack('f',data[2])
packed_brake=struct.pack('f',data[3])
packed_steering_angle=struct.pack('f',data[4])
lower=packed_mode+packed_gear+aux_data1+aux_data2+packed_accel+packed_brake+packed_steering_angle
send_data=self.upper+lower+self.tail
# print(len(send_data),send_data)
################## npc_ghost_cmd ##################
elif self.data_type == 'npc_ghost_cmd':
print('1')
lower=None
for npc in range(20) :
if npc <len(data):
npc_unique_id=struct.pack('<h',data[npc][0])
#car_name=struct.pack('<25s',data[npc][1])
status_data=struct.pack('<6f',data[npc][2],data[npc][3],data[npc][4],data[npc][5],data[npc][6],data[npc][7])
#pack_data=npc_unique_id+car_name+status_data
pack_data=npc_unique_id+status_data
else:
npc_index=struct.pack('h',0)
#car_name=struct.pack('<25s','')
status_data=struct.pack('ffffff',0.0,0.0,0.0,0.0,0.0,0.0)
pack_data=npc_index+status_data
if lower==None :
lower=pack_data
else :
lower+=pack_data
send_data=self.upper+lower+self.tail
print("send_data : ", send_data)
self.sock.sendto(send_data,(self.ip,self.port))