Files
controller-wisetopdataserver/python/biopro/exp_pro/__init__.py
T
2021-12-20 14:52:55 +08:00

490 lines
12 KiB
Python

"""This module just define the biopro.exp_pro module interface.
The actual implement was put at another module
"""
import abc
import datetime
from typing import Optional, Any, overload, List, Dict, Union, Tuple, TypeVar
from biopro.util.promise import *
STATUS_INIT = 0
STATUS_SETUP = 1
STATUS_READY = 2
STATUS_START = 3
STATUS_SUSPEND = 4 # suspend
STATUS_COMPLETE = 10 # normal exit
STATUS_INTERRUPTED = 11 # interrupted
STATUS_ERROR = -1 # error exit
STATUS_UNKNOWN = -2 # scheduler not init
ADDRESS = Tuple[int, int, int, int, int, int]
T = TypeVar('T')
R = TypeVar('R')
class DeviceInfo(metaclass=abc.ABCMeta):
"""Device Information"""
__slots__ = ()
@property
@abc.abstractmethod
def device_id(self) -> Optional[int]:
"""device number."""
pass
@property
@abc.abstractmethod
def device_name(self) -> str:
"""device name."""
pass
@property
@abc.abstractmethod
def mac_address(self) -> ADDRESS:
"""device max address"""
pass
class ExpApi(metaclass=abc.ABCMeta):
"""Experimental protocol API, make user can access/control the controller to do something.
"""
@abc.abstractmethod
def log_verbose(self, *message: Any):
"""logging message at verbose level"""
pass
@abc.abstractmethod
def log_info(self, *message: Any):
"""logging message at info level"""
pass
@abc.abstractmethod
def log_warn(self, *message: Any):
"""logging message at warn level"""
pass
@property
@abc.abstractmethod
def user(self) -> str:
"""get current user name"""
pass
@property
@abc.abstractmethod
def root_directory(self) -> str:
pass
@property
@abc.abstractmethod
def use_directory(self) -> str:
pass
@abc.abstractmethod
def change_directory(self, path: str) -> str:
pass
@abc.abstractmethod
def current_timestamp(self) -> int:
"""get current timestamp.
:return: timestamp [s]
"""
pass
def current_datetime(self) -> datetime.datetime:
"""get current datetime
:return:
"""
return datetime.datetime.fromtimestamp(self.current_timestamp())
@abc.abstractmethod
def schedule_time(self, timestamp: Union[float, datetime.datetime]):
"""set next wake up time at *timestamp*
:param timestamp: timestamp [s] or datetime or promise
"""
pass
@abc.abstractmethod
def schedule_sleep(self, sleep: Union[float, datetime.timedelta]):
"""set next wake up time after *sleep*
:param sleep:
"""
pass
@abc.abstractmethod
def make_device_info(self, *,
device_name: str = None,
mac_address: Union[str, ADDRESS] = None) -> DeviceInfo:
"""create a device info.
:param device_name: device name set in the user repository, alias allow
:param mac_address: device mac address
:return: device information
"""
pass
@abc.abstractmethod
def found_device(self) -> List[DeviceInfo]:
"""get scanned device.
:return: list of device response information
"""
pass
@abc.abstractmethod
def connected_device(self) -> List[DeviceInfo]:
"""get connected device.
:return: list of device information
"""
pass
@abc.abstractmethod
def ensure_device(self, *device_list: DeviceInfo) -> bool:
"""ensure device found and connected.
**In STATUS_INIT state**
It only check device connected and gain the lock.
**In STATUS_SETUP state**
It will set the device number according the *device_list* index, and make the parameter *device*
in some method available.
It will try to gain the experimental lock for user. (not implemented yet)
:param device_list: device list
"""
pass
@abc.abstractmethod
def all_device(self) -> List[DeviceInfo]:
"""all locked device, set by `ensure_device`_ .
:return: device used by this experimental
"""
pass
@abc.abstractmethod
def get_device(self, device: int) -> DeviceInfo:
"""
:param device: device number
:return: device used by this experimental
"""
pass
@abc.abstractmethod
def release_device(self, device_list: Optional[List[DeviceInfo]] = None, disconnect=True):
"""release locked device.
:param device_list: release device, None for all
:param disconnect: if True, also disconnect the device.
"""
pass
@abc.abstractmethod
def apply_setup(self, name: str):
"""apply experimental setup from user setting.
It equals to do `ensure_device`_ and `device_set_parameter`_
"""
pass
@abc.abstractmethod
def device_get_parameter(self, device: Union[int, DeviceInfo], parameter: str) -> Any:
"""get the *parameter* from the *device*
:param device: device number
:param parameter: parameter name
:return: parameter P value
"""
pass
@overload
def device_set_parameter(self, device: Union[int, DeviceInfo], parameter: Dict[str, Any]):
pass
@overload
def device_set_parameter(self, device: Union[int, DeviceInfo], parameter: str):
pass
@overload
def device_set_parameter(self, device: Union[int, DeviceInfo], p: str, v: Any):
pass
@overload
def device_set_parameter(self,
device: Union[int, DeviceInfo],
p1: str, v1: Any,
p2: str, v2: Any):
pass
@overload
def device_set_parameter(self,
device: Union[int, DeviceInfo],
p1: str, v1: Any,
p2: str, v2: Any,
p3: str, v3: Any):
pass
@abc.abstractmethod
def device_set_parameter(self, device: Union[int, DeviceInfo], *parameter):
"""set device parameter.
the *parameter* could be:
1. **str** configuration name store in user repository.
2. **Dict[str, Any]** parameter table
3. **str, value, ...** parameter sequence.
**error raise**
1. general case for device
* `RuntimeError`_ device not found
* `RuntimeError`_ device un-locked
#. general case for parameter setting
see :func:`biopro.devlib.library.DeviceParameter.set_parameter` for more detail
#. for **str** configuration name case
* `RuntimeError`_ not a meta file
* `FileNotFoundError`_
#. for **str, value, ...** parameter sequence.
* `ValueError`_ illegal parameter count
* `ValueError`_ illegal parameter name type
:param device: device number
:param parameter:
:return:
"""
pass
@abc.abstractmethod
def device_to_value(self, device: Union[int, DeviceInfo], parameter: str, value: Any) -> Any:
"""cast P value to V value for device parameter.
:param device: device number
:param parameter: parameter name
:param value: P value
:return: V value
"""
pass
@abc.abstractmethod
def device_to_parameter(self, device: Union[int, DeviceInfo], parameter: str, value: Any) -> Any:
"""cast V value to P value for device parameter.
:param device: device number
:param parameter: parameter name
:param value: V value
:return: P value
"""
pass
@abc.abstractmethod
def device_call_instruction(self, device: Union[int, DeviceInfo], instruction: str):
"""device invoke instruction.
:param device: device number
:param instruction: instruction
"""
pass
@abc.abstractmethod
def device_use_filepath(self, device: Union[int, DeviceInfo]) -> Optional[str]:
"""get device saving filepath
:param device: device number
:return: filepath
"""
pass
@abc.abstractmethod
def device_set_filepath(self, device: Union[int, DeviceInfo], filename: str) -> str:
"""set device saving filepath
:param device: device number
:param filename: filename
:return: filepath
"""
pass
@abc.abstractmethod
def device_unset_filepath(self, device: Union[int, DeviceInfo]) -> Optional[str]:
"""unset/clean device saving filepath
:param device: device number
:return: filepath
"""
pass
@abc.abstractmethod
def set_complete(self):
"""mark protocol complete. """
pass
@abc.abstractmethod
def set_error(self, message: str):
"""mark protocol terminated with error. """
pass
@abc.abstractmethod
def file_export(self,
ftype: str,
*filepath: Union[str, DeviceInfo],
**options) -> Promise[List[Union[str, DeviceInfo]]]:
"""export file.
:param ftype: export file type
:param filepath: source filepath or device info
:param options: export options
:return: promise of output filepath
"""
pass
@abc.abstractmethod
def file_archive(self,
filename: str,
*filepath: Union[str, DeviceInfo],
ftype: Optional[str] = None) -> Promise[str]:
"""archive/compress file.
if *filepath* is empty, use the exported path for all device.
:param filename: output filename
:param filepath: source filepath or device info
:param ftype: archive file type
:return: promise of output filepath
"""
pass
DEVICE = str
'''available device name list'''
SETUP = str
'''available setup name list'''
CONFIG = str
'''available configuration name list'''
META = dict
'''meta file configuration'''
FILE_EXPORT = str
'''file export type'''
FILE_ARCHIVE = str
'''file archive type'''
FILE_NAME = str
'''file name'''
FILE_PATH = str
'''file path'''
# noinspection PyUnusedLocal
def exp_options(f, *,
name: str = None,
desp: str = None,
opt_type=None,
readonly=False):
"""**decorator**, mark a function as a experimental protocol parameter.
This decorator will return a property. It will hide the original function *f*
into the property getter.
This property getter will get the hidden attribute from `ExpProtocol`_. If not found,
it will set this attribute a default value got from the result of *f*. getter will
set the value to the hidden attribute. deleter will reset the value.
:param f:
:param name: parameter name
:param desp: parameter description
:param opt_type:
:param readonly: readonly property, cannot not be set or reset
:return: property
"""
raise RuntimeError()
# noinspection PyUnusedLocal
def exp_protocol(name: str, template: str = None):
"""**decorator**, register ExpProtocol a export protocol"""
raise NotImplementedError()
class ExpProtocol(metaclass=abc.ABCMeta):
# protocol property, shortcut for protocol method
@property
def pro_name(self) -> str:
"""protocol name"""
raise RuntimeError()
@property
def pro_class(self) -> str:
"""protocol class name"""
raise RuntimeError()
@property
def pro_template(self) -> Optional[str]:
"""protocol template html filepath"""
raise RuntimeError()
@property
def pro_filepath(self) -> str:
"""protocol source filepath"""
raise RuntimeError()
@property
def pro_id(self) -> int:
"""protocol ID"""
raise RuntimeError()
@property
def pro_error(self) -> Optional[BaseException]:
"""protocol current error information"""
raise RuntimeError()
# predefine protocol method
def pro_ensure(self, api: ExpApi) -> bool:
"""ensure experimental setup"""
pass
def pro_setup(self, api: ExpApi):
"""setup experimental environment, including required device, parameter setting,
protocol parameter setting, file saving path and etc.
"""
pass
def pro_teardown(self, api: ExpApi):
"""teardown experimental environment, including device disconnect, file post processing and etc."""
pass
def pro_step(self, api: ExpApi):
"""experimental step.
:param api:
:return: exit code
"""
pass