fisrt commit
This commit is contained in:
@@ -0,0 +1,273 @@
|
||||
"""Data server API. it define the data/message protocol between the controller server and the data server.
|
||||
|
||||
**Data Server**
|
||||
|
||||
A server handle the data from device and saving.
|
||||
|
||||
"""
|
||||
|
||||
import abc
|
||||
from pathlib import Path
|
||||
from typing import Union, Tuple, List, Optional, AnyStr
|
||||
|
||||
from biopro.devlib.data import DataDecodeFormat
|
||||
from biopro.devlib.device import Device
|
||||
from biopro.devlib.library import DeviceLibraryRepository
|
||||
from biopro.recording import RecordingMetaFile
|
||||
from biopro.server.process import ControllerProcess
|
||||
from biopro.util.cli_base import CliOptions, cli_options, cli_flags
|
||||
from biopro.util.json import JSON_OBJECT
|
||||
from biopro.util.socket import SocketClient, SocketClientMacro
|
||||
|
||||
from json import loads as json_parse, dumps as _json_stringify
|
||||
|
||||
def json_stringify(o) -> str:
|
||||
return _json_stringify(o, separators=(',', ':'))
|
||||
|
||||
|
||||
class DataAPI(metaclass=abc.ABCMeta):
|
||||
"""Application Programming Interface for Data server.
|
||||
|
||||
"""
|
||||
ADDR = '/tmp/BPS-data.socket'
|
||||
'''data server socket file path'''
|
||||
|
||||
MODE_DISABLE = 'disable'
|
||||
'''data server hardware interface mode. disable'''
|
||||
|
||||
MODE_SINGLE = 'single'
|
||||
'''data server hardware interface mode. single device support'''
|
||||
|
||||
MODE_SELECTOR = 'selector'
|
||||
'''data server hardware interface mode. multiple device support'''
|
||||
|
||||
MODE = (MODE_DISABLE, MODE_SINGLE, MODE_SELECTOR)
|
||||
'''data server hardware interface mode.'''
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
@abc.abstractmethod
|
||||
def reset(self, spi_mode: Optional[str] = None):
|
||||
"""reset data server
|
||||
|
||||
:param spi_mode: reset spi mode
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def shutdown(self):
|
||||
"""shutdown data server"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def spi_mode(self) -> str:
|
||||
"""data server hardware interface mode"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def available_channel(self, reset=False) -> List[int]:
|
||||
"""get available channel.
|
||||
|
||||
It will exam the hardware if *reset* is True.
|
||||
|
||||
:param reset: re-check channel
|
||||
:return: available channel.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def unset_device(self, device: int):
|
||||
"""clean device configuration.
|
||||
|
||||
:param device: device ID
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_device_configuration(self,
|
||||
device: Union[int, Device],
|
||||
meta_file: Union[str, Path, RecordingMetaFile],
|
||||
data_format: Union[AnyStr, DataDecodeFormat]):
|
||||
"""set device configuration.
|
||||
|
||||
:param device: device ID
|
||||
:param meta_file: meta file path
|
||||
:param data_format: data decode format
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_sync(self, device: Union[None, int, Device] = None) -> bool:
|
||||
"""is start sync
|
||||
|
||||
:param device: device
|
||||
:return: None for any
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def start_sync(self, *device: Union[int, Device]):
|
||||
"""start sync
|
||||
|
||||
:param device: device, empty for all
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def stop_sync(self, *device: Union[int, Device]):
|
||||
"""stop sync
|
||||
|
||||
:param device: device IDs, empty for all
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def sync_file(self):
|
||||
"""sync file, write-back dirty file. """
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def hardware_test(self) -> JSON_OBJECT:
|
||||
""""""
|
||||
pass
|
||||
|
||||
|
||||
# noinspection PyAbstractClass
|
||||
class DataClient(SocketClient, DataAPI, metaclass=SocketClientMacro(DataAPI)):
|
||||
"""Data server socket client.
|
||||
|
||||
"""
|
||||
|
||||
__slots__ = ()
|
||||
|
||||
def __init__(self):
|
||||
super().__init__(self.ADDR)
|
||||
|
||||
def update_device_configuration(self,
|
||||
device: Union[int, Device],
|
||||
meta_file: Union[str, Path, RecordingMetaFile],
|
||||
data_format: Union[AnyStr, DataDecodeFormat]):
|
||||
|
||||
# meta file
|
||||
if isinstance(meta_file, str):
|
||||
meta_file = Path(meta_file)
|
||||
|
||||
if isinstance(meta_file, Path):
|
||||
# load meta file to make sure file is existed and loadable.
|
||||
meta_file = RecordingMetaFile.load(meta_file)
|
||||
|
||||
if not isinstance(meta_file, RecordingMetaFile):
|
||||
raise TypeError('not a recording meta file : ' + meta_file.__class__.__name__)
|
||||
|
||||
# meta_file.device = device
|
||||
meta_file = str(meta_file.filepath)
|
||||
|
||||
# device
|
||||
if isinstance(device, Device):
|
||||
# device = device.device_id
|
||||
device = json_stringify(device.as_json())
|
||||
|
||||
# data format
|
||||
if isinstance(data_format, (str, bytes)):
|
||||
pass
|
||||
|
||||
elif isinstance(data_format, DataDecodeFormat):
|
||||
data_format = data_format.name
|
||||
|
||||
else:
|
||||
raise TypeError('not a data decode format : ' + data_format.__class__.__name__)
|
||||
|
||||
# send
|
||||
self.send_command('update_device_configuration', device, meta_file, data_format)
|
||||
|
||||
def is_sync(self, device: Union[None, int, Device] = None) -> bool:
|
||||
if isinstance(device, Device):
|
||||
device = device.device_id
|
||||
|
||||
return self.send_command('is_sync', device)
|
||||
|
||||
def start_sync(self, *device: Union[int, Device]):
|
||||
self.send_command('start_sync', *self._to_device_id(*device))
|
||||
|
||||
def stop_sync(self, *device: Union[int, Device]):
|
||||
self.send_command('stop_sync', *self._to_device_id(*device))
|
||||
|
||||
@staticmethod
|
||||
def _to_device_id(*device: Union[int, Device]) -> Tuple[int, ...]:
|
||||
return tuple(map(lambda d: d.device_id if isinstance(d, Device) else d, device))
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
class DataServerOptions(CliOptions):
|
||||
"""cli options for data server.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# spi mode
|
||||
self.flag_spi_mode: str = 'selector'
|
||||
'''data server hardware interface mode'''
|
||||
|
||||
self.repository = DeviceLibraryRepository(no_system_default=True)
|
||||
'''device library repository'''
|
||||
|
||||
self.flag_disable_data_statistics = False
|
||||
'''flag of statistics the device data missing'''
|
||||
|
||||
@cli_options('--spi-mode')
|
||||
def _spi_mode(self, opt: str, value: str):
|
||||
"""spi mode. could be:
|
||||
disable :
|
||||
single : use ExtMemSpiInterface
|
||||
selector : use MultiExtMemSpiInterface (default)
|
||||
"""
|
||||
if value not in DataAPI.MODE:
|
||||
raise ValueError(value)
|
||||
|
||||
self.flag_spi_mode = value
|
||||
|
||||
@cli_options('-L', '--library', value='PATH')
|
||||
def _library_path(self, opt: str, value: str):
|
||||
self.repository.add_library_path(value)
|
||||
|
||||
@cli_flags('--disable-data-statistics')
|
||||
def _disable_data_statistics(self, opt: str):
|
||||
"""disable data lost statistics"""
|
||||
self.flag_disable_data_statistics = True
|
||||
|
||||
|
||||
class DataServerProcess(ControllerProcess[DataServerOptions, DataClient]):
|
||||
"""data server process
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
options: DataServerOptions,
|
||||
flag_disable=False):
|
||||
super().__init__('DataServer', options, flag_disable=flag_disable)
|
||||
|
||||
@property
|
||||
def socket_file(self) -> Path:
|
||||
return Path(DataClient.ADDR)
|
||||
|
||||
def get_process_arguments(self) -> List[str]:
|
||||
opt = self.options
|
||||
|
||||
cmd = ['data',
|
||||
'--spi-mode', opt.flag_spi_mode]
|
||||
|
||||
if opt.flag_disable_data_statistics:
|
||||
cmd.append('--disable-data-statistics')
|
||||
|
||||
for path in opt.repository.library_path:
|
||||
cmd.append('-L')
|
||||
cmd.append(str(path))
|
||||
|
||||
return cmd
|
||||
|
||||
def new_client(self) -> DataClient:
|
||||
return DataClient()
|
||||
|
||||
def shutdown(self):
|
||||
with DataClient() as client:
|
||||
client.shutdown()
|
||||
Reference in New Issue
Block a user