Exemple #1
0
class Sim(object):
    def __init__(self, config, working_dir=None):
        self.logger = logging.getLogger("Sim")

        self.logger.info("Initializing simulation")

        # Config
        self.config = config

        # Working directory (for csv files and whatnot)
        if working_dir is not None:
            self.set_working_dir(working_dir)

        self.logger.info("Working directory is {} ({})".format(
            self.config.working_dir,
            os.path.join(os.getcwd(), self.config.working_dir)))

        self.ensure_working_dir()

        # Simulator control
        self.step_listeners = []  # For in-sim control (per step)
        self.end_conditions = []
        self.end_listeners = []

        # Pre and post
        self.preprocessors = []
        self.postprocessors = []

        # Time
        self.fixed_timestep_usec = Units.usec(
            config.fixed_timestep)  # Convert to usec
        self.time_dialator = TimeDialator(self)  # We're going to step this

        # Components
        self.pusher = Pusher(self, self.config.pusher)
        self.track = Track(self, self.config.track)
        self.pod = Pod(self, self.config.pod)
        #self.fcu = Fcu(self, self.config.fcu)

        # Component setup

        # Set the initial pusher position to meet the pusher plate
        # @todo: should we set this somewhere else? Like in a run controller?
        self.pusher.position = self.pod.pusher_plate_offset

        # Sensors
        self.sensors = {}
        self.sensors['pod'] = PodSensor(self, self.config.sensors.pod)
        self.sensors['pod'].add_step_listener(
            SensorCsvWriter(self, self.config.sensors.pod))

        self.sensors['pusher'] = PusherSensor(self, self.config.sensors.pusher)
        self.sensors['pusher'].add_step_listener(
            SensorCsvWriter(self, self.config.sensors.pusher))

        # - Accelerometers
        self.sensors['accel'] = []

        for idx, sensor_config in self.config.sensors.accel.iteritems():
            # Note: we need to create a Config object here because Config does not currently handle lists very well...
            self.sensors['accel'].append(
                Accelerometer(self, Config(sensor_config)))
            sensor = self.sensors['accel'][idx]
            sensor.add_step_listener(
                AccelerometerTestListener(self, sensor.config))
            sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
            #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))

        # - Laser Contrast Sensors
        """
        self.sensors['laser_contrast'] = []
        for idx, sensor_config in self.config.sensors.laser_contrast.iteritems():
            self.sensors['laser_contrast'].append(LaserContrastSensor(self, Config(sensor_config)))
            sensor = self.sensors['laser_contrast'][idx]
            #sensor.add_step_listener(LaserContrastTestListener(self, sensor.config))  # For debugging
            sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
            #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))  # These don't have 'raw' values since they just call an interrupt

        # - Laser Opto Sensors (height and yaw)
        self.sensors['laser_opto'] = []
        for idx, sensor_config in self.config.sensors.laser_opto.iteritems():
            self.sensors['laser_opto'].append(LaserOptoSensor(self, Config(sensor_config)))
            sensor = self.sensors['laser_opto'][idx]
            #sensor.add_step_listener(LaserOptoTestListener(self, sensor.config))  # For debugging
            sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
            #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))   
        
        # - Laser Distance Sensor
        self.sensors['laser_dist'] = LaserDistSensor(self, Config(self.config.sensors.laser_dist))
        sensor = self.sensors['laser_dist']
        sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
        #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))
        """

        # - Brake Sensors: MLP, limit switches (for both)
        pass

        # Networking
        self.comms = PodComms(self, self.config.networking)
        self.add_end_listener(self.comms)

        # FCU (!)
        if self.config.fcu.enabled:
            self.fcu = Fcu(self, self.config.fcu)
            self.add_end_listener(self.fcu)
        else:
            # @todo: will this have any side effects (e.g. not having an fcu?)
            self.logger.info(
                "Not initializing FCU because it is disabled via config.")

        # Initial setup
        # @todo: write a method by which we can maybe control the push from the ground station or rpod_control vb.net app
        # @todo: write a means by which we can start the push some configurable amount of time after the pod enters READY state
        #self.pusher.start_push()  # Only for testing

        # Volatile
        self.elapsed_time_usec = 0
        self.n_steps_taken = 0

        # Testing laser opto sensor class
        # self.laser_opto_sensors = LaserOptoSensors(self, self.config.sensors.laser_opto)

        #self.pod_sensor_writer.pause()  # Paused for use in the gui

        # Testing brakes  (pod now has brakes)
        #from brakes import Brake
        #self.brake_1 = Brake(self, None)
        #self.brake_1.gap = 0.025 # Set it to test forces

        # End condition checker (to stop the simulation)
        #self.add_end_condition(SimEndCondition())  # Currently disabled in favor of using the RunController

        # Handle data writing
        # @ todo: handle data writing. Note: Each sim instance should be handed a directory to use for writing data

    @classmethod
    def load_config_files(cls, config_files):
        """ Load one or more config files (later files overlay earlier ones) """

        ymls = []
        for configfile in config_files:
            with open(configfile, 'rb') as f:
                ymls.append(yaml.load(f))
        merged = {}
        for yml in ymls:
            merged = yaml_merge(merged, yml)

        config = Config(merged)
        return config.sim

        #config = Config()
        #for configfile in config_files:
        # Note: each file loaded by the config will overlay on the previously loaded files
        #    config.loadfile(configfile)
        #return config.sim

    def set_working_dir(self, working_dir):
        """ Set our working directory (for file writing and whatnot) """
        self.config.working_dir = working_dir

    def data_logging_enabled(self, data_writer, sensor):
        """ Tell data writers whether or not to log data (e.g. csv writers) """
        # @todo: write something that gets a value from runtime config
        # @todo: write something that turns data logging on and off based on FCU state (assuming the FCU is enabled)
        # @todo: potentially turn on or off based on sensor, writer type, or a combination of the two
        return True  # Turn data logging off for now

    def step(self, dt_usec):

        # Step the pusher first (will apply pressure and handle disconnection)
        self.pusher.step(dt_usec)

        # Step the pod (will handle all other forces and pod physics)
        self.pod.step(dt_usec)

        # Step the sensors
        for sensor in self.sensors.values():
            # Some of our sensors are lists of sensors
            if isinstance(sensor, list):
                for s in sensor:
                    s.step(dt_usec)
            else:
                sensor.step(dt_usec)

        # Step the time dialator to keep our timers in sync
        self.time_dialator.step(dt_usec)
        if self.n_steps_taken % 500 == 0:
            self.logger.debug(
                "Time dialation factor is {} after {} steps".format(
                    self.time_dialator.dialation, self.n_steps_taken))

            # Debugging
            self.logger.debug("Track DB {}".format(
                self.fcu.lib.u32FCU_FCTL_TRACKDB__Get_CurrentDB()))

            info = [
                #self.fcu.lib.u8FCU_FCTL_TRACKDB__Accel__Get_Use(),  # Deprecated
                self.fcu.lib.
                s32FCU_FCTL_TRACKDB__Accel__Get_Accel_Threshold_mm_ss(),
                self.fcu.lib.
                s16FCU_FCTL_TRACKDB__Accel__Get_Accel_ThresholdTime_x10ms(),
                self.fcu.lib.
                s32FCU_FCTL_TRACKDB__Accel__Get_Decel_Threshold_mm_ss(),
                self.fcu.lib.
                s16FCU_FCTL_TRACKDB__Accel__Get_Decel_ThresholdTime_x10ms(),
            ]
            self.logger.debug("Track DB: Accel: {}".format(info))

            info = {
                'psa': self.pusher.acceleration,
                'psv': self.pusher.velocity,
                'psp': self.pusher.position,
                'pda': self.pod.acceleration,
                'pdv': self.pod.velocity,
                'pdp': self.pod.position,
            }
            self.logger.debug(
                "Pusher avp:  {psa}  {psv}  {psp};  Pod avp:  {pda}  {pdv}  {pdp}"
                .format(**info))

        self.elapsed_time_usec += dt_usec
        self.n_steps_taken += 1

        for step_listener in self.step_listeners:
            step_listener.step_callback(self)

    def run_threaded(self):
        """ Run the simulator in a thread and return the thread (don't join it here) """

        t = threading.Thread(target=self.run, args=())
        t.daemon = True
        t.start()
        return t  # Return the thread, but don't join it (the caller can join if they want to)

    def run(self):

        self.logger.info("Starting simulation")

        self.end_flag = False
        sim_start_t = time.time()

        # Notify preprocessors
        for processor in self.preprocessors:
            processor.process(self)

        # Networking
        self.comms.run_threaded()  # Start the network node listeners

        # FCU
        if self.config.fcu.enabled:
            self.fcu.run_threaded()

        while (True):

            # Check our end listener(s) to see if we should end the simulation (e.g. the pod has stopped)
            for listener in self.end_conditions:
                if listener.is_finished(self):
                    self.end_flag = True

            if self.end_flag:
                # Notify our 'finished' listeners and break the loop
                for end_listener in self.end_listeners:
                    end_listener.end_callback(self)

                break

            self.step(self.fixed_timestep_usec)

        sim_end_t = time.time()
        sim_time = sim_end_t - sim_start_t
        #print "LaserOptoTestListener: gap sensor took {} samples that were within a gap.".format(self.lotl.n_gaps)
        self.logger.info(
            "Simulated {} steps/{} seconds in {} actual seconds.".format(
                self.n_steps_taken, self.elapsed_time_usec / 1000000,
                sim_time))

        # Notify postprocessors
        for processor in self.postprocessors:
            processor.process(self)

    def add_step_listener(self, listener):
        """ 
        Register a listener that will be called every step. 
        A step listener can be any class that implements the following method:
        - step_callback(sim)
        """
        self.step_listeners.append(listener)

    def add_end_condition(self, listener):
        """ Add a listener that will tell us if we should end the simulator """
        self.end_conditions.append(listener)

    def add_end_listener(self, listener):
        """ Add a listener for when we've ended the sim """
        self.end_listeners.append(listener)

    def add_preprocessor(self, processor):
        self.preprocessors.append(processor)

    def add_postprocessor(self, processor):
        self.postprocessors.append(processor)

    def ensure_working_dir(self):
        """ Ensure existence of base directory for data storage """
        # @todo: Log the error/exception if there is one
        # Try to make the directory(s)

        path = self.config.working_dir

        try:
            os.makedirs(path)
        except OSError as exc:  # Python >2.5
            if exc.errno == errno.EEXIST and os.path.isdir(path):
                pass
            else:
                raise
Exemple #2
0
class Sim:
    def __init__(self, config, working_dir=None):
        self.logger = logging.getLogger("Sim")

        self.logger.info("Initializing simulation")

        # Config
        self.config = config

        # Working directory (for csv files and whatnot)
        if working_dir is not None:
            self.set_working_dir(working_dir)

        self.logger.info("Working directory is {} ({})".format(
            self.config.working_dir,
            os.path.join(os.getcwd(), self.config.working_dir)))

        self.ensure_working_dir()

        # Simulator control
        self.end_conditions = []
        self.end_listeners = []

        # Pre and post
        self.preprocessors = []
        self.postprocessors = []

        # Time
        self.fixed_timestep_usec = Units.usec(
            config.fixed_timestep)  # Convert to usec
        self.time_dialator = TimeDialator(self)  # We're going to step this

        # Components
        self.pusher = Pusher(self, self.config.pusher)
        self.track = Track(self, self.config.track)
        self.pod = Pod(self, self.config.pod)
        #self.fcu = Fcu(self, self.config.fcu)

        # Sensors
        self.sensors = {}
        self.sensors['pod'] = PodSensor(self, self.config.sensors.pod)
        self.sensors['pod'].add_step_listener(
            SensorCsvWriter(self, self.config.sensors.pod))

        # - Accelerometers
        self.sensors['accel'] = []

        for idx, sensor_config in self.config.sensors.accel.iteritems():
            # Note: we need to create a Config object here because Config does not currently handle lists very well...
            self.sensors['accel'].append(
                Accelerometer(self, Config(sensor_config)))
            sensor = self.sensors['accel'][idx]
            sensor.add_step_listener(
                AccelerometerTestListener(self, sensor.config))
            sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
            #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))

        # - Laser Contrast Sensors
        self.sensors['laser_contrast'] = []
        for idx, sensor_config in self.config.sensors.laser_contrast.iteritems(
        ):
            self.sensors['laser_contrast'].append(
                LaserContrastSensor(self, Config(sensor_config)))
            sensor = self.sensors['laser_contrast'][idx]
            #sensor.add_step_listener(LaserContrastTestListener(self, sensor.config))  # For debugging
            sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
            #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))  # These don't have 'raw' values since they just call an interrupt

        # - Laser Opto Sensors (height and yaw)
        self.sensors['laser_opto'] = []
        for idx, sensor_config in self.config.sensors.laser_opto.iteritems():
            self.sensors['laser_opto'].append(
                LaserOptoSensor(self, Config(sensor_config)))
            sensor = self.sensors['laser_opto'][idx]
            #sensor.add_step_listener(LaserOptoTestListener(self, sensor.config))  # For debugging
            sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
            #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))

        # - Laser Distance Sensor
        self.sensors['laser_dist'] = LaserDistSensor(
            self, Config(self.config.sensors.laser_dist))
        sensor = self.sensors['laser_dist']
        sensor.add_step_listener(SensorCsvWriter(self, sensor.config))
        #sensor.add_step_listener(SensorRawCsvWriter(self, sensor.config))

        # - Brake Sensors: MLP, limit switches (for both)
        pass

        # Networking
        self.comms = PodComms(self, self.config.networking)
        self.add_end_listener(self.comms)

        # FCU (!)
        if self.config.fcu.enabled:
            self.fcu = Fcu(self, self.config.fcu)
            self.add_end_listener(self.fcu)
        else:
            # @todo: will this have any side effects (e.g. not having an fcu?)
            self.logger.info(
                "Not initializing FCU because it is disabled via config.")

        # Initial setup
        self.pusher.start_push()

        # Volatile
        self.elapsed_time_usec = 0
        self.n_steps_taken = 0

        # Testing laser opto sensor class
        # self.laser_opto_sensors = LaserOptoSensors(self, self.config.sensors.laser_opto)

        #self.pod_sensor_writer.pause()  # Paused for use in the gui

        # Testing brakes  (pod now has brakes)
        #from brakes import Brake
        #self.brake_1 = Brake(self, None)
        #self.brake_1.gap = 0.025 # Set it to test forces

        # End condition checker (to stop the simulation)
        self.add_end_condition(SimEndCondition())

        # Handle data writing
        # @ todo: handle data writing. Note: Each sim instance should be handed a directory to use for writing data

    @classmethod
    def load_config_files(cls, config_files):
        """ Load one or more config files (later files overlay earlier ones) """

        config = Config()
        for configfile in config_files:
            # Note: each file loaded by the config will overlay on the previously loaded files
            config.loadfile(configfile)
        return config.sim

    def set_working_dir(self, working_dir):
        """ Set our working directory (for file writing and whatnot) """
        self.config.working_dir = working_dir

    def step(self, dt_usec):

        # Step the pusher first (will apply pressure and handle disconnection)
        self.pusher.step(dt_usec)

        # Step the pod (will handle all other forces and pod physics)
        self.pod.step(dt_usec)

        # Step the sensors
        for sensor in self.sensors.values():
            # Some of our sensors are lists of sensors
            if isinstance(sensor, list):
                for s in sensor:
                    s.step(dt_usec)
            else:
                sensor.step(dt_usec)

        # Step the time dialator to keep our timers in sync
        self.time_dialator.step(dt_usec)
        if self.n_steps_taken % 500 == 0:
            self.logger.debug(
                "Time dialation factor is {} after {} steps".format(
                    self.time_dialator.dialation, self.n_steps_taken))

        self.elapsed_time_usec += dt_usec
        self.n_steps_taken += 1

    def run_threaded(self):
        """ Run the simulator in a thread and return the thread (don't join it here) """

        t = threading.Thread(target=self.run, args=())
        t.daemon = True
        t.start()
        return t  # Return the thread, but don't join it (the caller can join if they want to)

    def run(self):

        self.logger.info("Starting simulation")

        self.end_flag = False
        sim_start_t = time.time()

        # Notify preprocessors
        for processor in self.preprocessors:
            processor.process(self)

        # Networking
        self.comms.run_threaded()  # Start the network node listeners

        # FCU
        if self.config.fcu.enabled:
            self.fcu.run_threaded()

        while (True):

            # Check our end listener(s) to see if we should end the simulation (e.g. the pod has stopped)
            for listener in self.end_conditions:
                if listener.is_finished(self):
                    self.end_flag = True

            if self.end_flag:
                # Notify our 'finished' listeners and break the loop
                for end_listener in self.end_listeners:
                    end_listener.end_callback(self)

                break

            self.step(self.fixed_timestep_usec)

        sim_end_t = time.time()
        sim_time = sim_end_t - sim_start_t
        #print "LaserOptoTestListener: gap sensor took {} samples that were within a gap.".format(self.lotl.n_gaps)
        self.logger.info(
            "Simulated {} steps/{} seconds in {} actual seconds.".format(
                self.n_steps_taken, self.elapsed_time_usec / 1000000,
                sim_time))

        # Notify postprocessors
        for processor in self.postprocessors:
            processor.process(self)

    def add_end_condition(self, listener):
        """ Add a listener that will tell us if we should end the simulator """
        self.end_conditions.append(listener)

    def add_end_listener(self, listener):
        """ Add a listener for when we've ended the sim """
        self.end_listeners.append(listener)

    def add_preprocessor(self, processor):
        self.preprocessors.append(processor)

    def add_postprocessor(self, processor):
        self.postprocessors.append(processor)

    def ensure_working_dir(self):
        """ Ensure existence of base directory for data storage """
        # @todo: Log the error/exception if there is one
        # Try to make the directory(s)

        path = self.config.working_dir

        try:
            os.makedirs(path)
        except OSError as exc:  # Python >2.5
            if exc.errno == errno.EEXIST and os.path.isdir(path):
                pass
            else:
                raise