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

NpcGhostInfo[]

-

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

          • (warning) 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
CODE
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))     
    
CODE