"""Controller API, it define the data/message protocol between the web server and the controller server. **Controller Server** A facet server which transfer the request to purpose manager class. """ import base64 from typing import Optional, Iterable, TypeVar, Generic from biopro.text import * from biopro.util.json import * from biopro.util.socket import SocketClient, SocketClientMacro, SocketTimeoutError try: from biopro.util.router import router, Router except ImportError: Router = type def router(*args, **kwargs): return lambda f: f T = TypeVar('T') class ComplexResponse(Generic[T]): """response with the cookie data""" __slots__ = 'response', 'response_cookie' def __init__(self, response: T, cookie: Union[Dict[str, str], 'ComplexResponse'] = None): self.response: T = response '''original response data''' if isinstance(cookie, dict): cookie = dict(cookie) elif isinstance(cookie, ComplexResponse): cookie = dict(cookie.response_cookie) else: cookie = {} self.response_cookie = cookie '''cookie''' def set_cookie(self, **cookie: str): self.response_cookie.update(cookie) class ControlAPI(metaclass=Router): """Application Programming Interface for Controller server. **API grouping** 1. ``hardware_*`` controller general function #. ``get_device_*``, ``device_*`` device relate function #. ``file_*`` file system, export function from :class:`biopro.file.manager.FileManager` #. ``user_*`` user setting, export function from :class:`biopro.file.user.manager.UserSettingManager` #. ``exp_pro_*`` experimental protocol. export function from :class:`biopro.exp_pro.manager.ExpManager` """ ADDR = '/tmp/BPS.socket' '''controller server socket file path''' __slots__ = () @router('hardware/init') def hardware_info(self) -> JSON_OBJECT: """the information of the master hardware. **Request Url** **Response Specification** :class:`biopro.server.controller.HardwareInfo` :return: server information in json object. """ raise NotImplementedError() @router('hardware/datetime', GET=dict(content=None), POST={}) def hardware_datetime(self, content: Optional[str]) -> str: """get/set hardware datetime. **Request Url** **Request Body** datetime expression **Response Specification** datetime expression """ raise NotImplementedError() @router('hardware/scan', PUT={}) def hardware_scan(self) -> bool: """scan nearby device. **Request Url** **Response Specification** boolean, success or not this function may broadcast scan message. :return: success or not """ raise NotImplementedError() @router('hardware/device') def hardware_device(self) -> JSON_ARRAY: """get the information of the found devices. **Request Url** **Response Specification** list of :class:`biopro.devlib.device.DeviceResponseInfo` :return: id list of the device response information """ raise NotImplementedError() @router('hardware/device', POST={}) def hardware_device_found(self, content: str, *, scan: str = 'false') -> JSON: """get the information of the specific device. **Request Url** **Request Body** ``MAC_ADDRESS`` **Response Specification** :class:`biopro.devlib.device.DeviceResponseInfo` :return: device response information """ raise NotImplementedError() @router('hardware/device', PUT={}) def hardware_device_connect(self, content: Union[str, JSON_OBJECT]) -> JSON_OBJECT: """connect device. **Request Url** **Request Body** :class:`biopro.devlib.device.DeviceResponseInfo` required "device_address". secondary required "device_name" and "serial_number" other left property are ignored. **Response Specification** :class:`biopro.devlib.device.Device`. this function will broadcast connected message. :param content: device name and address :return: """ raise NotImplementedError() def hardware_device_direct_connect(self, content: Union[str, JSON_OBJECT]) -> JSON_OBJECT: """direct connect device. **Request Url** **Request Body** :class:`biopro.devlib.device.DeviceResponseInfo` required "device_address". secondary required "device_name" and "serial_number" other left property are ignored. **Response Specification** :class:`biopro.devlib.device.Device`. this function will broadcast connected message. :param content: device name and address :return: """ raise NotImplementedError() @router('hardware/device', DEL={'device': None}) @router('hardware/device/', DEL={}) def hardware_device_disconnect(self, device: Optional[int], force=False) -> bool: """disconnect device. **Request Url** **Response Specification** boolean, success or not this function will broadcast disconnected message. :param device: device ID, None for all :param force: force disconnect :return: """ raise NotImplementedError() @router('hardware/menu') def hardware_menu(self) -> Optional[JSON_OBJECT]: """hardware manager menu. **Request Url** **Response Specification** nullable menu json. :class:`biopro.devlib.menu.Menu`. :return: """ raise NotImplementedError() @router('hardware/send/') def hardware_send(self, command: str, **options: str) -> JSON: """send command to the hardware. **Request Url** **command format** **general/controller command** =============== =============== ========================= command options description =============== =============== ========================= led state=STATE power_off s=SEC test [SECTION=S[,S]] device manager=COMMAND measure item=ITEM =============== =============== ========================= **master device command** =================== =========== ============================= command options description =================== =========== ============================= scan create_demo_device =================== =========== ============================= **Response Specification** :param command: hardware command :param options: command options :return: """ raise NotImplementedError() @router('device/') def get_device_info_all(self) -> JSON_ARRAY: """get all of the information of the connected device. **Request Url** **Response Specification** list of :class:`biopro.devlib.device.Device` :return: """ raise NotImplementedError() @router('device//info') def get_device_info(self, device: int) -> JSON_OBJECT: """get the information of the device with ID ``device`` . **Request Url** **Response Specification** :class:`biopro.devlib.device.Device` :param device: device ID :return: json object of device information, null it not Found """ raise NotImplementedError() @router('device//instruction') def device_instruction_all(self, device: int) -> List[str]: """list device instructions. **Request Url** **Response Specification** list of instruction :param device: device ID :return: list of instruction """ raise NotImplementedError() @router('device/all/instruction/', POST={}) def device_all_instruction(self, instruction: str, content: str) -> bool: """send instruction to all of the connected device **Request Url** **Request Body** device ID list spread with ',' **Response Specification** boolean, success or not :param instruction: device instruction :param content: device IDs :return: success or not """ raise NotImplementedError() @router('device//instruction/', POST={}) def device_instruction(self, device: int, instruction: str) -> bool: """send instruction to device **Request Url** **Response Specification** boolean, success or not :param device: device ID :param instruction: device instruction :return: success or not """ raise NotImplementedError() @router('device/battery', GET={}) def device_battery(self) -> List[int]: """get device battery information. **Request Url** **Response Specification** battery :return: battery """ raise NotImplementedError() @router('device/parent', GET={}) def device_parent(self) -> List[int]: """get device parent information. **Request Url** **Response Specification** parent :return: parent """ raise NotImplementedError() @router('device/recording_file_name', GET={}) def device_recording_file_name(self) -> List[int]: """get device recording_file_name information. **Request Url** **Response Specification** recording_file_name :return: recording_file_name """ raise NotImplementedError() @router('device//sd_card', GET={}) def device_sd_card_status(self, device:int) -> None: raise NotImplementedError() @router('device//calibration', GET={}) def device_update_calibration(self) -> str: """get device battery information. **Request Url** **Response Specification** Calibration :return: Boolean """ raise NotImplementedError() @router('device//', GET={}) def device_update_radio_frequency(self, device: int, radio_frequency:int) -> bool: """get device radio frequency. **Request Url** **Response Specification** :return: Boolean """ raise NotImplementedError() @router('device//', GET={}) def device_update_led_brightness(self, device: int, led_brightness:int) -> bool: """get device led brightness. **Request Url** **Response Specification** :return: Boolean """ raise NotImplementedError() @router('device//', GET={}) def device_update_accelerator_sensitivity(self, device: int, accelerator_sensitivity:int) -> bool: """get device accelerator sensitivity. **Request Url** **Response Specification** :return: Boolean """ raise NotImplementedError() @router('device//p', GET={}) def device_parameter_last_update_timestamp(self, device: int) -> int: """get last timestamp of parameter operating. **Request Url** **Response Specification** timestamp :param device: device ID :return: timestamp """ raise NotImplementedError() @router('device//p/', GET={}, POST={}) def device_parameter(self, device: int, parameter: str, content: Optional[str] = None) -> JSON: """get/set device parameter P value. **Request Url** **Request Body** new parameter value or operator **Response Specification** raw parameter value. :param device: device ID :param parameter: parameter name :param content: new parameter value :return: new parameter P value """ raise NotImplementedError() @router('device//P/', GET={}) def device_parameter_value(self, device: int, parameter: str) -> JSON: """get device parameter V value. **Request Url** **Response Specification** human readable parameter value. :param device: device ID :param parameter: parameter name :return: parameter V value """ raise NotImplementedError() @router('device//file', GET={'unset': False}, DEL={'unset': True}) def device_save_path(self, device: int, unset: bool) -> Optional[str]: """get the current saving *path*. **Request Url** **Response Specification** file path, None if DELETE :param device: device ID :param unset: unset path :return: file path """ raise NotImplementedError() @router('device//file', PUT={'dry_run': False}, POST={'dry_run': True}) def device_save_path_set(self, device: int, content: str, dry_run: bool) -> str: """set device saving *path* **Request Url** **Request content** filename, use % to auto generate field. ========= ============================================ flag meaning ========= ============================================ %% char '%' %t datetime %Td date %Tt time %TY year %Tn month in number %TN month in text %Tw week in number %TW week in text %TD day %Th hour (12 hour) %TH hour (24 hour) %Tm minute %Ts second %d device ID %Dn device name %Dm device address %Dt device library name %Un user name (not support now) %?FLAG{%} if FLAG result not empty, use content in {%} ========= ============================================ **Response Specification** file path :param device: device ID :param content: file name :param dry_run: just return result, do not set anything :return: file path """ raise NotImplementedError() @router('device/history/', GET={'user': None}) @router('device/history/') def device_history_get(self, user: Optional[str]) -> JSON_ARRAY: """get user used device history. **Request Url** **Response Specification** list of :class:`biopro.devlib.device.DeviceResponseInfo` :param user: certain user :return: """ raise NotImplementedError() @router('device/history//all/found') def device_history_found_all(self, user: str) -> JSON_ARRAY: """ **Request Url** **Response Specification** list of :class:`biopro.devlib.device.DeviceResponseInfo` :param user: certain user :return: """ raise NotImplementedError() @router('device/history///found') def device_history_found(self, user: str, device: int) -> JSON_OBJECT: """ **Request Url** **Response Specification** :class:`biopro.devlib.device.DeviceResponseInfo` :param user: certain user :param device: device number :return: """ raise NotImplementedError() @router('device/history/', DEL={'device': None}) @router('device/history//', DEL={}) def device_history_delete(self, user: str, device: Optional[int]) -> bool: """remove used device record from history. **Request Url** **Response Specification** boolean, success or not :param user: certain user :param device: device number :return: """ raise NotImplementedError() @router('file/', DEL={}) def file_clean_storage(self) -> JSON: """clean storage **Request Url** **Response Specification** boolean, success or not :func:`biopro.file.api.FileAPI.clean_storage` :return: """ raise NotImplementedError() @router('file/p') def file_index(self) -> JSON: """root directory information. **Request Url** **Response Specification** :class:`biopro.file.util.RootInfo` **Relative Method** :func:`biopro.file.api.FileAPI.index` :return: root directory information """ raise NotImplementedError() @router('file/p/', GET={'path': ''}) @router('file/p/') def file_list(self, path: str) -> JSON: """list directory content **Request Url** **Response Specification** :class:`biopro.file.util.DirectoryInfo` **Relative Method** :func:`biopro.file.api.FileAPI.list` :param path: directory path :return: directory information """ raise NotImplementedError() @router('file/p/', GET={'path': '', 'start': 0, 'end': 0}) @router('file/p///') def file_list_range(self, path: str) -> JSON: """list directory content **Request Url** **Response Specification** :class:`biopro.file.util.DirectoryInfo` **Relative Method** :func:`biopro.file.api.FileAPI.list` :param path: directory path :return: directory information """ raise NotImplementedError() @router('file/p/', POST={}) def file_rename(self, path: str, content: str) -> JSON: """rename recording session to *path* **Request Url** **Request Body** target file path **Response Specification** :class:`biopro.file.util.FileInfo` **Relative Method** :func:`biopro.file.api.FileAPI.rename` :param path: source file path :param content: target file path :return: file information """ raise NotImplementedError() @router('file/p/', PUT={}) def file_mkdir(self, path: str) -> JSON: """create directory at *path* **Request Url** **Response Specification** :class:`biopro.file.util.DirectoryInfo` **Relative Method** :func:`biopro.file.api.FileAPI.mkdir` :param path: directory path need to be create :return: directory information """ raise NotImplementedError() @router('file/p/', DEL={}) def file_delete(self, path: str) -> JSON: """delete file at *path* **Request Url** **Response Specification** bool. **Relative Method** :func:`biopro.file.api.FileAPI.delete` :param path: :return: success or not """ raise NotImplementedError() @router('file/f/', GET={'path': ''}) @router('file/f/') def file_info(self, path: str) -> JSON: """get file information. **Request Url** **Response Specification** :class:`biopro.file.util.FileInfo` **Relative Method** :func:`biopro.file.api.FileAPI.info` :param path: file path :return: file information :raises FileNotFoundError: """ raise NotImplementedError() @router('file/f//-meta') def file_meta_info(self, path: str) -> JSON: """get meta file information. **Request Url** **Response Specification** :class:`biopro.recording.RecordingMetaFile` **Relative Method** :func:`biopro.file.api.FileAPI.meta` :param path: file path :return: meta information :raises FileNotFoundError: """ raise NotImplementedError() @router('file/f//-export') def file_export_info(self, path: str) -> JSON: """get exported files information. **Request Url** **Response Specification** **relative method** :func:`biopro.file.api.FileAPI.export_info` :param path: file path :return: file export information """ raise NotImplementedError() @router('file/e/') def file_export_functions(self) -> JSON: """list export functions **Request Url** **Response Specification** :class:`biopro.file.export.ExternalFunction` **Relative Method** :func:`biopro.file.manager.FileManager.get_export_functions` :return: list of functions """ raise NotImplementedError() @router('file/e/') def file_export_parameters(self, ftype: str) -> JSON: """list export function options. **Request Url** **Response Specification** :class:`biopro.file.export.ExternalParameter` **Relative Method** :func:`biopro.file.manager.FileManager.get_export_parameters` :param ftype: export function type :return: list of parameters """ raise NotImplementedError() @router('file/e//') def file_export(self, ftype: str, path: str, **options: str) -> str: """export file from *path* **Request Url** **Request Options** export function options. **Response Specification** path **Relative Method** :func:`biopro.file.api.FileAPI.export_file` :param ftype: export function :param path: source file path :param options: export function options :return: export result file path """ raise NotImplementedError() @router('file/s', DEL=dict(method='invalid_all', ftype='', path='')) @router('file/s/p/', GET=dict(method='info'), PUT=dict(method='new'), POST=dict(method='reload'), DEL=dict(method='invalid')) def file_segment(self, method: str, path: str) -> JSON: """file segment operating. if *method* is **invalid_all**, ignore *ftype* and *path*. **Request Url** **Response Specification** for method is *info* and *new* :class:`biopro.file.segment.FileSegment` for left methods: boolean, success or not :param method: one of info, new, reload, invalid and invalid_all :param path: export file link :return: """ raise NotImplementedError() @router('file/s//') def file_segment_get(self, segment: int, path: str) -> JSON: """get file segment **Request Url** **Response Specification** :class:`biopro.file.segment.FileSegmentContent` :param segment: segment index :param path: export file link :return: """ raise NotImplementedError() @router('file/h', PUT={}) def file_handle_open(self, content: str) -> JSON: """open file **Request Url** **Request Body** open file path **Response Specification** :class:`biopro.server.cache.FileHandle` **Relative Method** :func:`biopro.server.cache.CacheManager.file_open` :param content: file path :return: file handle :raises FileNotFoundError: """ raise NotImplementedError() @router('file/h/', GET={}) def file_handle_info(self, handle: int) -> JSON: """get information of file handle **Request Url** **Response Specification** :class:`biopro.server.cache.FileHandle`, null if *handle* not found **Relative Method** :func:`biopro.server.cache.CacheManager.file_meta` :param handle: file handle :return: """ raise NotImplementedError() @router('file/h/', DEL={}) def file_handle_close(self, handle: int): """close file handle **Request Url** **Response Specification** null **Relative Method** :func:`biopro.server.cache.CacheManager.file_close` :param handle: file handle :return: """ raise NotImplementedError() @router('user') def user_all(self) -> JSON_ARRAY: """get all of the user name **Request Url** **Response Specification** list of string **relative method** :func:`biopro.file.user.UserSettingAPI.user_list` :return: """ raise NotImplementedError() @router('user/-info', GET=dict(user='')) @router('user/u/') def user_info(self, user: str, *, user_session: Optional[str] = None) -> JSON: """get user information **Request Url** **Response Specification** :class:`biopro.file.user.AbstractUserSetting` :class:`biopro.file.user.UserSetting` null if no user login. **Used Cookie** :param user: user name :param user_session: user session :return: """ raise NotImplementedError() @router('user/u/', POST={}, COOKIE=('user_session',)) def user_login(self, user: str, content: str, *, user_session: Optional[str] = None) -> ComplexResponse[JSON]: """login an user **Request Url** **Request Body** user password **Response Specification** :class:`biopro.file.user.UserSetting` **Used Cookie** :param user: user name :param content: user password :param user_session: user session :return: """ raise NotImplementedError() @router('user/-logout', POST={}, COOKIE=('user_session',)) def user_logout(self, user_session: Optional[str] = None) -> JSON: """logout an user **Request Url** **Request Body** user password **Response Specification** null **Used Cookie** :param user_session: user session :return: """ raise NotImplementedError() @router('user/u/', PUT={}, COOKIE=('user_session',)) def user_add(self, user: str, content: str, *, user_session: Optional[str] = None) -> ComplexResponse[JSON]: """add an user **Request Url** **Request Body** user password **Response Specification** :class:`biopro.file.user.UserSetting` **Used Cookie** :param user: user name :param content: user password :param user_session: user session :return: """ raise NotImplementedError() @router('user/u/', DEL={}, COOKIE=('user_session',)) def user_del(self, user: str, content: str, *, user_session: Optional[str]) -> bool: """delete an user **Request Url** **Request Body** user password **Response Specification** boolean, success or not **Used Cookie** :param user: user name :param content: user password :param user_session: user session :return: """ raise NotImplementedError() @router('user/-sync', POST={}, COOKIE=('user_session',)) def user_sync(self, user_session: Optional[str]) -> bool: """sync user setting **Request Url** **Response Specification** success or not **Used Cookie** :param user_session: user session :return: """ raise NotImplementedError() @router('user/c', COOKIE=('user_session',)) def user_configuration_list(self, user_session: Optional[str], **options: str) -> List[str]: """list/filter user's configurations **Request Url** **Request Options** * ``device`` : device name filter. * ``library`` : library name filter. **Response Specification** list of configuration name, empty if user not login **Used Cookie** :param user_session: user session :param options: filter options :return: configuration name list """ raise NotImplementedError() @router('user/c/', COOKIE=('user_session',)) def user_configuration_get(self, name: str, user_session: Optional[str] = None) -> JSON_OBJECT: """get user configuration. **Request Url** **Response Specification** :class:`biopro.recording.RecordingMetaFile` null if user not login **Used Cookie** :param name: configuration name :param user_session: user session :return: """ raise NotImplementedError() @router('user/c/', PUT={}, COOKIE=('user_session',)) def user_configuration_add(self, name: str, content: str, user_session: Optional[str] = None) -> bool: """add new user configuration from meta file **Request Url** **Request Body** source meta filepath **Response Specification** boolean, success or not **Used Cookie** :param name: new configuration name. :param content: source meta filepath :param user_session: user session :return: success or not """ raise NotImplementedError() @router('user/c/', DEL={}, COOKIE=('user_session',)) def user_configuration_del(self, name: str, user_session: Optional[str] = None) -> bool: """delete user configuration. **Request Url** **Response Specification** boolean, success or not **Used Cookie** :param name: configuration name want to removed. :param user_session: user session :return: success or not """ raise NotImplementedError() @router('user/c/', POST={}, COOKIE=('user_session',)) def user_configuration_clone(self, name: str, content: str, user_session: Optional[str] = None) -> bool: """clone configuration *name* from *from_user* to *user* and given a *new_name* **Request Url** **Request Body** ``[USER:]NAME`` * ``USER`` : *from_user* * ``NAME`` : configuration name **Response Specification** boolean, success or not **Used Cookie** :param name: source configuration name :param content: target configuration name :param user_session: user session :return: success or not """ raise NotImplementedError() @router('user/e', COOKIE=('user_session',)) def user_setup_list(self, user_session: Optional[str] = None) -> List[str]: """list user experimental setup list. **Request Url** **Response Specification** list of name of experimental setup, empty if user not login **Used Cookie** :param user_session: user session :return: """ raise NotImplementedError() @router('user/e/', COOKIE=('user_session',)) def user_setup_info(self, name: str, user_session: Optional[str] = None) -> JSON_OBJECT: """get user experimental setup information. **Request Url** **Response Specification** :class:`biopro.file.user.UserSetup`, null if user not login **Used Cookie** :param name: setup name :param user_session: user session :return: """ raise NotImplementedError() @router('user/e/', PUT={}, COOKIE=('user_session',)) def user_setup_new(self, name: str, user_session: Optional[str] = None) -> JSON_OBJECT: """add new user experimental setup. **Request Url** **Response Specification** :class:`biopro.file.user.UserSetup`, null if user not login **Used Cookie** :param name: setup name :param user_session: user session :return: """ raise NotImplementedError() @router('user/e/', DEL={}, COOKIE=('user_session',)) def user_setup_del(self, name: str, user_session: Optional[str] = None) -> bool: """remove user experimental setup. **Request Url** **Response Specification** boolean, success or not **Used Cookie** :param name: new setup name :param user_session: user session :return: success or not """ raise NotImplementedError() @router('user/a', COOKIE=('user_session',)) def user_alias_list(self, user_session: Optional[str] = None) -> JSON_ARRAY: """list user device alias. **Request Url** **Response Specification** list of :class:`biopro.file.user.UserDeviceAlias`, empty if user not login **Used Cookie** :param user_session: user session :return: """ raise NotImplementedError() @router('user/a/', COOKIE=('user_session',)) def user_alias_info(self, name: str, user_session: Optional[str] = None) -> JSON_OBJECT: """get user device alias information. **Request Url** **Response Specification** :class:`biopro.file.user.UserDeviceAlias`, null if user not login **Used Cookie** :param name: device name, alias or mac address :param user_session: user session :return: """ raise NotImplementedError() @router('user/a/', PUT={}, DEL=dict(content=''), COOKIE=('user_session',)) def user_alias_edit(self, name: str, content: str, user_session: Optional[str] = None) -> JSON_OBJECT: """edit user device alias information. **Request Url** **Request Body** new device alias. empty for delete **Response Specification** :class:`biopro.file.user.UserDeviceAlias` null if DELETE, or create a alias without giving device address, or user not login **Used Cookie** :param name: device name, alias or mac address :param content: new device alias :param user_session: user session :return: """ raise NotImplementedError() @router('exp') def exp_pro_list(self) -> JSON: """list available experimental protocol **Request Url** **Response Specification** list of :class:`biopro.exp_pro.protocol.ExpProtocolInfo` **relative method** :func:`biopro.exp_pro.manager.ExpManager.list_available_protocol` :return: protocol """ raise NotImplementedError() @router('exp', PUT={}, COOKIE=('user_session',)) def exp_pro_create(self, content: str, *, user_session: Optional[str] = None) -> int: """create new experimental protocol **Request Url** **Request Body** protocol name **Response Specification** protocol ID **Used Cookie** **relative method** :func:`biopro.exp_pro.manager.ExpManager.create_protocol` :return: protocol ID """ raise NotImplementedError() @router('exp/p/', GET={}) def exp_pro_options(self, protocol: int) -> JSON: """list experimental protocol options **Request Url** **Response Specification** list of :class:`biopro.exp_pro.protocol.ExpOption` **relative method** :func:`biopro.exp_pro.manager.ExpManager.protocol_options` :param protocol: protocol ID :return: """ raise NotImplementedError() @router('exp/p//', DEL=dict(option='', content='')) @router('exp/p//', GET=dict(content=None), PUT={}, DEL=dict(content='')) def exp_pro_option(self, protocol: int, option: str, content: Optional[str] = None) -> bool: """set/reset experimental protocol option **Request Url** **Request Body** new option value **Response Specification** boolean, success or not **relative method** :func:`biopro.exp_pro.manager.ExpManager.protocol_option` :func:`biopro.exp_pro.manager.ExpManager.protocol_reset` :param protocol: protocol ID :param option: protocol option name :param content: option value :return: """ raise NotImplementedError() @router('exp/s') def exp_pro_status_all(self) -> JSON: """get all experimental protocol option status **Request Url** **Response Specification** list of :class:`biopro.exp_pro.schedule.AbstractExpProtocolState` or :class:`biopro.exp_pro.schedule.ExpProtocolState` **relative method** :func:`biopro.exp_pro.manager.ExpManager.protocol_status` :return: """ raise NotImplementedError() @router('exp/s/') def exp_pro_status(self, protocol: int) -> JSON: """get experimental protocol status **Request Url** **Response Specification** :class:`biopro.exp_pro.schedule.AbstractExpProtocolState` :class:`biopro.exp_pro.schedule.ExpProtocolState` **relative method** :func:`biopro.exp_pro.manager.ExpManager.protocol_status` :param protocol: protocol ID :return: """ raise NotImplementedError() @router('exp/s/', POST={}) def exp_pro_command(self, protocol: int, content: str) -> bool: """get experimental protocol status **Request Url** **Request Body** ============== =============================== action command description ============== =============================== ensure ensure experimental setup start start experimental interrupt interrupt experimental suspend suspend experimental resume resume suspend experimental ============== =============================== **Response Specification** boolean, success or not **relative method** :func:`biopro.exp_pro.manager.ExpManager.protocol_command` :param protocol: protocol ID :param content: action command :return: """ raise NotImplementedError() @router('hardware/restart') def restart_server(self) -> None: """restart server process. **Request Url** This method will terminate current process with restart exit code. The startup shell script should handle this exit code and re-run server process. """ raise NotImplementedError() def run_project(self, project) -> bool: raise NotImplementedError() def get_running_project(self) -> bool: raise NotImplementedError() def stop_project(self, project) -> bool: raise NotImplementedError() def set_project(self, project, content) -> bool: raise NotImplementedError() def set_project_cycle(self, project, index, content) -> bool: raise NotImplementedError() def show_device_data(self, device) -> bool: raise NotImplementedError() # noinspection PyAbstractClass class ControlClient(SocketClient, ControlAPI, metaclass=SocketClientMacro(ControlAPI)): """Connect to controller server through the socket. All of the method in this class will pack the parameter into bytes and transmit to the controller server through the socket. """ __slots__ = () def __init__(self): super().__init__(self.ADDR) class ControlContext(JsonSerialize): """Connect to controller server through the socket. It can hole the socket connection between the command call. :: context = ControlContext() with context as client: # do something context.accept(client.call_function()) This class implement context manager, and will catch all exception then set the error response. **Error Response Specification** :: return = response | error error = { "error": str # error message } """ __slots__ = '_client', '_data', '_response_cookie', '_error' def __init__(self): self._client: ControlClient = None self._data = None self._response_cookie = {} self._error = None def accept(self, data: Any): """accept the data from controller server.""" if isinstance(data, ComplexResponse): self._response_cookie = data.response_cookie data = data.response if isinstance(data, bytes): data = base64.b64encode(data).decode() self._data = data @property def response_data(self) -> JSON: """original response data""" return self._data @property def response_cookie(self) -> Iterable[Dict[str, Any]]: """response cookie""" for k, v in self._response_cookie.items(): yield dict(key=k, value=v) @property def has_error(self) -> bool: return self._error is not None def as_json(self) -> JSON_OBJECT: """ :return: json data """ if self._error is not None: return { 'error': self._error[0], 'message': self._error[1] } else: return { 'data': self._data } def __enter__(self): """nothing, return self""" self._client = ret = ControlClient() ret.open_socket() return ret def __exit__(self, exc_type, exc_val, exc_tb): """capture error, and set it into json data with key ``error``.""" client: Optional[ControlClient] = self._client self._client = None if client is not None: try: client.close_socket() except: raise NotImplementedError() # handler error during DataClient using if exc_type is not None: if exc_type == ConnectionRefusedError: # for AF_INET self._error = (exc_type.__name__, SERVER_NOT_AVAILABLE) elif exc_type == FileNotFoundError: # for AF_UNIX self._error = (exc_type.__name__, SERVER_NOT_AVAILABLE) elif exc_type == SocketTimeoutError: self._error = (exc_type.__name__, SERVER_NO_RESPONSE) elif exc_type == RuntimeError and len(exc_val.args) >= 2: exc_type = exc_val.args[0] exc_val = exc_val.args[1:] if isinstance(exc_val, str): self._error = (exc_type, str(exc_val)) elif isinstance(exc_val, (tuple, list)): self._error = (exc_type, ' '.join(map(str, exc_val))) else: self._error = (exc_type, str(exc_val)) else: self._error = (exc_type.__name__, str(exc_val)) # suppressed exception return True