from typing import Any, Callable, Dict, Mapping, Sequence, TypeVar, Union
import numpy as np
from annotypes import NO_DEFAULT, Anno, Array
from scanpointgenerator import CompoundGenerator
from malcolm.compat import OrderedDict
from malcolm.core import VMeta
from malcolm.modules import builtin
from .infos import ConfigureParamsInfo, Info, ParameterTweakInfo
T = TypeVar("T")
with Anno("The Infos returned from other Parts"):
APartInfo = Mapping[str, Array[Info]]
UPartInfo = Union[APartInfo, Mapping[str, Sequence[Info]]]
with Anno("Infos about current Part status to be passed to other parts"):
AInfos = Union[Array[Info]]
with Anno("Generator instance providing specification for scan"):
AGenerator = Union[CompoundGenerator]
with Anno("List of axes in inner dimension of generator that should be moved"):
AAxesToMove = Union[Array[str]]
UAxesToMove = Union[AAxesToMove, Sequence[str]]
with Anno("List of points at which the run will return in Armed state"):
ABreakpoints = Union[Array[np.int32]]
UBreakpoints = Union[ABreakpoints, Sequence[int]]
with Anno("Parameters that need to be changed to make them compatible"):
AParameterTweakInfos = Union[Array[ParameterTweakInfo]]
UInfos = Union[AInfos, Sequence[Info], Info, None]
UParameterTweakInfos = Union[
AParameterTweakInfos, Sequence[ParameterTweakInfo], ParameterTweakInfo, None
]
with Anno("Directory to write data to"):
AFileDir = str
with Anno("Argument for fileTemplate, normally filename without extension"):
AFormatName = str
with Anno(
"""Printf style template to generate filename relative to fileDir.
Arguments are:
1) %s: the value of formatName"""
):
AFileTemplate = str
with Anno("The demand exposure time of this scan, 0 for the maximum possible"):
AExposure = float
# Pull re-used annotypes into our namespace in case we are subclassed
APart = builtin.hooks.APart
AContext = builtin.hooks.AContext
# also bring in superclass which a subclasses may refer to
ControllerHook = builtin.hooks.ControllerHook
def check_array_info(anno: Array, value: Any) -> T:
assert anno.is_array and issubclass(
anno.typ, Info
), f"Expected Anno wrapping Array[something], got {anno}"
ret = anno(value)
bad = [x for x in ret if not isinstance(x, anno.typ)]
assert not bad, f"Passed objects {bad} that are not of type {anno.typ}"
return ret
[docs]class ValidateHook(ControllerHook[UParameterTweakInfos]):
"""Called at validate() to check parameters are valid"""
# Allow CamelCase for axesToMove as it must match ConfigureParams which
# will become a configure argument, so must be camelCase to match EPICS
# normative types conventions
# noinspection PyPep8Naming
def __init__(
self,
part: APart,
context: AContext,
part_info: UPartInfo,
generator: AGenerator,
axesToMove: AAxesToMove,
breakpoints: ABreakpoints,
**kwargs: Any,
) -> None:
super().__init__(
part,
context,
part_info=part_info,
generator=generator,
axesToMove=axesToMove,
breakpoints=breakpoints,
**kwargs,
)
[docs] def validate_return(self, ret: UParameterTweakInfos) -> AParameterTweakInfos:
"""Check that all returned infos are ParameterTweakInfo that list
the parameters that need to be changed to make them compatible with
this part. ValidateHook will be re-run with the modified parameters."""
return check_array_info(AParameterTweakInfos, ret)
[docs]class ReportStatusHook(ControllerHook[UInfos]):
"""Called before Validate, Configure, PostRunArmed and Seek hooks to report
the current configuration of all parts"""
[docs] def validate_return(self, ret: UInfos) -> AInfos:
"""Check that all parts return Info objects relevant to other parts"""
return check_array_info(AInfos, ret)
with Anno("Number of steps already completed"):
ACompletedSteps = int
with Anno("Number of steps we should configure for"):
AStepsToDo = int
[docs]class PostConfigureHook(ControllerHook[None]):
"""Called at the end of configure() to store configuration info calculated
in the Configure hook"""
def __init__(self, part: APart, context: AContext, part_info: APartInfo) -> None:
super().__init__(part, context, part_info=part_info)
[docs]class PreRunHook(ControllerHook[None]):
"""Called at the start of run()"""
[docs]class RunHook(ControllerHook[None]):
"""Called at run() to start the configured steps running"""
[docs]class PostRunArmedHook(ControllerHook[None]):
"""Called at the end of run() when there are more steps to be run"""
# Allow CamelCase for axesToMove as it must match ConfigureParams which
# will become a configure argument, so must be camelCase to match EPICS
# normative types conventions
# noinspection PyPep8Naming
def __init__(
self,
part: APart,
context: AContext,
completed_steps: ACompletedSteps,
steps_to_do: AStepsToDo,
part_info: UPartInfo,
generator: AGenerator,
axesToMove: AAxesToMove,
**kwargs: Any,
) -> None:
super().__init__(
part,
context,
completed_steps=completed_steps,
steps_to_do=steps_to_do,
part_info=part_info,
generator=generator,
axesToMove=axesToMove,
**kwargs,
)
[docs]class PostRunReadyHook(ControllerHook[None]):
"""Called at the end of run() when there are no more steps to be run"""
[docs]class PauseHook(ControllerHook[None]):
"""Called at pause() to pause the current scan before Seek is called"""
[docs]class SeekHook(ControllerHook[None]):
"""Called at seek() or at the end of pause() to reconfigure for a different
number of completed_steps"""
# Allow CamelCase for axesToMove as it must match ConfigureParams which
# will become a configure argument, so must be camelCase to match EPICS
# normative types conventions
# noinspection PyPep8Naming
def __init__(
self,
part: APart,
context: AContext,
completed_steps: ACompletedSteps,
steps_to_do: AStepsToDo,
part_info: APartInfo,
generator: AGenerator,
axesToMove: AAxesToMove,
**kwargs: Any,
) -> None:
super().__init__(
part,
context,
completed_steps=completed_steps,
steps_to_do=steps_to_do,
part_info=part_info,
generator=generator,
axesToMove=axesToMove,
**kwargs,
)
[docs]class AbortHook(ControllerHook[None]):
"""Called at abort() to stop the current scan"""