Skip to content

Commit 76ec331

Browse files
committed
ControllerDevice: implement new ABC for controler devices (issue #111)
* NEWS: make note of new ABC. * microscope/devices.py: new ControllerDevice class. * microscope/deviceserver.py: if the device being served is a ControllerDevice, also register its controlled devices with Pyro.
1 parent 83f1256 commit 76ec331

File tree

3 files changed

+64
-4
lines changed

3 files changed

+64
-4
lines changed

NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ Version 0.3.0 (upcoming)
2727

2828
* Changes to device ABCs:
2929

30+
* New ABC `ControllerDevice` for controller devices.
31+
3032
* Laser devices:
3133

3234
* New abstract methods:

microscope/devices.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1109,3 +1109,39 @@ def set_position(self, position):
11091109

11101110
def get_filters(self):
11111111
return [(k,v) for k,v in self._filters.items()]
1112+
1113+
1114+
class ControllerDevice(Device, metaclass=abc.ABCMeta):
1115+
"""Device that controls multiple devices.
1116+
1117+
Controller devices usually control multiple stage devices,
1118+
typically a XY and Z stage, a filterwheel, and a light source.
1119+
Controller devices also include multi light source engines.
1120+
1121+
Each of the controlled devices requires a name. The choice of
1122+
name and its documentation is left to the concrete class.
1123+
1124+
Initialising and shutting down a controller device must initialise
1125+
and shutdown the controlled devices. Concrete classes should be
1126+
careful to prevent that the shutdown of a controlled device does
1127+
not shutdown the controller and the other controlled devices.
1128+
This might require that controlled devices do nothing as part of
1129+
their shutdown and initialisation.
1130+
1131+
"""
1132+
1133+
def initialize(self) -> None:
1134+
super().initialize()
1135+
for d in self.devices.values():
1136+
d.initialize()
1137+
1138+
@property
1139+
@abc.abstractmethod
1140+
def devices(self) -> typing.Mapping[str, Device]:
1141+
"""Map of names to the controlled devices."""
1142+
raise NotImplementedError()
1143+
1144+
def _on_shutdown(self) -> None:
1145+
for d in self.devices.values():
1146+
d.shutdown()
1147+
super()._on_shutdown()

microscope/deviceserver.py

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
import Pyro4
3838

39-
from microscope.devices import FloatingDeviceMixin
39+
import microscope.devices
4040

4141
# Pyro configuration. Use pickle because it can serialize numpy ndarrays.
4242
Pyro4.config.SERIALIZERS_ACCEPTED.add('pickle')
@@ -147,7 +147,7 @@ def run(self):
147147
time.sleep(5)
148148
else:
149149
break
150-
if (isinstance(self._device, FloatingDeviceMixin)
150+
if (isinstance(self._device, microscope.devices.FloatingDeviceMixin)
151151
and len(self._id_to_host) > 1):
152152
uid = str(self._device.get_id())
153153
if uid not in self._id_to_host or uid not in self._id_to_port:
@@ -168,11 +168,33 @@ def run(self):
168168
# Run the Pyro daemon in a separate thread so that we can do
169169
# clean shutdown under Windows.
170170
pyro_daemon.register(self._device, type(self._device).__name__)
171+
if isinstance(self._device, microscope.devices.ControllerDevice):
172+
# AUTOPROXY should be enabled by default. If we find it
173+
# disabled, there must be a reason why, so raise an error
174+
# instead of silently enabling it.
175+
if not Pyro4.config.AUTOPROXY:
176+
raise RuntimeError('serving of a ControllerDevice requires'
177+
' Pyro4 AUTOPROXY option enabled')
178+
179+
# Autoproxy does not work with marshal serializer.
180+
Pyro4.config.SERIALIZERS_ACCEPTED.discard('marshal')
181+
182+
for sub_device in self._device.devices.values():
183+
# FIXME: by the time we do this the device has already
184+
# been created and initialised and that won't be
185+
# logged. We need to rethink having a log per device
186+
# (issue #110)
187+
sub_device._logger.addHandler(stderr_handler)
188+
sub_device._logger.addHandler(log_handler)
189+
sub_device._logger.addFilter(Filter())
190+
# This requires
191+
pyro_daemon.register(sub_device)
192+
171193
pyro_thread = Thread(target = pyro_daemon.requestLoop)
172194
pyro_thread.daemon = True
173195
pyro_thread.start()
174196
logger.info('Serving %s' % pyro_daemon.uriFor(self._device))
175-
if isinstance(self._device, FloatingDeviceMixin):
197+
if isinstance(self._device, microscope.devices.FloatingDeviceMixin):
176198
logger.info('Device UID on port %s is %s' % (port, self._device.get_id()))
177199
# Wait for termination event. We should just be able to call
178200
# wait() on the exit_event, but this causes issues with locks
@@ -239,7 +261,7 @@ def term_func(sig, frame):
239261
# Keep track of how many of these classes we have set up.
240262
# Some SDKs need this information to index devices.
241263
count = 0
242-
if issubclass(cls, FloatingDeviceMixin):
264+
if issubclass(cls, microscope.devices.FloatingDeviceMixin):
243265
# Need to provide maps of uid to host and port.
244266
uid_to_host = {}
245267
uid_to_port = {}

0 commit comments

Comments
 (0)