Example #1
0
    def __init__(self,
                 device,
                 adapters=(),
                 device_builder=None,
                 control_server=None):
        super(Simulation, self).__init__()

        self._device_builder = device_builder

        self._device = device
        self._adapters = AdapterCollection(*adapters)

        self._speed = 1.0  # Multiplier for delta t
        self._cycle_delay = 0.1  # Target time between cycles

        self._start_time = None  # Real time when the simulation started
        self._cycles = 0  # Number of cycles processed
        self._runtime = 0.0  # Total simulation time processed

        self._running = False
        self._started = False
        self._stop_commanded = False

        # Constructing the control server must be deferred until the end,
        # because the construction is not complete at this point
        self._control_server = None  # Just initialize to None and use property setter afterwards
        self.control_server = control_server

        self.log.debug(
            'Created simulation. Device type: %s, Protocol(s): %s, Possible setups for '
            'switching: %s, Control server: %s', device.__class__.__name__,
            ', '.join(self._adapters.protocols),
            ', '.join(device_builder.setups.keys())
            if device_builder else None, control_server)
Example #2
0
    def test_set_device(self):
        adapter = DummyAdapter(protocol="foo")
        adapter.interface = MagicMock()

        collection = AdapterCollection(adapter)
        collection.set_device("test")

        self.assertEqual(adapter.interface.device, "test")
    def __init__(self, device, adapters=(), device_builder=None, control_server=None):
        super(Simulation, self).__init__()

        self._device_builder = device_builder

        self._device = device
        self._adapters = AdapterCollection(*adapters)

        self._speed = 1.0  # Multiplier for delta t
        self._cycle_delay = 0.1  # Target time between cycles

        self._start_time = None  # Real time when the simulation started
        self._cycles = 0  # Number of cycles processed
        self._runtime = 0.0  # Total simulation time processed

        self._running = False
        self._started = False
        self._stop_commanded = False

        # Constructing the control server must be deferred until the end,
        # because the construction is not complete at this point
        self._control_server = None  # Just initialize to None and use property setter afterwards
        self._control_server_thread = None
        self.control_server = control_server

        self.log.debug(
            'Created simulation. Device type: %s, Protocol(s): %s, Possible setups for '
            'switching: %s, Control server: %s', device.__class__.__name__,
            ', '.join(self._adapters.protocols),
            ', '.join(device_builder.setups.keys()) if device_builder else None,
            control_server)
Example #4
0
    def test_configuration(self):
        collection = AdapterCollection(
            DummyAdapter("protocol_a", options={
                "bar": 2,
                "foo": 3
            }),
            DummyAdapter("protocol_b", options={
                "bar": True,
                "foo": False
            }),
        )

        self.assertDictEqual(
            collection.configuration(),
            {
                "protocol_a": {
                    "bar": 2,
                    "foo": 3
                },
                "protocol_b": {
                    "bar": True,
                    "foo": False
                },
            },
        )

        self.assertDictEqual(
            collection.configuration("protocol_a"),
            {
                "protocol_a": {
                    "bar": 2,
                    "foo": 3
                },
            },
        )

        self.assertDictEqual(
            collection.configuration("protocol_b"),
            {
                "protocol_b": {
                    "bar": True,
                    "foo": False
                },
            },
        )
Example #5
0
    def test_remove_adapter(self):
        collection = AdapterCollection(DummyAdapter("foo"))

        self.assertSetEqual(set(collection.protocols), {"foo"})
        self.assertRaises(RuntimeError, collection.remove_adapter, "bar")

        assertRaisesNothing(self, collection.remove_adapter, "foo")

        self.assertEqual(len(collection.protocols), 0)
Example #6
0
    def test_remove_adapter(self):
        collection = AdapterCollection(DummyAdapter('foo'))

        self.assertSetEqual(set(collection.protocols), {'foo'})
        self.assertRaises(RuntimeError, collection.remove_adapter, 'bar')

        assertRaisesNothing(self, collection.remove_adapter, 'foo')

        self.assertEqual(len(collection.protocols), 0)
Example #7
0
    def test_configuration(self):
        collection = AdapterCollection(
            DummyAdapter('protocol_a', options={'bar': 2, 'foo': 3}),
            DummyAdapter('protocol_b', options={'bar': True, 'foo': False}))

        self.assertDictEqual(collection.configuration(),
                             {
                                 'protocol_a': {'bar': 2, 'foo': 3},
                                 'protocol_b': {'bar': True,
                                                'foo': False}
                             })

        self.assertDictEqual(collection.configuration('protocol_a'),
                             {
                                 'protocol_a': {'bar': 2, 'foo': 3},
                             })

        self.assertDictEqual(collection.configuration('protocol_b'),
                             {
                                 'protocol_b': {'bar': True, 'foo': False},
                             })
Example #8
0
    def test_add_adapter(self):
        collection = AdapterCollection()
        self.assertEqual(len(collection.protocols), 0)

        assertRaisesNothing(self, collection.add_adapter, DummyAdapter('foo'))

        self.assertEqual(len(collection.protocols), 1)
        self.assertSetEqual(set(collection.protocols), {'foo'})

        assertRaisesNothing(self, collection.add_adapter, DummyAdapter('bar'))

        self.assertEqual(len(collection.protocols), 2)
        self.assertSetEqual(set(collection.protocols), {'foo', 'bar'})

        self.assertRaises(RuntimeError, collection.add_adapter, DummyAdapter('bar'))
Example #9
0
    def test_add_adapter(self):
        collection = AdapterCollection()
        self.assertEqual(len(collection.protocols), 0)

        assertRaisesNothing(self, collection.add_adapter, DummyAdapter("foo"))

        self.assertEqual(len(collection.protocols), 1)
        self.assertSetEqual(set(collection.protocols), {"foo"})

        assertRaisesNothing(self, collection.add_adapter, DummyAdapter("bar"))

        self.assertEqual(len(collection.protocols), 2)
        self.assertSetEqual(set(collection.protocols), {"foo", "bar"})

        self.assertRaises(RuntimeError, collection.add_adapter,
                          DummyAdapter("bar"))
Example #10
0
class Simulation(object):
    """
    The Simulation class controls certain aspects of a device simulation,
    the most important one being time.

    Once :meth:`start` is called, the process-method of the device
    is called in regular intervals. The time between these calls is
    influenced by the cycle_delay property. Because of the way some
    network protocols work, the actual processing time can be
    longer or shorter, so cycle_delay should be seen as a guideline
    rather than a guaranteed parameter.

    In the simplest case, the actual time-delta between two cycles
    is passed to the simulated device so that it can update its internal
    state according to the elapsed time. It is however possible to set
    a simulation speed, which serves as a multiplier for this time.
    If the speed is set to 2 and 0.1 seconds pass between two cycles,
    the simulation is asked to simulate 0.2 seconds, and so on. Speed 0
    effectively stops all time dependent calculations in the
    simulated device.

    Another possibility to pause the simulation is the pause-method. After
    calling it, all processing in the device is suspended, while the communication
    adapters continue to work. This can be used to simulate that a device is "hanging".
    The simulation can be continued using the resume-method.

    A number of status properties provide information about the simulation.
    The total uptime (in actually elapsed time) can be obtained through the
    uptime-property, whereas the runtime-property contains the simulated time.
    The cycles-property indicates the total number of simulation cycles, which
    does not increase when the simulation is paused.

    Finally, the simulation can be stopped entirely with the stop-method.

    All functionality except for the start-method can be made available to remote
    computers via a :class:`ControlServer`-instance. The way to expose device and simulation
    is to pass a 'host:port'-string as the control_server argument,
    which will construct the control server. Simulation will try to start the
    control server using the start_server method.

    :param device: The simulated device.
    :param adapters: Adapters which expose the simulated device.
    :param device_builder: :class:`~lewis.core.devices.DeviceBuilder` instance to enable setup-
                           switching at runtime.
    :param control_server: 'host:port'-string to construct control server or None.
    """
    def __init__(self,
                 device,
                 adapters=(),
                 device_builder=None,
                 control_server=None):
        super(Simulation, self).__init__()

        self._device_builder = device_builder

        self._device = device
        self._adapters = AdapterCollection(*adapters)

        self._speed = 1.0  # Multiplier for delta t
        self._cycle_delay = 0.1  # Target time between cycles

        self._start_time = None  # Real time when the simulation started
        self._cycles = 0  # Number of cycles processed
        self._runtime = 0.0  # Total simulation time processed

        self._running = False
        self._started = False
        self._stop_commanded = False

        # Constructing the control server must be deferred until the end,
        # because the construction is not complete at this point
        self._control_server = None  # Just initialize to None and use property setter afterwards
        self.control_server = control_server

        self.log.debug(
            'Created simulation. Device type: %s, Protocol(s): %s, Possible setups for '
            'switching: %s, Control server: %s', device.__class__.__name__,
            ', '.join(self._adapters.protocols),
            ', '.join(device_builder.setups.keys())
            if device_builder else None, control_server)

    def _create_control_server(self, control_server):
        if control_server is None:
            return None

        return ControlServer(
            {
                'device':
                ExposedObject(self._device,
                              exclude_inherited=True,
                              lock=self._adapters.device_lock),
                'simulation':
                ExposedObject(self,
                              exclude=('start', 'control_server', 'log'),
                              exclude_inherited=True),
                'interface':
                ExposedObject(self._adapters,
                              exclude=('device_lock', 'add_adapter',
                                       'remove_adapter', 'handle', 'log'),
                              exclude_inherited=True)
            }, control_server)

    @property
    def setups(self):
        """
        A list of setups that are available. Use :meth:`switch_setup` to
        change the setup.
        """
        return list(self._device_builder.setups.keys()
                    ) if self._device_builder is not None else []

    def switch_setup(self, new_setup):
        """
        This method switches the setup, which means that it replaces the currently
        simulated device with a new device, as defined by the setup.

        If any error occurs during setup switching it is logged and re-raised.

        :param new_setup: Name of the new setup to load.
        """
        try:
            self._device = self._device_builder.create_device(new_setup)
            self._adapters.set_device(self._device)
            self.log.info('Switched setup to \'%s\'', new_setup)
        except Exception as e:
            self.log.error(
                'Caught an error while trying to switch setups. Setup not switched, '
                'simulation continues: %s', e)
            raise

    def start(self):
        """
        Starts the simulation.
        """
        self.log.info('Starting simulation')

        self._running = True
        self._started = True
        self._stop_commanded = False

        if self._control_server is not None:
            self._control_server.start_server()

        self._adapters.connect()

        self._start_time = datetime.now()

        delta = 0.0

        while not self._stop_commanded:
            delta = self._process_cycle(delta)

        self._running = False
        self._started = False

        self._adapters.disconnect()

    def _process_cycle(self, delta):
        """
        Processes one cycle, which consists of one simulation cycle and processing
        of control server commands. The method measures how long all this takes
        and returns the elapsed time in seconds.

        :param delta: Elapsed time in last cycle, passed to simulation.
        :return: Elapsed time in this cycle.
        """
        start = datetime.now()

        self._process_simulation_cycle(delta)

        delta = seconds_since(start)

        return delta

    def _process_simulation_cycle(self, delta):
        """
        If the simulation is not paused, the device's process-method is
        called with the supplied delta, multiplied by the simulation speed.

        If the simulation is paused, the process sleeps for the duration
        of one cycle_delay.

        :param delta: Time delta passed to simulation.
        """
        self.log.debug('Cycle, dt=%s', delta)

        sleep(self._cycle_delay)

        if self._running:
            delta_simulation = delta * self._speed

            with self._adapters.device_lock:
                self._device.process(delta_simulation)

            if self._control_server:
                self._control_server.process()

            self._cycles += 1
            self._runtime += delta_simulation

    @property
    def cycle_delay(self):
        """
        Desired time between simulation cycles, this can not be negative.
        Use 0 for highest possible processing rate.
        """
        return self._cycle_delay

    @cycle_delay.setter
    def cycle_delay(self, delay):
        if delay < 0.0:
            raise ValueError('Cycle delay can not be negative.')

        self._cycle_delay = delay

        self.log.info('Changed cycle delay to %s', self._cycle_delay)

    @property
    def cycles(self):
        """
        Simulation cycles processed since start has been called.
        """
        return self._cycles

    @property
    def uptime(self):
        """
        Elapsed time in seconds since the simulation has been started.
        """
        if not self._started:
            return 0.0
        return seconds_since(self._start_time)

    @property
    def speed(self):
        """
        Simulation speed. Actual elapsed time is multiplied with this property
        to determine simulated time. Values greater than 1 increase the simulation
        speed, values between 1 and 0 decrease it. A speed of 0 effectively pauses
        the simulation.
        """
        return self._speed

    @speed.setter
    def speed(self, new_speed):
        if new_speed < 0:
            raise ValueError('Speed can not be negative.')

        self._speed = new_speed

        self.log.info('Changed speed to %s', self._speed)

    @property
    def runtime(self):
        """
        The accumulated simulation time. Whenever speed is different from 1, this
        progresses at a different rate than uptime.
        """
        return self._runtime

    def set_device_parameters(self, parameters):
        """
        Set multiple parameters of the simulated device "simultaneously". The passed
        parameter is assumed to be device parameter/value dict.
        The method only allows to set existing attributes. If there are invalid
        attribute names, the attributes are not updated, instead a RuntimeError
        is raised. The same happens if any of the parameters are methods, which
        can not be updated with this mechanisms.

        :param parameters: Dict of device attribute/values to update the device.
        """
        invalid_parameters = set(parameters.keys()) - set(
            x for x in dir(self._device)
            if not callable(getattr(self._device, x)))
        if invalid_parameters:
            raise RuntimeError(
                'The following parameters do not exist in the device or are methods: {}.'
                'Parameters not updated.'.format(invalid_parameters))

        with self._adapters.device_lock:
            for name, value in parameters.items():
                setattr(self._device, name, value)

        self.log.debug('Updated device parameters: %s', parameters)

    def pause(self):
        """
        Pause the simulation. Can only be called after start has been called.
        """
        if not self._running:
            raise RuntimeError('Can only pause a running simulation.')

        self.log.info('Pausing simulation')

        self._running = False

    def resume(self):
        """
        Resume a paused simulation. Can only be called after start
        and pause have been called.
        """
        if not self._started or self._running:
            raise RuntimeError('Can only resume a paused simulation.')

        self.log.info('Resuming simulation')

        self._running = True

    def stop(self):
        """
        Stops the simulation entirely.
        """

        self.log.warning('Stopping simulation')

        self._stop_commanded = True

    @property
    def is_started(self):
        """
        This property is true if the simulation has been started.
        """
        return self._started

    @property
    def is_paused(self):
        """
        True if the simulation is paused (implies that the simulation has been started).
        """
        return self._started and not self._running

    @property
    def control_server(self):
        """
        ControlServer-instance that exposes the object to remote machines. Can only
        be set before start has been called or on a running simulation if no
        control server was previously present. If the server is not running, it will be started
        after it has been set.
        """
        return self._control_server

    @control_server.setter
    def control_server(self, control_server):
        if self.is_started and self._control_server:
            raise RuntimeError(
                'Can not replace control server while simulation is running.')

        self._control_server = self._create_control_server(control_server)

        if self.is_started and self._control_server is not None:
            self._control_server.start_server()
Example #11
0
    def test_connect_disconnect_connected(self):
        collection = AdapterCollection(DummyAdapter("foo", running=False),
                                       DummyAdapter("bar", running=False))

        # no arguments connects everything
        collection.connect()

        self.assertDictEqual(collection.is_connected(), {
            "bar": True,
            "foo": True
        })
        self.assertTrue(collection.is_connected("bar"))
        self.assertTrue(collection.is_connected("foo"))

        collection.disconnect()

        self.assertDictEqual(collection.is_connected(), {
            "bar": False,
            "foo": False
        })
        self.assertFalse(collection.is_connected("bar"))
        self.assertFalse(collection.is_connected("foo"))

        collection.connect("foo")
        self.assertDictEqual(collection.is_connected(), {
            "bar": False,
            "foo": True
        })
        self.assertFalse(collection.is_connected("bar"))
        self.assertTrue(collection.is_connected("foo"))

        self.assertRaises(RuntimeError, collection.connect, "baz")
        self.assertRaises(RuntimeError, collection.disconnect, "baz")

        collection.disconnect()  # Clean up so that the test does not hang
class Simulation(object):
    """
    The Simulation class controls certain aspects of a device simulation,
    the most important one being time.

    Once :meth:`start` is called, the process-method of the device
    is called in regular intervals. The time between these calls is
    influenced by the cycle_delay property. Because of the way some
    network protocols work, the actual processing time can be
    longer or shorter, so cycle_delay should be seen as a guideline
    rather than a guaranteed parameter.

    In the simplest case, the actual time-delta between two cycles
    is passed to the simulated device so that it can update its internal
    state according to the elapsed time. It is however possible to set
    a simulation speed, which serves as a multiplier for this time.
    If the speed is set to 2 and 0.1 seconds pass between two cycles,
    the simulation is asked to simulate 0.2 seconds, and so on. Speed 0
    effectively stops all time dependent calculations in the
    simulated device.

    Another possibility to pause the simulation is the pause-method. After
    calling it, all processing in the device is suspended, while the communication
    adapters continue to work. This can be used to simulate that a device is "hanging".
    The simulation can be continued using the resume-method.

    A number of status properties provide information about the simulation.
    The total uptime (in actually elapsed time) can be obtained through the
    uptime-property, whereas the runtime-property contains the simulated time.
    The cycles-property indicates the total number of simulation cycles, which
    does not increase when the simulation is paused.

    Finally, the simulation can be stopped entirely with the stop-method.

    All functionality except for the start-method can be made available to remote
    computers via a :class:`ControlServer`-instance. The way to expose device and simulation
    is to pass a 'host:port'-string as the control_server argument,
    which will construct the control server. Simulation will try to start the
    control server using the start_server method.

    :param device: The simulated device.
    :param adapters: Adapters which expose the simulated device.
    :param device_builder: :class:`~lewis.core.devices.DeviceBuilder` instance to enable setup-
                           switching at runtime.
    :param control_server: 'host:port'-string to construct control server or None.
    """

    def __init__(self, device, adapters=(), device_builder=None, control_server=None):
        super(Simulation, self).__init__()

        self._device_builder = device_builder

        self._device = device
        self._adapters = AdapterCollection(*adapters)

        self._speed = 1.0  # Multiplier for delta t
        self._cycle_delay = 0.1  # Target time between cycles

        self._start_time = None  # Real time when the simulation started
        self._cycles = 0  # Number of cycles processed
        self._runtime = 0.0  # Total simulation time processed

        self._running = False
        self._started = False
        self._stop_commanded = False

        # Constructing the control server must be deferred until the end,
        # because the construction is not complete at this point
        self._control_server = None  # Just initialize to None and use property setter afterwards
        self._control_server_thread = None
        self.control_server = control_server

        self.log.debug(
            'Created simulation. Device type: %s, Protocol(s): %s, Possible setups for '
            'switching: %s, Control server: %s', device.__class__.__name__,
            ', '.join(self._adapters.protocols),
            ', '.join(device_builder.setups.keys()) if device_builder else None,
            control_server)

    def _create_control_server(self, control_server):
        if control_server is None:
            return None

        return ControlServer({
            'device': ExposedObject(
                self._device,
                exclude_inherited=True,
                lock=self._adapters.device_lock
            ),
            'simulation': ExposedObject(
                self,
                exclude=('start', 'control_server', 'log'),
                exclude_inherited=True
            ),
            'interface': ExposedObject(
                self._adapters,
                exclude=('device_lock', 'add_adapter', 'remove_adapter', 'handle', 'log'),
                exclude_inherited=True
            )},
            control_server)

    @property
    def setups(self):
        """
        A list of setups that are available. Use :meth:`switch_setup` to
        change the setup.
        """
        return list(self._device_builder.setups.keys()) if self._device_builder is not None else []

    def switch_setup(self, new_setup):
        """
        This method switches the setup, which means that it replaces the currently
        simulated device with a new device, as defined by the setup.

        If any error occurs during setup switching it is logged and re-raised.

        :param new_setup: Name of the new setup to load.
        """
        try:
            self._device = self._device_builder.create_device(new_setup)
            self._adapters.set_device(self._device)
            self.log.info('Switched setup to \'%s\'', new_setup)
        except Exception as e:
            self.log.error(
                'Caught an error while trying to switch setups. Setup not switched, '
                'simulation continues: %s', e)
            raise

    def start(self):
        """
        Starts the simulation.
        """
        self.log.info('Starting simulation')

        self._running = True
        self._started = True
        self._stop_commanded = False

        self._start_control_server()

        self._adapters.connect()

        self._start_time = datetime.now()

        delta = 0.0

        while not self._stop_commanded:
            delta = self._process_cycle(delta)

        self._running = False
        self._started = False

        self.log.info('Simulation has ended.')

    def _start_control_server(self):
        if self._control_server is not None and self._control_server_thread is None:
            def control_server_loop():
                self._control_server.start_server()

                while not self._stop_commanded:
                    self._control_server.process(blocking=True)

                self.log.info('Stopped processing control server commands, ending thread.')

            self._control_server_thread = Thread(target=control_server_loop)
            self._control_server_thread.start()

    def _stop_control_server(self):
        if self._control_server_thread is not None:
            self._control_server_thread.join(timeout=1.0)
            self._control_server_thread = None

    def _process_cycle(self, delta):
        """
        Processes one cycle, which consists of one simulation cycle and processing
        of control server commands. The method measures how long all this takes
        and returns the elapsed time in seconds.

        :param delta: Elapsed time in last cycle, passed to simulation.
        :return: Elapsed time in this cycle.
        """
        start = datetime.now()

        self._process_simulation_cycle(delta)

        delta = seconds_since(start)

        return delta

    def _process_simulation_cycle(self, delta):
        """
        If the simulation is not paused, the device's process-method is
        called with the supplied delta, multiplied by the simulation speed.

        If the simulation is paused, the process sleeps for the duration
        of one cycle_delay.

        :param delta: Time delta passed to simulation.
        """
        self.log.debug('Cycle, dt=%s', delta)

        sleep(self._cycle_delay)

        if self._running:
            delta_simulation = delta * self._speed

            with self._adapters.device_lock:
                self._device.process(delta_simulation)

            self._cycles += 1
            self._runtime += delta_simulation

    @property
    def cycle_delay(self):
        """
        Desired time between simulation cycles, this can not be negative.
        Use 0 for highest possible processing rate.
        """
        return self._cycle_delay

    @cycle_delay.setter
    def cycle_delay(self, delay):
        if delay < 0.0:
            raise ValueError('Cycle delay can not be negative.')

        self._cycle_delay = delay

        self.log.info('Changed cycle delay to %s', self._cycle_delay)

    @property
    def cycles(self):
        """
        Simulation cycles processed since start has been called.
        """
        return self._cycles

    @property
    def uptime(self):
        """
        Elapsed time in seconds since the simulation has been started.
        """
        if not self._started:
            return 0.0
        return seconds_since(self._start_time)

    @property
    def speed(self):
        """
        Simulation speed. Actual elapsed time is multiplied with this property
        to determine simulated time. Values greater than 1 increase the simulation
        speed, values between 1 and 0 decrease it. A speed of 0 effectively pauses
        the simulation.
        """
        return self._speed

    @speed.setter
    def speed(self, new_speed):
        if new_speed < 0:
            raise ValueError('Speed can not be negative.')

        self._speed = new_speed

        self.log.info('Changed speed to %s', self._speed)

    @property
    def runtime(self):
        """
        The accumulated simulation time. Whenever speed is different from 1, this
        progresses at a different rate than uptime.
        """
        return self._runtime

    def set_device_parameters(self, parameters):
        """
        Set multiple parameters of the simulated device "simultaneously". The passed
        parameter is assumed to be device parameter/value dict.
        The method only allows to set existing attributes. If there are invalid
        attribute names, the attributes are not updated, instead a RuntimeError
        is raised. The same happens if any of the parameters are methods, which
        can not be updated with this mechanisms.

        :param parameters: Dict of device attribute/values to update the device.
        """
        invalid_parameters = set(parameters.keys()) - set(
            x for x in dir(self._device) if not callable(getattr(self._device, x)))
        if invalid_parameters:
            raise RuntimeError(
                'The following parameters do not exist in the device or are methods: {}.'
                'Parameters not updated.'.format(invalid_parameters))

        with self._adapters.device_lock:
            for name, value in parameters.items():
                setattr(self._device, name, value)

        self.log.debug('Updated device parameters: %s', parameters)

    def pause(self):
        """
        Pause the simulation. Can only be called after start has been called.
        """
        if not self._running:
            raise RuntimeError('Can only pause a running simulation.')

        self.log.info('Pausing simulation')

        self._running = False

    def resume(self):
        """
        Resume a paused simulation. Can only be called after start
        and pause have been called.
        """
        if not self._started or self._running:
            raise RuntimeError('Can only resume a paused simulation.')

        self.log.info('Resuming simulation')

        self._running = True

    def stop(self):
        """
        Stops the simulation entirely.
        """
        if self.is_started:
            self.log.warning('Stopping simulation')

            self._stop_commanded = True

            self._stop_control_server()
            self._adapters.disconnect()

    @property
    def is_started(self):
        """
        This property is true if the simulation has been started.
        """
        return self._started

    @property
    def is_paused(self):
        """
        True if the simulation is paused (implies that the simulation has been started).
        """
        return self._started and not self._running

    @property
    def control_server(self):
        """
        ControlServer-instance that exposes the object to remote machines. Can only
        be set before start has been called or on a running simulation if no
        control server was previously present. If the server is not running, it will be started
        after it has been set.
        """
        return self._control_server

    @control_server.setter
    def control_server(self, control_server):
        if self.is_started and self._control_server:
            raise RuntimeError('Can not replace control server while simulation is running.')

        self._control_server = self._create_control_server(control_server)

        if self.is_started and self._control_server is not None:
            self._control_server.start_server()
Example #13
0
    def test_connect_disconnect_connected(self):
        collection = AdapterCollection(
            DummyAdapter('foo', running=False), DummyAdapter('bar', running=False))

        # no arguments connects everything
        collection.connect()

        self.assertDictEqual(collection.is_connected(), {'bar': True, 'foo': True})
        self.assertTrue(collection.is_connected('bar'))
        self.assertTrue(collection.is_connected('foo'))

        collection.disconnect()

        self.assertDictEqual(collection.is_connected(), {'bar': False, 'foo': False})
        self.assertFalse(collection.is_connected('bar'))
        self.assertFalse(collection.is_connected('foo'))

        collection.connect('foo')
        self.assertDictEqual(collection.is_connected(), {'bar': False, 'foo': True})
        self.assertFalse(collection.is_connected('bar'))
        self.assertTrue(collection.is_connected('foo'))

        self.assertRaises(RuntimeError, collection.connect, 'baz')
        self.assertRaises(RuntimeError, collection.disconnect, 'baz')

        collection.disconnect()  # Clean up so that the test does not hang