diff --git a/donkeycar/parts/datastore.py b/donkeycar/parts/datastore.py index 0f4faac19..11f3679af 100644 --- a/donkeycar/parts/datastore.py +++ b/donkeycar/parts/datastore.py @@ -222,7 +222,7 @@ def put_record(self, data): # in case val is a numpy.float32, which json doesn't like json_data[key] = float(val) - elif typ in ['str', 'float', 'int', 'boolean', 'vector']: + elif typ in ['str', 'float', 'int', 'boolean', 'vector', 'dict']: json_data[key] = val elif typ is 'image': diff --git a/donkeycar/parts/dgym.py b/donkeycar/parts/dgym.py index fc9f1a42e..a0082f43c 100644 --- a/donkeycar/parts/dgym.py +++ b/donkeycar/parts/dgym.py @@ -27,6 +27,7 @@ def __init__(self, sim_path, host="127.0.0.1", port=9091, headless=0, env_name=" self.action = [0.0, 0.0] self.running = True self.info = { 'pos' : (0., 0., 0.)} + self.delay = float(delay) if "body_style" in conf: @@ -52,5 +53,23 @@ def shutdown(self): time.sleep(0.2) self.env.close() +class DonkeyGymEnvWithInfo(DonkeyGymEnv): + """ This subclass will return the Simulator's info dictionary along with the current image. + The info dictionary will include the reward and done flags. + """ + def __init__(self, *args, **kwargs): + # only allow known json-able entries from the sim's info dictionary + self.valid = ['pos', 'cte', 'speed', 'hit'] + + super(DonkeyGymEnvWithInfo,self).__init__(*args, **kwargs) + + def update(self): + while self.running: + self.frame, reward, done, info = self.env.step(self.action) + self.info = {key: info[key] for key in info if key in self.valid} + self.info['reward'] = reward + self.info['done'] = done - + def run_threaded(self, steering, throttle): + super(DonkeyGymEnvWithInfo,self).run_threaded(steering, throttle) + return self.frame, self.info diff --git a/donkeycar/templates/cfg_complete.py b/donkeycar/templates/cfg_complete.py index 94711b62b..05dc051d6 100644 --- a/donkeycar/templates/cfg_complete.py +++ b/donkeycar/templates/cfg_complete.py @@ -223,6 +223,7 @@ GYM_CONF = { "body_style" : "donkey", "body_rgb" : (128, 128, 128), "car_name" : "me", "font_size" : 100} # body style(donkey|bare|car01) body rgb 0-255 SIM_HOST = "127.0.0.1" # when racing on virtual-race-league use host "trainmydonkey.com" SIM_ARTIFICIAL_LATENCY = 0 # this is the millisecond latency in controls. Can use useful in emulating the delay when useing a remote server. values of 100 to 400 probably reasonable. +DONKEY_GYM_INFO = False # Include Sim info dict when saving Tub files. #publish camera over network #This is used to create a tcp service to pushlish the camera feed diff --git a/donkeycar/templates/cfg_path_follow.py b/donkeycar/templates/cfg_path_follow.py index d104b6ccb..6655b3a66 100644 --- a/donkeycar/templates/cfg_path_follow.py +++ b/donkeycar/templates/cfg_path_follow.py @@ -109,3 +109,4 @@ DONKEY_GYM = False DONKEY_SIM_PATH = "path to sim" #"/home/tkramer/projects/sdsandbox/sdsim/build/DonkeySimLinux/donkey_sim.x86_64" DONKEY_GYM_ENV_NAME = "donkey-generated-track-v0" # ("donkey-generated-track-v0"|"donkey-generated-roads-v0"|"donkey-warehouse-v0"|"donkey-avc-sparkfun-v0") +DONKEY_GYM_INFO = False # Include Sim info dict when saving Tub files. diff --git a/donkeycar/templates/complete.py b/donkeycar/templates/complete.py index 45b44dad8..09b910f5e 100644 --- a/donkeycar/templates/complete.py +++ b/donkeycar/templates/complete.py @@ -103,10 +103,15 @@ def drive(cfg, model_path=None, use_joystick=False, model_type=None, camera_type from donkeycar.parts.dgym import DonkeyGymEnv inputs = [] + outputs=['cam/image_array'] threaded = True if cfg.DONKEY_GYM: - from donkeycar.parts.dgym import DonkeyGymEnv - cam = DonkeyGymEnv(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY) + from donkeycar.parts.dgym import DonkeyGymEnv, DonkeyGymEnvWithInfo + if cfg.DONKEY_GYM_INFO: + outputs.append( 'sim/info' ) + cam = DonkeyGymEnvWithInfo(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY) + else: + cam = DonkeyGymEnv(cfg.DONKEY_SIM_PATH, host=cfg.SIM_HOST, env_name=cfg.DONKEY_GYM_ENV_NAME, conf=cfg.GYM_CONF, delay=cfg.SIM_ARTIFICIAL_LATENCY) threaded = True inputs = ['angle', 'throttle'] elif cfg.CAMERA_TYPE == "PICAM": @@ -130,7 +135,7 @@ def drive(cfg, model_path=None, use_joystick=False, model_type=None, camera_type else: raise(Exception("Unkown camera type: %s" % cfg.CAMERA_TYPE)) - V.add(cam, inputs=inputs, outputs=['cam/image_array'], threaded=threaded) + V.add(cam, inputs=inputs, outputs=outputs, threaded=threaded) if use_joystick or cfg.USE_JOYSTICK_AS_DEFAULT: #modify max_throttle closer to 1.0 to have more power @@ -569,7 +574,11 @@ def run(self, mode, recording): if cfg.RECORD_DURING_AI: inputs += ['pilot/angle', 'pilot/throttle'] types += ['float', 'float'] - + + if cfg.DONKEY_GYM_INFO: + inputs += ['sim/info'] + types += ['dict'] + th = TubHandler(path=cfg.DATA_PATH) tub = th.new_tub_writer(inputs=inputs, types=types, user_meta=meta) V.add(tub, inputs=inputs, outputs=["tub/num_records"], run_condition='recording') diff --git a/donkeycar/tests/test_tub.py b/donkeycar/tests/test_tub.py index 5fe636c10..77fd8ba4b 100644 --- a/donkeycar/tests/test_tub.py +++ b/donkeycar/tests/test_tub.py @@ -48,6 +48,24 @@ def test_tub_write_numpy(tub): rec_out = tub.get_record(rec_index) assert type(rec_out['user/throttle']) == float +def test_tub_dict(tub): + """Tub can save a record that contains a dictionary with json-able types.""" + d = {'pos': (49.99962, 0.7410042, 50.16277), + 'cte': -0.0006235633, + 'hit': None, + 'done': True } + x=123 + z=0.0 + rec_in = {'user/angle': x, 'user/throttle':z, 'sim/info':d} + tub.meta['inputs'].append('sim/info') + tub.meta['types'].append('dict') + rec_index = tub.put_record(rec_in) + rec_out = tub.get_record(rec_index) + assert type(rec_out['sim/info']) == dict + assert type(rec_out['sim/info']['cte']) == float + assert rec_out['sim/info']['hit'] is None + assert rec_out['sim/info']['done'] is True + def test_tub_exclude(tub): """ Make sure the Tub will exclude records in the exclude set """