2222an :mod:`Adapter <lewis.adapters>`).
2323"""
2424
25+ from collections .abc import Collection , Sequence
2526from datetime import datetime
2627from threading import Thread
2728from time import sleep
29+ from typing import Any
2830
29- from lewis .core .adapters import AdapterCollection
31+ from lewis .core .adapters import Adapter , AdapterCollection
3032from lewis .core .control_server import ControlServer , ExposedObject
31- from lewis .core .devices import DeviceRegistry
33+ from lewis .core .devices import DeviceBuilder , DeviceRegistry
3234from lewis .core .logging import has_log
3335from lewis .core .utils import seconds_since
36+ from lewis .devices import Device
3437
3538
3639@has_log
@@ -81,7 +84,13 @@ class Simulation:
8184 :param control_server: 'host:port'-string to construct control server or None.
8285 """
8386
84- def __init__ (self , device , adapters = (), device_builder = None , control_server = None ) -> None :
87+ def __init__ (
88+ self ,
89+ device : Device ,
90+ adapters : Sequence [Adapter ] = (),
91+ device_builder : DeviceBuilder | None = None ,
92+ control_server : str | None = None ,
93+ ) -> None :
8594 super (Simulation , self ).__init__ ()
8695
8796 self ._device_builder = device_builder
@@ -102,7 +111,9 @@ def __init__(self, device, adapters=(), device_builder=None, control_server=None
102111
103112 # Constructing the control server must be deferred until the end,
104113 # because the construction is not complete at this point
105- self ._control_server = None # Just initialize to None and use property setter afterwards
114+ self ._control_server : ControlServer | None = (
115+ None # Just initialize to None and use property setter afterwards
116+ )
106117 self ._control_server_thread = None
107118 self .control_server = control_server
108119
@@ -115,7 +126,7 @@ def __init__(self, device, adapters=(), device_builder=None, control_server=None
115126 control_server ,
116127 )
117128
118- def _create_control_server (self , control_server ) :
129+ def _create_control_server (self , control_server : str | None ) -> ControlServer | None :
119130 if control_server is None :
120131 return None
121132
@@ -147,14 +158,14 @@ def _create_control_server(self, control_server):
147158 )
148159
149160 @property
150- def setups (self ):
161+ def setups (self ) -> list [ str ] :
151162 """
152163 A list of setups that are available. Use :meth:`switch_setup` to
153164 change the setup.
154165 """
155166 return list (self ._device_builder .setups .keys ()) if self ._device_builder is not None else []
156167
157- def switch_setup (self , new_setup ) -> None :
168+ def switch_setup (self , new_setup : str ) -> None :
158169 """
159170 This method switches the setup, which means that it replaces the currently
160171 simulated device with a new device, as defined by the setup.
@@ -164,7 +175,7 @@ def switch_setup(self, new_setup) -> None:
164175 :param new_setup: Name of the new setup to load.
165176 """
166177 try :
167- self ._device = self ._device_builder .create_device (new_setup )
178+ self ._device = self ._device_builder .create_device (new_setup ) # pyright: ignore reportOptionalMemberAccess
168179 self ._adapters .set_device (self ._device )
169180 self .log .info ("Switched setup to '%s'" , new_setup )
170181 except Exception as e :
@@ -196,31 +207,34 @@ def start(self) -> None:
196207 while not self ._stop_commanded :
197208 delta = self ._process_cycle (delta )
198209
210+ self ._wait_for_control_server_to_stop ()
211+
199212 self ._running = False
200213 self ._started = False
201214
202215 self .log .info ("Simulation has ended." )
203216
204217 def _start_control_server (self ) -> None :
205- if self ._control_server is not None and self ._control_server_thread is None :
218+ control_server = self ._control_server
219+ if control_server is not None and self ._control_server_thread is None :
206220
207221 def control_server_loop () -> None :
208- self . _control_server .start_server ()
222+ control_server .start_server ()
209223
210224 while not self ._stop_commanded :
211- self . _control_server .process (blocking = True )
225+ control_server .process (blocking = True )
212226
213227 self .log .info ("Stopped processing control server commands, ending thread." )
214228
215229 self ._control_server_thread = Thread (target = control_server_loop )
216230 self ._control_server_thread .start ()
217231
218- def _stop_control_server (self ) -> None :
232+ def _wait_for_control_server_to_stop (self ) -> None :
219233 if self ._control_server_thread is not None :
220234 self ._control_server_thread .join (timeout = 1.0 )
221235 self ._control_server_thread = None
222236
223- def _process_cycle (self , delta ) :
237+ def _process_cycle (self , delta : float ) -> float :
224238 """
225239 Processes one cycle, which consists of one simulation cycle and processing
226240 of control server commands. The method measures how long all this takes
@@ -237,7 +251,7 @@ def _process_cycle(self, delta):
237251
238252 return delta
239253
240- def _process_simulation_cycle (self , delta ) -> None :
254+ def _process_simulation_cycle (self , delta : float ) -> None :
241255 """
242256 If the simulation is not paused, the device's process-method is
243257 called with the supplied delta, multiplied by the simulation speed.
@@ -261,15 +275,15 @@ def _process_simulation_cycle(self, delta) -> None:
261275 self ._runtime += delta_simulation
262276
263277 @property
264- def cycle_delay (self ):
278+ def cycle_delay (self ) -> float :
265279 """
266280 Desired time between simulation cycles, this can not be negative.
267281 Use 0 for highest possible processing rate.
268282 """
269283 return self ._cycle_delay
270284
271285 @cycle_delay .setter
272- def cycle_delay (self , delay ) -> None :
286+ def cycle_delay (self , delay : float ) -> None :
273287 if delay < 0.0 :
274288 raise ValueError ("Cycle delay can not be negative." )
275289
@@ -278,14 +292,14 @@ def cycle_delay(self, delay) -> None:
278292 self .log .info ("Changed cycle delay to %s" , self ._cycle_delay )
279293
280294 @property
281- def cycles (self ):
295+ def cycles (self ) -> int :
282296 """
283297 Simulation cycles processed since start has been called.
284298 """
285299 return self ._cycles
286300
287301 @property
288- def uptime (self ):
302+ def uptime (self ) -> float :
289303 """
290304 Elapsed time in seconds since the simulation has been started.
291305 """
@@ -294,7 +308,7 @@ def uptime(self):
294308 return seconds_since (self ._start_time )
295309
296310 @property
297- def speed (self ):
311+ def speed (self ) -> float :
298312 """
299313 Simulation speed. Actual elapsed time is multiplied with this property
300314 to determine simulated time. Values greater than 1 increase the simulation
@@ -304,7 +318,7 @@ def speed(self):
304318 return self ._speed
305319
306320 @speed .setter
307- def speed (self , new_speed ) -> None :
321+ def speed (self , new_speed : float ) -> None :
308322 if new_speed < 0 :
309323 raise ValueError ("Speed can not be negative." )
310324
@@ -313,14 +327,14 @@ def speed(self, new_speed) -> None:
313327 self .log .info ("Changed speed to %s" , self ._speed )
314328
315329 @property
316- def runtime (self ):
330+ def runtime (self ) -> float :
317331 """
318332 The accumulated simulation time. Whenever speed is different from 1, this
319333 progresses at a different rate than uptime.
320334 """
321335 return self ._runtime
322336
323- def set_device_parameters (self , parameters ) -> None :
337+ def set_device_parameters (self , parameters : dict [ str , Any ] ) -> None :
324338 """
325339 Set multiple parameters of the simulated device "simultaneously". The passed
326340 parameter is assumed to be device parameter/value dict.
@@ -377,26 +391,24 @@ def stop(self) -> None:
377391 self .log .warning ("Stopping simulation" )
378392
379393 self ._stop_commanded = True
380-
381- self ._stop_control_server ()
382394 self ._adapters .disconnect ()
383395
384396 @property
385- def is_started (self ):
397+ def is_started (self ) -> bool :
386398 """
387399 This property is true if the simulation has been started.
388400 """
389401 return self ._started
390402
391403 @property
392- def is_paused (self ):
404+ def is_paused (self ) -> bool :
393405 """
394406 True if the simulation is paused (implies that the simulation has been started).
395407 """
396408 return self ._started and not self ._running
397409
398410 @property
399- def control_server (self ):
411+ def control_server (self ) -> ControlServer | None :
400412 """
401413 ControlServer-instance that exposes the object to remote machines. Can only
402414 be set before start has been called or on a running simulation if no
@@ -406,7 +418,7 @@ def control_server(self):
406418 return self ._control_server
407419
408420 @control_server .setter
409- def control_server (self , control_server ) -> None :
421+ def control_server (self , control_server : str | None ) -> None :
410422 if self .is_started and self ._control_server :
411423 raise RuntimeError ("Can not replace control server while simulation is running." )
412424
@@ -437,19 +449,25 @@ class SimulationFactory:
437449 .. warning:: This class is meant for internal use at the moment and may change frequently.
438450 """
439451
440- def __init__ (self , devices_package ) -> None :
452+ def __init__ (self , devices_package : str ) -> None :
441453 self ._reg = DeviceRegistry (devices_package )
442454
443455 @property
444- def devices (self ):
456+ def devices (self ) -> Collection [ str ] :
445457 """Names of available devices."""
446458 return self ._reg .devices
447459
448- def get_protocols (self , device ) :
460+ def get_protocols (self , device : str ) -> list [ str ] :
449461 """Returns a list of available protocols for the specified device."""
450462 return self ._reg .device_builder (device ).protocols
451463
452- def create (self , device , setup = None , protocols = None , control_server = None ):
464+ def create (
465+ self ,
466+ device : str ,
467+ setup : str | None = None ,
468+ protocols : dict [str , dict [str , Any ]] | None = None ,
469+ control_server : str | None = None ,
470+ ) -> Simulation :
453471 """
454472 Creates a :class:`Simulation` according to the supplied parameters.
455473
@@ -463,22 +481,22 @@ def create(self, device, setup=None, protocols=None, control_server=None):
463481 """
464482
465483 device_builder = self ._reg .device_builder (device )
466- device = device_builder .create_device (setup )
484+ device_instance = device_builder .create_device (setup )
467485
468486 adapters = []
469487
470488 if protocols is not None :
471489 for protocol , options in protocols .items ():
472490 interface = device_builder .create_interface (protocol )
473- interface .device = device
491+ interface .device = device_instance
474492
475493 adapter = interface .adapter (options = options or {})
476494 adapter .interface = interface
477495
478496 adapters .append (adapter )
479497
480498 return Simulation (
481- device = device ,
499+ device = device_instance ,
482500 adapters = adapters ,
483501 device_builder = device_builder ,
484502 control_server = control_server ,
0 commit comments