Files
controller-wisetopdataserver/python/biopro/controller.py
T
2022-09-30 10:56:37 +08:00

1731 lines
43 KiB
Python

"""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/<int: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/<str:command>')
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/<int: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/<int: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/<str: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/<int:device>/instruction/<str: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/<int:device>/sd_card', GET={})
def device_sd_card_status(self, device:int) -> None:
raise NotImplementedError()
@router('device/<int:device>/calibration', GET={})
def device_update_calibration(self) -> str:
"""get device battery information.
**Request Url**
**Response Specification**
Calibration
:return: Boolean
"""
raise NotImplementedError()
@router('device/<int:device>/<int:radio_frequency>', 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/<int:device>/<int:led_brightness>', 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/<int:device>/<int:accelerator_sensitivity>', 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/<int: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/<int:device>/p/<str:parameter>', 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/<int:device>/P/<str:parameter>', 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/<int: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/<int: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/<str:user>')
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/<str:user>/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/<str:user>/<int:device>/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/<str:user>', DEL={'device': None})
@router('device/history/<str:user>/<int:device>', 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/<path:path>')
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/<path:path>/<start:int>/<end:int>')
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/<path:path>', 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/<path:path>', 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/<path:path>', 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/<path:path>')
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/<path:path>/-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/<path:path>/-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/<str:ftype>')
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/<str:ftype>/<path:path>')
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/<path:path>',
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/<int:segment>/<path:path>')
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/<int:handle>', 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/<int:handle>', 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/<str:user>')
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/<str:user>', 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/<str:user>', 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/<str:user>', 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/<str:name>', 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/<str:name>', 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/<str:name>', 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/<str:name>', 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/<str:name>', 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/<str:name>', 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/<str:name>', 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/<str:name>', 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/<str:name>',
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/<int:protocol>', 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/<int:protocol>/',
DEL=dict(option='', content=''))
@router('exp/p/<int:protocol>/<str:option>',
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/<int:protocol>')
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/<int:protocol>', 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