fisrt commit

This commit is contained in:
peterlu14
2021-12-20 14:52:55 +08:00
parent af68735608
commit 2341ef2e75
297 changed files with 89609 additions and 61 deletions
+273
View File
@@ -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()