Files
controller-wisetopdataserver/python/biopro/server/main.py
T
2022-02-10 17:39:45 +08:00

1867 lines
62 KiB
Python

from time import sleep
from typing import Iterable
from datetime import datetime
import biopro.impl.vcgencmd as vcg
from biopro.data import DataServerOptions, DataAPI
from biopro.device.manager import DeviceManagerOptions, DeviceHardwareInterface
from biopro.devlib.data import DataDecodeFormat, RawDataDecoder
from biopro.devlib.device import DeviceCommandAction, DeviceCommonInstruction
from biopro.devlib.instruction import InternalInstruction, DeviceInstructionError
# from biopro.exp_pro.manager import ExpManagerOption, ExpManager
from biopro.file.manager import FileOptions
from biopro.message import *
from biopro.util.address import address_str
from biopro.util.datetime import *
from biopro.util.logger import logging_info, logging_verbose
from biopro.util.sleep import long_sleep
from biopro.util.sys import system_call, current_command_header
from .cache import CacheAPI, CacheNotifyHandler, CacheManager
from .controller import *
from .data import DataServerProcess
from .data_stream import DataStreamThread
from .led import LEDControlThread
from .mqtt import MqttThread
from .connect import RoutineConnectDeviceThread
from .socket import SocketServer, ServerThread, ServerShutdownException
from .trash import RoutineTrashThread
from .ups import UPSDetectThread
from .api import ApiRequests
from biopro.api.auth import AuthAPI
from biopro.api.controller import ControllerAPI
from biopro.api.device import DeviceAPI
### do not import Websocket mode
###from .websocket import WebsocketThread
_RUNTIME_COMPILE = False
T = TypeVar('T')
def _fc(c):
return c % len(FORE_COLOR) + FORE_COLOR[0]
class ControlServer(SocketServer, ControlServerAPI, CacheAPI, CacheNotifyHandler):
"""Data Server"""
def __init__(self, *options):
"""create data server.
**Server Options**
accept
1. :class:`biopro.server.controller.ControlServerOptions`
#. :class:`biopro.data.DataServerOptions`
#. :class:`biopro.file._api.FileOptions`
#. :class:`biopro.device.manager.DeviceManagerOptions`
:param options: server options
"""
super().__init__((ControlAPI, CacheAPI), 'Controller', self.ADDR,
socket_listen=10,
thread_pool_count=16)
# controller ID
from ._identify import controller_device_id
self.__controller_ID = controller_device_id()
# auth set jwt
AuthAPI.set_key()
# controller server
self._controller_options = c = ControlServerOptions.get_options(*options)
# file managers
self._file_manager = self._init_file_manager(c)
self._file_save_cache = {}
# device manager
device_option = DeviceManagerOptions.get_options(*options)
self._device_manager = DeviceManager(device_option, self)
# data server
data_options = DataServerOptions.get_options(*options)
data_options.repository = device_option.repository
self.data_server = DataServerProcess(data_options)
# experimental protocol
# exp_options = ExpManagerOption.get_options(*options)
# self.exp_manager = ExpManager(self, exp_options)
# cache
self.cache_manager = CacheManager(self,
c.cache_server_cache_size,
# time unit from sec to ms
c.cache_server_cache_time * 1000)
# websocket server ###
### self.websocket_thread = WebsocketThread(self,
### c.websocket_address,
### c.websocket_port)
# remote server / MQTT
self.mqtt_thread = None
if not c.flag_disable_remote_server:
self.mqtt_thread = MqttThread(self,
self.socket_mqtt_ip,
self.socket_mqtt_port,
'control_server',
self.log_verbose,
'MQTT-control-server')
# routine trash thread
self._routine_trash_thread = None
# if not c.flag_disable_routine_trash:
# self._routine_trash_thread = RoutineTrashThread(self._file_manager,
# c.trash_routine_time,
# c.trash_expire_time,
# c.trash_remove_expire_time)
# routine data routine
self._data_stream_thread = None
# LED control
self.led_thread = LEDControlThread(disable=c.flag_disable_led_control,
led_count=c.led_count,
refresh_mode=not c.flag_disable_led_refresh)
# device detect
self._detect_device_thread = RoutineConnectDeviceThread(self._device_manager, self.mqtt_thread, self.led_thread, 3)
if not c.flag_disable_led_control:
self.led_thread.set_state(LED.COLOR_RED)
# UPS detect
self.ups_thread = None
if not c.flag_disable_ups_detect:
self.log_verbose('UPS_ENABLE', True)
self.ups_thread = UPSDetectThread(self, c.flag_ups_wait_time)
# user session
self._session = {}
self._user_session_expire = c.user_session_expire_days * 24 * 60 * 60 # sec
# temp use variable
# indicate those device together
self._device_instruction_group = None
# scan max duration
self._scan_max_timeout = 5
# _api request build
self._api = ApiRequests()
# init method
def _init_file_manager(self, option: ControlServerOptions) -> FileManager:
"""initialize file manager
:param option: control server cli options
:return: file manager instance
"""
r = Path(option.storage_root).absolute()
if not r.exists():
self.log_info('create storage root', r)
r.mkdir(parents=True)
file_manager_options = FileOptions.with_default_structure(r)
e = option.export_root
if e is not None:
file_manager_options.export_root = str(Path(e).absolute())
# create manager
return FileManager(file_manager_options)
# abstract property override
@property
def options(self) -> ControlServerOptions:
return self._controller_options
@property
def file_manager(self) -> FileManager:
return self._file_manager
@property
def device_manager(self) -> DeviceManager:
return self._device_manager
# server setup
def setup(self) -> None:
"""setup the control server
"""
# print allow host
from biopro.util.inet import allow_host
for host in allow_host():
self.log_info('allow_host', host)
# UPS
if self.ups_thread is not None:
self.run_thread(self.ups_thread)
# LED
self.run_thread(self.led_thread)
self.led_thread.set_state(LED.POWER_ON)
# sha checking
# if _RUNTIME_COMPILE or not self._controller_options.flag_disable_sha_check:
# self.run_thread(_RandomCrashThread())
# data server
interface = self.device_manager.interface
options = self.data_server.options
if interface == DeviceHardwareInterface.INTERFACE_DUMMY:
options.flag_spi_mode = DataAPI.MODE_DISABLE
elif interface == DeviceHardwareInterface.INTERFACE_CC2650_S:
options.flag_spi_mode = DataAPI.MODE_SINGLE
options.storage_root = str(self.file_manager.storage_root)
self.data_server.start_process()
# file manager
self.log_verbose('setup file manager')
self.file_manager.setup_storage()
self.file_manager.update_storage()
self.submit_thread(self._setup_file_manager)
# device manager
self.log_verbose('setup device manager')
self.device_manager.setup()
self.submit_thread(self._setup_library)
# device manager reset
self.submit_thread(self._setup_device)
# self.submit_thread(self._setup_controller)
self._setup_controller()
# self.run_thread(self._detect_device_thread)
# experimental protocol manager
# if not self._controller_options.flag_disable_exp_manager:
# self.exp_manager.setup()
# self.exp_manager.reload_protocol()
# websocket server
### self.log_verbose('setup websocket server', pc(self.websocket_thread.websocket_address, YELLOW))
# websocket server thread run ###
### self.run_thread(self.websocket_thread)
# broadcast greeting
self.broadcast_command(DataMessage.greeting())
# routine trashing
if self._routine_trash_thread is not None:
self.run_thread(self._routine_trash_thread)
# remote server
if self.mqtt_thread is not None:
self.mqtt_thread.start()
# self.run_thread(self.mqtt_thread)
def _setup_file_manager(self):
self.log_verbose('setup file manager')
# file manager clean external directory
self.file_manager.umount_external()
if not self._controller_options.flag_disable_external_storage:
# file manager mount external storage
self.file_manager.mount_external_auto()
if len(self._controller_options.external_path_map):
for f, t in self._controller_options.external_path_map:
self.file_manager.mount_external(f, Path(t))
self.log_verbose('cache root info')
### init file info get
# self.file_manager.info('').as_json()
# self.file_manager.list('').as_json()
return
def _setup_library(self):
self.log_verbose('reload library')
self.device_manager.reload_library()
def _setup_controller(self):
hardware_info = self.hardware_info().as_json_1()
from ._identify import controller_device_id
mqtt_id = controller_device_id()
hardware_info['mqtt_id'] = mqtt_id
hardware_info['status'] = 'running'
controller = ControllerAPI.getByMac(hardware_info['mac_address'])
if len(controller) == 0:
ControllerAPI.create(hardware_info)
elif len(controller) > 0:
if hardware_info['mac_address'] != controller[0]['mac_address'] or hardware_info['software_version'] != controller[0]['software_version'] or hardware_info['mqtt_id'] != controller[0]['mqtt_id']:
ControllerAPI.updateByAttrs('mac_address', hardware_info['mac_address'], {'software_version': hardware_info['software_version'], 'mqtt_id': mqtt_id })
def _setup_device(self):
# wait data server
self.log_verbose('device hardware reset')
self.device_manager.reset(software_reset=False)
self.log_verbose('wait data server')
if self.device_manager.interface == DeviceHardwareInterface.INTERFACE_DUMMY:
sleep(1)
else:
sleep(3)
try:
available_channel = self._setup_get_available_channel()
if available_channel is None:
self.device_manager.setup(DeviceHardwareInterface.INTERFACE_DUMMY)
else:
self.broadcast_command('state:device_available')
except ServerShutdownException as e:
self.interrupt(e)
return
except RuntimeError as e:
self.log_warn('reset fail', ':', str(e))
print_exception(self)
if self._controller_options.shutdown_if_reset_fail:
self.interrupt(ServerShutdownException())
return
self._setup_restart_device_manager(DeviceHardwareInterface.INTERFACE_DUMMY)
self._setup_reset_data_server()
except:
print_exception(self)
finally:
self.led_thread.set_state(LED.AVAILABLE)
def _setup_get_available_channel(self) -> Optional[List[int]]:
client = self.data_server.client()
if client is None:
raise RuntimeError('data server not available')
retry = 0
while retry < 10:
try:
with client:
client.reset()
break
except RuntimeError:
self.log_verbose('wait data server', 'x%d' % retry)
sleep(1)
retry += 1
else:
raise RuntimeError('data server not available')
# reset data server
with client:
data_spi_mode = client.spi_mode()
self.log_verbose('data server spi mode', data_spi_mode)
available_channel = client.available_channel()
# check channel
if len(available_channel) == 0:
if data_spi_mode == DataAPI.MODE_DISABLE:
return None
else:
raise RuntimeError('empty channel')
expect_channel = self._controller_options.expect_available_channel
if expect_channel is not None:
self.log_verbose('expect channel', expect_channel)
retry = 0
while available_channel != expect_channel and retry < 3:
sleep(1)
with client:
available_channel = client.available_channel(reset=True)
retry += 1
self.log_verbose('available channel', available_channel)
return available_channel
def _setup_restart_device_manager(self, interface: str):
self.log_info('shutdown device manager')
self.device_manager.shutdown()
# self.broadcast_command('shutdown')
self.log_info('restart device manager with interface', interface)
self.device_manager.setup(interface)
def _setup_reset_data_server(self):
"""change data server spi mode according device manager hardware interface"""
spi_mode = None
interface = self.device_manager.interface
if interface == DeviceHardwareInterface.INTERFACE_DUMMY:
spi_mode = DataAPI.MODE_DISABLE
elif interface == DeviceHardwareInterface.INTERFACE_CC2650_S:
spi_mode = DataAPI.MODE_SINGLE
if spi_mode is not None:
self.log_verbose('reset data server to mode', spi_mode)
client = self.data_server.client()
if client is not None:
with client:
client.reset(spi_mode)
# server close
def close(self, external_interrupt: bool):
self.broadcast_command('shutdown')
self.data_server.stop_process(external_interrupt)
self._stop_data_stream_thread()
self.device_manager.shutdown()
self.file_manager.shutdown(umount_external=True)
self.cache_manager.shutdown()
### self.websocket_thread.close()
self.led_thread.close()
if self.mqtt_thread is not None:
self.mqtt_thread.shutdown()
# self.mqtt_thread.close()
# user session
def new_user_session(self) -> str:
session = UserSession(None)
self._session[session.user_session] = session
self.log_info('new user session', session.user_session)
return session.user_session
@logging_info
def del_user_session(self, user_session: Optional[str] = None):
if user_session is None:
self._session.clear()
else:
try:
del self._session[user_session]
except KeyError:
pass
def get_user_session(self,
user_session: Optional[str],
new_session = False,
allow_empty = False) -> Optional[UserSession]:
"""
:param user_session: user session ID
:param new_session: new a user session if *user_session* not found.
:param allow_empty: consider empty *user_session* None
:return: user session
"""
if user_session is None:
session = None
elif len(user_session) == 0:
if allow_empty:
session = None
else:
raise None
else:
session = self._session.get(user_session, None)
if session is not None:
session.time()
elif new_session:
session = UserSession(user_session)
self._session[session.user_session] = session
self.log_info('new user session', session.user_session)
return session
def expire_user_session(self):
current_time = time()
for user_session in list(self._session.keys()):
session = self._session[user_session]
if session.time(update=False) - current_time > self._user_session_expire:
del self._session[user_session]
# general post action
def _post_device_list(self) -> List[CompletedDevice]:
"""general post action for getting list of connected devices.
:return: connected device list
"""
ret = self.device_manager.list_device()
if len(ret) == 0:
# no connected device
self.led_thread.set_state(LED.AVAILABLE)
else:
self.led_thread.set_state(LED.CONNECTED)
return ret
def _post_device_connected(self, device: Optional[int]):
"""general post action for any device has connected.
:param device: device connected
"""
self.led_thread.set_state(LED.CONNECTED)
self.broadcast_command(DataMessage.connected(device))
def _post_device_disconnected(self, device: Optional[int]):
"""general post action for any device has disconnected.
:param device: device disconnected.
"""
client = self.data_server.client()
if client is not None:
with client:
if client.is_sync(device):
client.stop_sync(device)
if len(self.device_manager.list_device()) == 0:
self.led_thread.set_state(LED.AVAILABLE)
data = {
"connect_status": 'idle'
}
self._api.updateDeviceByAttr('connect_status', 'update', data)
self.broadcast_command(DataMessage.disconnected(device))
# self.broadcast_command('device/disconnected/' + str(device))
# server provide method implement : hardware information
@logging_info
def hardware_info(self) -> HardwareInfo:
return HardwareInfo(
name=self.server_name,
device_ID=self.__controller_ID,
device_mac=self._controller_mac_address()
)
def _controller_mac_address(self) -> Optional[str]:
from biopro.util.inet import ip_addr
if _RUNTIME_COMPILE:
interface_list = ip_addr('eth0')
else:
interface_list = ip_addr()
for interface in interface_list:
if _RUNTIME_COMPILE:
if interface.interface == 'eth0':
break
else:
if interface.interface.startswith('e'):
break
else:
return None
return interface.link_addr
def hardware_datetime(self, content: Optional[str]) -> str:
if content is not None:
frontend_time = datetime.fromtimestamp(int(content) / 1000)
local_time = datetime.now()
if frontend_time > local_time:
self.log_verbose('timestamp update', local_time, frontend_time, frontend_time-local_time)
set_datetime(frontend_time)
# d = str_datetime(content)
# print('d',d)
# d = datetime
# # set System datetime
# self.file_manager.update_storage()
return datetime_str()
@logging_info
def hardware_menu(self) -> Optional[Menu]:
return super().hardware_menu()
# server provide method implement : hardware functions
@logging_info
def hardware_scan(self) -> bool:
self.hardware_scan_device()
return True
def hardware_scan_device(self):
self.broadcast_command(DataMessage.scan(''))
device_not_found = True
# we want once find any device, update the information ASAP by broadcasting
def callback(_: DeviceResponseInfo):
nonlocal device_not_found
device_not_found = False
self.broadcast_command(DataMessage.scan('update'))
if self.device_manager.scan_callback(callback, timeout=self._scan_max_timeout, all_device=True):
self.broadcast_command(DataMessage.scan('done'))
# if device_not_found:
# # no device found, longer the max scan time
# self._scan_max_timeout = min(10, self._scan_max_timeout + 2)
@logging_info
def hardware_device(self) -> List[DeviceResponseInfo]:
return self.device_manager.found()
@logging_info
def hardware_device_found(self, content: Union[str, JSON_OBJECT, DeviceInfo], *,
scan = False) -> Optional[DeviceResponseInfo]:
return super().hardware_device_found(content, scan=scan)
@logging_info
def hardware_device_connect(self, content: Union[str, JSON_OBJECT, DeviceInfo]) -> CompletedDevice:
if isinstance(content, DeviceResponseInfo):
response = content
else:
response = to_device_info(content)
try:
ret = self.device_manager.connect(response)
except DeviceInstructionError as e:
'''when device reset fail error'''
return None
else:
if ret is not None:
# update device status by id
DeviceAPI.updateByMac(
address_str(ret.mac_address),
{
'battery': ret.battery,
'connect_id': ret.device_id,
'connect_status': 'connect',
'device_version': ret.device_version,
'library': ret.library_name,
'library_version': ret.library_version,
}
)
self._post_device_connected(ret.device_id)
return ret
def hardware_device_direct_connect(self, content: Union[str, JSON_OBJECT, DeviceInfo]) -> CompletedDevice:
if isinstance(content, DeviceResponseInfo):
response = content
else:
response = to_device_info(content)
try:
ret = self.device_manager.connect(response)
except DeviceInstructionError as e:
'''when device reset fail error'''
return None
else:
if ret is not None:
self._post_device_connected(ret.device_id)
return ret
@logging_info
def hardware_device_disconnect(self, device: Optional[int], force=False) -> bool:
connect_device = self.device_manager.get_device(device)
DeviceAPI.updateByMac(
address_str(connect_device.mac_address),
{
'connect_id': -1,
'connect_status': 'idle',
'auto_connect': False,
}
)
if device is None:
self.device_manager.disconnect_all(force)
ret = True
else:
ret = self.device_manager.disconnect(device, force)
data = {
"connect_status": 'idle',
"connect_id": -1,
"connect_time": 0,
"auto_connect": False
}
self._api.updateDeviceByAttr('connect_id', str(device), data)
self._post_device_disconnected(device)
return ret
@logging_info
def hardware_send(self, command: str, **options: str):
# hardware command for development mode
if command == 'led':
if not _RUNTIME_COMPILE:
return self._hardware_send_led(options)
elif command == 'power_off':
return self._hardware_send_power_off(options)
elif command == 'test':
if not _RUNTIME_COMPILE:
try:
return self._hardware_send_test(options)
except:
print_exception(self)
elif command == 'test_set':
if not _RUNTIME_COMPILE:
try:
return self._hardware_send_test_set(options)
except:
print_exception(self)
elif command == 'device':
try:
return self._hardware_send_device(options)
except:
print_exception(self)
elif command == 'measure':
try:
return self._hardware_send_measure(options['item'])
except:
pass
else:
ret = self.device_manager.handle_command(None, command, options)
self._post_hardware_send(command, None, ret)
return None
def _hardware_send_led(self, options: Dict[str, str]) -> bool:
state = options.get('state', None)
if state is not None:
self.led_thread.set_state(state)
return True
def _hardware_send_power_off(self, options: Dict[str, str]) -> bool:
s = options.get('s', 0)
try:
s = int(s)
except ValueError:
s = 0
def _power_off(_sec: int):
if not long_sleep(_sec, lambda: self.is_closed):
UPSDetectThread.power_off()
self.submit_thread(_power_off, s)
return True
def _hardware_send_measure(self, item: str) -> Union[int, float]:
if item == 'temp':
return vcg.measure_temp()
else:
raise RuntimeError('unknown measure item')
def _hardware_send_device(self, options: Dict[str, str]) -> JSON:
command = options.get('manager', None)
if command is not None:
# manager command
if command == 'all':
# noinspection PyTypeChecker
return ['all', 'reload']
elif command == 'reload':
self.device_manager.reload_library()
return True
else:
# TODO other manager command
pass
return False
else:
# TODO command for device
pass
def _post_hardware_send(self, command: str, device: Optional[int], action: DeviceCommandAction):
"""
:param command: send command
:param device: target device
:param action: what's happen
"""
self.log_verbose('hardware send', command, 'device', device, ':', action)
if action == DeviceCommandAction.CONNECTED:
self._post_device_connected(device)
elif action == DeviceCommandAction.DISCONNECTED:
self._post_device_disconnected(device)
elif action == DeviceCommandAction.SCAN:
self.broadcast_command(DataMessage.scan('done'))
# server provide method implement : device functions
@logging_info
def get_device_info_all(self) -> List[CompletedDevice]:
return self._post_device_list()
@logging_info
def get_device_info(self, device: int) -> Optional[CompletedDevice]:
return super().get_device_info(device)
# @logging_info
# def device_call(self, device: int) -> bool:
# self.device_manager.call_device(device)
#
# return True
@logging_info
def device_instruction_all(self, device: int) -> List[str]:
return self.device_manager.list_device_instruction(device)
@logging_info
def device_all_instruction(self, instruction: str, content: str) -> bool:
if len(content) == 0:
# for all device
device_id = list(map(lambda d: d.device_id, self.device_manager.list_device()))
else:
device_id = list(map(int, filter(len, content.split(' '))))
self.log_verbose('device', device_id, instruction)
self._device_instruction_group = device_id
try:
return self._device_all_instruction(instruction, device_id)
except:
print_exception(self)
finally:
self._device_instruction_group = None
def _device_all_instruction(self, instruction: str, device_id: List[int]) -> bool:
if instruction == DeviceCommonInstruction.START:
for device in device_id:
self.device_manager.device_instruction(device, instruction)
client = self.data_server.client()
if client is not None:
with client:
client.start_sync(*device_id)
for device in device_id:
self.broadcast_command(DataMessage.start(device))
elif instruction == DeviceCommonInstruction.INTERRUPT or instruction == DeviceCommonInstruction.RESET:
client = self.data_server.client()
if client is not None:
with client:
client.stop_sync(*device_id)
for device in device_id:
self.device_manager.device_instruction(device, instruction)
# for device in device_id:
# self.broadcast_command(DataMessage.stop(device))
if instruction == DeviceCommonInstruction.RECORD_ALL:
for device in device_id:
self.device_manager.device_instruction(device, instruction)
client = self.data_server.client()
if client is not None:
with client:
client.start_sync(*device_id)
# for device in device_id:
# self.broadcast_command(DataMessage.start(device))
return True
@logging_info
def device_instruction(self, device: int, instruction: str) -> bool:
self.device_manager.device_instruction(device, instruction)
return True
@logging_info
def device_battery(self) -> List[int]:
device_list = self.device_manager.list_device()
if len(device_list) > 0:
battery_list = list(map(lambda x: self.device_manager.get_device(x).battery, device_list))
return battery_list
@logging_info
def device_parent(self, device: int, content: Optional[str] = None) -> List[int]:
if content is not None:
self.device_manager.get_device(device).update_parent_info(JsonSerialize.to_json(content))
return self.device_manager.get_device(device).parent
def device_recording_file_name(self, device: int, content: Optional[str] = None) -> List[int]:
if content is not None:
self.device_manager.get_device(device).update_recording_file_name_info(str(content))
return self.device_manager.get_device(device).recording_file_name
@logging_info
def device_sd_card_status(self, device:int) -> None:
return None
@logging_info
def device_update_calibration(self, device: int) -> Union[bool, str]:
connect_device = self.device_manager.get_device(device)
mac_address = connect_device.mac_address
try:
if connect_device.library.name.startswith('Neulive3.'):
connect_device.calibration_info('NeuliveThreeOne')
elif connect_device.library.name.startswith('Neulive'):
connect_device.calibration_info('TDC4VC')
elif connect_device.library.name.startswith('EliteEIS'):
connect_device.calibration_info('EISZeroOne')
except BaseException as e:
# reset device info in db
DeviceAPI.updateByMac(
address_str(mac_address),
{
'connect_id': -1,
'connect_status': 'idle',
'auto_connect': False,
}
)
# disconnect device
self.hardware_device_disconnect(device, True)
print(e)
return False
else:
# update_calibration_info
DeviceAPI.updateByMac(
address_str(mac_address),
{
'calibration': str(connect_device.calibration)
}
)
# DeviceAPI.updateByAttrs('connect_status', 'update', {"connect_status": 'connect'})
return True
@logging_info
def device_update_radio_frequency(self, device: int, content:int) -> bool:
d = self.device_manager.get_device(device)
res = d.update_radio_frequency(content)
return res
@logging_info
def device_update_led_brightness(self, device: int, content:int) -> bool:
d = self.device_manager.get_device(device)
res = d.update_led_brightness(content)
return res
@logging_info
def device_update_accelerator_sensitivity(self, device: int, content:int) -> bool:
d = self.device_manager.get_device(device)
res = d.update_accelerator_sensitivity(content)
return res
@logging_info
def device_set_central_rf(self, device: int, content: int) -> bool:
d = self.device_manager.get_device(device)
res = d.central_rf_set(content)
@logging_info
def device_get_central_version(self, device: int) -> Optional[list]:
d = self.device_manager.get_device(device)
res = d.central_version_get()
return res
@logging_info
def device_parameter_last_update_timestamp(self, device: int) -> int:
d = self.device_manager.get_device(device)
if d is None:
raise RuntimeError(DEVICE_NOT_FOUND, str(device))
return d.parameter.last_modified_timestamp
@logging_info
def device_parameter(self, device: int, parameter: str, content: Optional[str] = None) -> Any:
# if content is not None:
# self.broadcast_command('parameter:' + str(device) + '/' + parameter + '/' + content)
return self.device_manager.handle_device_parameter_command(device, parameter, content)
@logging_info
def device_parameter_value(self, device: int, parameter: str) -> Any:
return self.device_manager.handle_device_parameter_value(device, parameter)
@logging_info
def device_save_path(self, device: int, unset: bool) -> Optional[str]:
d = self.device_manager.get_device(device)
if d is None:
raise RuntimeError(DEVICE_NOT_FOUND, str(device))
if unset:
try:
del self._file_save_cache[device]
except KeyError:
pass
return self.file_manager.unset(device)
else:
info = self.file_manager.use(device)
if info.is_meta_file:
# do not used file_path which contain file extension
return info.parent_path + '/' + info.file_name
directory = info.file_path
content = self._file_save_cache.get(device, self.TEMP_FILENAME_EXPR)
content = format_filename(content, d)
if '/' not in content:
content = directory + '/' + content
self.log_verbose('device_save_path', device, content)
return content
@logging_info
def device_save_path_set(self, device: int, content: str, dry_run: bool) -> str:
d = self.device_manager.get_device(device)
if d is None:
raise RuntimeError(DEVICE_NOT_FOUND, str(device))
content = re.sub("^ +", "", content)
content = re.sub(" +$", "", content)
content = re.sub(" +", "_", content)
ret = format_filename(content, d)
if dry_run:
return ret
else:
info = self.file_manager.use(device)
if info.is_meta_file:
directory = info.parent_path
else:
directory = info.file_path
self._file_save_cache[d.device_id] = content
if '/' not in ret:
ret = directory + '/' + ret
self.log_verbose('device_save_path_set', device, ret)
return ret
# server provide method implement : device history
@logging_info
def device_history_get(self, user: Optional[str]) -> List[DeviceResponseInfo]:
raise NotImplementedError()
@logging_info
def device_history_found_all(self, user: str) -> List[DeviceResponseInfo]:
raise NotImplementedError()
@logging_info
def device_history_found(self, user: str, device: int) -> Optional[DeviceResponseInfo]:
raise NotImplementedError()
@logging_info
def device_history_delete(self, user: str, device: Optional[int]) -> bool:
raise NotImplementedError()
# server provide method implement : file manager functions
@logging_info
def file_clean_storage(self) -> bool:
return super().file_clean_storage()
@logging_info
def file_index(self) -> RootInfo:
return super().file_index()
@logging_info
def file_list(self, path: str) -> DirectoryInfo:
return super().file_list(path)
@logging_info
def file_list_range(self, path: str, start: int, end: int) -> DirectoryInfo:
return super().file_list_range(path, start, end)
@logging_info
def file_rename(self, path: str, content: str) -> FileInfo:
return super().file_rename(path, content)
@logging_info
def file_mkdir(self, path: str) -> DirectoryInfo:
return super().file_mkdir(path)
@logging_info
def file_delete(self, path: str) -> bool:
return super().file_delete(path)
@logging_info
def file_info(self, path: str) -> FileInfo:
return super().file_info(path)
@logging_info
def file_meta_info(self, path: str) -> RecordingMetaFile:
return super().file_meta_info(path)
@logging_info
def file_export_info(self, path: str) -> Dict[str, Optional[Path]]:
return super().file_export_info(path)
@logging_info
def file_export_functions(self) -> Tuple[ExternalExportFunction, ...]:
return super().file_export_functions()
@logging_info
def file_export_parameters(self, ftype: str) -> Tuple[ExternalParameter, ...]:
return super().file_export_parameters(ftype)
@logging_info
def file_export(self, ftype: str, path: str, **options: str) -> str:
return super().file_export(ftype, path, **options)
@logging_info
def file_segment(self, method: str, path: str) -> Union[None, bool, FileSegment]:
return super().file_segment(method, path)
@logging_info
def file_segment_get(self, segment: int, path: str):
return super().file_segment_get(segment, path)
@logging_info
def file_handle_open(self, content: str) -> Optional[FileHandle]:
info = self.file_manager.info(content)
meta = info.meta_file
if meta is None:
raise RuntimeError('not a meta file')
handle = self.cache_manager.file_open(meta)
return self.file_handle_info(handle)
@logging_info
def file_handle_info(self, handle: int) -> Optional[FileHandle]:
info = self.cache_manager.file_info(handle)
if info is None:
return None
# modify file path
info.shadow_path(self.file_manager.shadow_path(Path(info.filepath)))
return info
@logging_info
def file_handle_close(self, handle: int):
self.cache_manager.file_close(handle)
@logging_info
def user_all(self) -> List[str]:
raise super().user_all()
@logging_info
def user_info(self, user: str, *, user_session: Optional[str] = None) -> Optional[AbstractUserSetting]:
return super().user_info(user, user_session=user_session)
@logging_info
def user_login(self, user: str, content: str, *,
user_session = None) -> ComplexResponse[AbstractUserSetting]:
raise super().user_login(user, content, user_session=user_session)
@logging_info
def user_logout(self, user_session: Optional[str] = None) -> None:
super().user_logout(user_session)
@logging_info
def user_add(self, user: str, content: str, *,
user_session = None) -> ComplexResponse[AbstractUserSetting]:
return super().user_add(user, content, user_session=user_session)
@logging_info
def user_del(self, user: str, content: str) -> bool:
return super().user_del(user, content)
@logging_info
def user_sync(self, user_session: Optional[str]) -> bool:
return super().user_sync(user_session)
@logging_info
def user_configuration_list(self, user_session: Optional[str], **options: str) -> List[str]:
return super().user_configuration_list(user_session, **options)
@logging_info
def user_configuration_get(self, name: str, user_session: Optional[str] = None) -> Optional[RecordingMetaFile]:
return super().user_configuration_get(name, user_session)
@logging_info
def user_configuration_add(self, name: str, content: str, user_session: Optional[str] = None) -> bool:
return super().user_configuration_add(name, content, user_session)
@logging_info
def user_configuration_del(self, name: str, user_session: Optional[str] = None) -> bool:
return super().user_configuration_del(name, user_session)
@logging_info
def user_configuration_clone(self, name: str, content: str, user_session: Optional[str] = None) -> bool:
return super().user_configuration_clone(name, content, user_session)
@logging_info
def user_setup_list(self, user_session: Optional[str] = None) -> List[str]:
return super().user_setup_list(user_session)
@logging_info
def user_setup_info(self, name: str, user_session: Optional[str] = None) -> Optional[UserSetup]:
return super().user_setup_info(name, user_session)
@logging_info
def user_setup_new(self, name: str, user_session: Optional[str] = None) -> Optional[UserSetup]:
return super().user_setup_new(name, user_session)
@logging_info
def user_setup_del(self, name: str, user_session: Optional[str] = None) -> bool:
return super().user_setup_del(name, user_session)
@logging_info
def user_alias_list(self, user_session: Optional[str] = None) -> List[UserDeviceAlias]:
return super().user_alias_list(user_session)
@logging_info
def user_alias_info(self, name: str, user_session: Optional[str] = None) -> Optional[UserDeviceAlias]:
return super().user_alias_info(name, user_session)
@logging_info
def user_alias_edit(self, name: str, content: str, user_session: Optional[str] = None) -> Optional[UserDeviceAlias]:
return super().user_alias_edit(name, content, user_session)
# @logging_info
# def exp_pro_list(self) -> List[ExpProtocolInfo]:
# return self.exp_manager.list_available_protocol()
@logging_info
def exp_pro_create(self, content: str, *,
user_session = None) -> int:
user_name = self._session_user_name(user_session)
if user_name is None:
raise RuntimeError('require user login')
self.file_manager.user_setting.user_info(user_name)
# p = self.exp_manager.create_protocol(content, user_name)
return
# @logging_info
# def exp_pro_options(self, protocol: int) -> List[ExpOption]:
# return self.exp_manager.protocol_options(protocol)
# @logging_info
# def exp_pro_option(self, protocol: int, option: str, content: Optional[str] = None) -> bool:
# if content == '':
# # option reset
# if option == '':
# # reset all options
# self.exp_manager.protocol_reset(protocol)
# else:
# self.exp_manager.protocol_reset(protocol, *option.split(','))
# else:
# # option get/set
# self.exp_manager.protocol_option(protocol, option, content)
# return True
# @logging_info
# def exp_pro_status_all(self) -> List[AbstractExpProtocolState]:
# ret = []
# for p in self.exp_manager.list_protocol():
# s = self.exp_manager.protocol_status(p)
# if s is not None:
# ret.append(s)
# else:
# ret.append(AbstractExpProtocolState(p))
# return ret
# @logging_info
# def exp_pro_status(self, protocol: int) -> Optional[ExpProtocolState]:
# return self.exp_manager.protocol_status(protocol)
# @logging_info
# def exp_pro_command(self, protocol: int, content: str) -> bool:
# if content == 'ensure':
# return self.exp_manager.ensure_protocol(protocol)
# elif content == 'start':
# self.exp_manager.submit_protocol(protocol)
# elif content == 'interrupt':
# self.exp_manager.interrupt_protocol(protocol)
# elif content == 'suspend':
# self.exp_manager.suspend_protocol(protocol)
# elif content == 'resume':
# self.exp_manager.resume_protocol(protocol)
# else:
# raise RuntimeError('unknown protocol command : ' + content)
# return True
@logging_info
def device_internal_command(self, device: int, oper: str, value: Any) -> bool:
device = self.device_manager.get_device(device)
if oper in (InternalInstruction.PREDEFINED_DATA_FORMAT, InternalInstruction.PREDEFINED_DATA_FORMAT_CALI):
if device is not None:
self._device_set_data_format(device, value)
return True
elif oper == InternalInstruction.PREDEFINED_SYNC:
if device is not None:
self._device_set_sync(device, bool(value))
return True
elif oper == InternalInstruction.PREDEFINED_DISABLE_CACHE:
if device is not None:
self._device_set_disable_cache(device, value)
return True
else:
return False
def _device_set_data_format(self, device: CompletedDevice, value: Any):
if isinstance(value, (str, bytes)):
value = DataDecodeFormat.parse(value)
if not isinstance(value, DataDecodeFormat):
raise TypeError('not DataDecodeFormat : ' + value.__class__.__name__)
self.log_verbose('update data format', value)
client = self.data_server.client()
if client is not None:
info = self.file_manager.use(device)
if info.is_meta_file:
# overwrite
info = self.file_manager.save(device, info, overwrite=True)
else:
filename = self._file_save_cache.get(device.device_id, self.TEMP_FILENAME_EXPR)
filename = format_filename(filename, device)
info = self.file_manager.save(device, filename)
with client:
client.update_device_configuration(device, info.meta_file, value)
def _device_set_disable_cache(self, device: CompletedDevice, disable):
if disable:
self.log_verbose('disable cache', device.device_id)
info = self.file_manager.use(device)
if not info.is_meta_file:
raise RuntimeError('%s should set after %s' % (
InternalInstruction.PREDEFINED_DISABLE_CACHE,
InternalInstruction.PREDEFINED_DATA_FORMAT
))
meta_file = info.meta_file
assert meta_file is not None
self.cache_manager.disable_cache(device, meta_file)
else:
self.cache_manager.enable_cache(device)
def _device_set_sync(self, device: CompletedDevice, sync: bool):
if sync:
self.log_verbose('start sync', device.device_id)
self.cache_manager.drop_data(device)
client = self.data_server.client()
if client is not None:
info = self.file_manager.use(device)
with client:
if not info.is_meta_file:
# save data in raw format.
filename = format_filename(self.TEMP_FILENAME_EXPR, device)
info = self.file_manager.save(device, filename)
client.update_device_configuration(device, info.meta_file, RawDataDecoder())
print('***456')
if self._device_instruction_group is None:
client.start_sync(device)
if self._device_instruction_group is None:
# broadcast
self.broadcast_command(DataMessage.start(device.device_id))
else:
self.log_verbose('stop sync', device.device_id)
if self._device_instruction_group is None:
start_sync = False
client = self.data_server.client()
if client is not None:
with client:
# start_sync = client.is_sync(device)
start_sync = True
if start_sync:
client.stop_sync(device)
if start_sync:
# unset file info, but keep file path cache
self.file_manager.unset(device.device_id)
# broadcast
self.broadcast_command(DataMessage.stop(device.device_id))
# server provide method implement : websocket broadcast functions
@logging_info
def broadcast_command(self, command: str, device: Optional[int] = None):
self.log_verbose(pc('broadcast_command', GREEN),
'(All)' if device is None else '(%d)' % device, command)
### self.websocket_thread.broadcast_command(command, device=device)
if self.mqtt_thread is not None:
self.mqtt_thread.broadcast_command(command, device=device)
# data stream thread
def data_request_stream(self, start: bool, handler: DataMessageHandler):
if start:
t = self._start_data_stream_thread()
t.add_handle(handler)
else:
t = self._data_stream_thread
if t is not None:
t.del_handle(handler)
if not t.has_handle():
self._stop_data_stream_thread()
def _start_data_stream_thread(self) -> DataStreamThread:
t = self._data_stream_thread
if self._data_stream_thread is None:
self._data_stream_thread = t = self.run_thread(DataStreamThread(self))
return t
def _stop_data_stream_thread(self):
t = self._data_stream_thread
self._data_stream_thread = None
if t is not None:
t.close()
# noinspection PyShadowingNames
@logging_info
def websocket_command(self, command: str) -> Union[None, str, bytes, List[str]]:
if command == DataMessage.GREETING:
return DataMessage.greeting()
elif command == DataMessage.SYNC:
return self._websocket_command_sync(map(lambda d: d.device_id, self._post_device_list()))
elif command.startswith(DataMessage.SYNC):
_, device_list = part_suffix(command, ':')
if ',' in device_list:
return self._websocket_command_sync(map(int, device_list.split(',')))
else:
return self._websocket_command_sync([int(device_list)])
elif command.startswith(DataMessage.BROADCAST):
_, message = part_suffix(command, ':')
if message is not None:
self.broadcast_command(DataMessage.broadcast(message))
return None
# noinspection PyShadowingNames
def _websocket_command_sync(self, device_list: Iterable[int]) -> Union[str, List[str]]:
client = self.data_server.client()
if client is None:
return DataMessage.sync(False)
else:
result_start = []
result_stop = []
with client:
for device in device_list:
if client.is_sync(device):
result_start.append(device)
else:
result_stop.append(device)
return [DataMessage.start(result_start), DataMessage.stop(result_stop)]
# noinspection PyShadowingNames
def websocket_data_request(self, handler: DataMessageHandler, command: Optional[str]):
try:
self._handle_command_response(handler, command)
except (ValueError, IndexError) as e:
self.log_warn(e)
handler.on_error(DataMessage.warn(str(e)))
except BaseException as e:
self.log_warn(e)
handler.on_error(DataMessage.error(str(e)))
# noinspection PyShadowingNames
def _handle_command_response(self, handler: DataMessageHandler, command: Optional[str]):
"""
:param command: websocket data request command
:return: error message
"""
response = handler.update_command(command)
if response is None:
return
if len(handler.listen_device) == 0:
handler.on_error("empty channel")
elif handler.sample_rate is None and handler.data_points is None:
handler.on_error("sample rate not set")
else:
self.cache_manager.get_data_message(handler, response)
# server provide method implement : cache API
@logging_verbose
def clear(self):
self.cache_manager.clear()
def push_data(self, data: Union[bytes, List[bytes]]):
self.cache_manager.push_data(data)
@logging_verbose
def drop_data(self, *device: int):
self.cache_manager.drop_data(*device)
def stop_data(self, device: int):
self.cache_manager.stop_data(device)
# noinspection PyShadowingNames
@logging_verbose
def notify_data(self, device: int, message: str):
self.broadcast_command(message, device=device)
# led function
def get_led_state(self) -> str:
return self.led_thread.state
def set_led_state(self, state: str):
self.led_thread.set_state(state)
# hardware send test
if not _RUNTIME_COMPILE:
def _hardware_send_test(self, options: Dict[str, str]):
ret = {
'name': NAME,
}
if len(options) == 0 or 'software' in options:
ret['software'] = self._hardware_send_test_software(options.get('software', None))
if len(options) == 0 or 'system' in options:
ret['system'] = self._hardware_send_test_system(options.get('system', None))
if len(options) == 0 or 'device' in options:
ret['device'] = self.device_manager.hardware_test()
if len(options) == 0 or 'hardware' in options:
ret['hardware'] = self._hardware_send_test_hardware(options.get('hardware', None))
return ret
def _hardware_send_test_software(self, section: Optional[str] = None) -> Dict[str, Any]:
if section is not None:
if section == '':
section = None
else:
section = section.split(',')
ret = {
'version': VERSION,
'git-rev': REV_VERSION,
'vendor': VENDOR,
'copyright': COPYRIGHT,
}
if section is None or 'file' in section:
ret['file'] = {
'storage_root': str(self.file_manager.storage_root),
'export_root': str(self.file_manager.export_root)
}
if section is None or 'cache' in section:
ret['cache'] = {
'cache_size': self.cache_manager.cache_size,
'cache_time': self.cache_manager.cache_time,
}
# if section is None or 'websocket' in section:
# ret['websocket'] = {
# 'address': self.websocket_thread.websocket_address,
# 'port': self.websocket_thread.websocket_port,
# }
if section is None or 'mqtt' in section:
ret['mqtt'] = None if self.mqtt_thread is None else self.mqtt_thread.hardware_test()
if section is None or 'trash' in section:
trash_manager = self._routine_trash_thread
ret['trash'] = None if trash_manager is None else {
'routine_interval': trash_manager.routine_interval,
'trash_expire': trash_manager.trash_expire,
'remove_expire': trash_manager.remove_expire,
}
if section is None or 'led' in section:
ret['led'] = {
'state_all': list(LED.COLOR.keys()),
'state': self.led_thread.state,
'disable': self.led_thread.is_disabled,
'count': self.led_thread.led_count,
'refresh': self.led_thread.refresh_mode
}
if section is None or 'ups' in section:
ret['ups'] = self.ups_thread is not None
if section is None or 'control' in section:
ret['control'] = {
'socket_file': self.ADDR,
'cmds': current_command_header()
}
if section is None or 'data_process' in section:
ret['data_process'] = {
'socket_file': str(self.data_server.socket_file),
'spi_mode': self.data_server.options.flag_spi_mode,
'disable_data_statistics': self.data_server.options.flag_disable_data_statistics,
'cmds': self.data_server.get_process_arguments()
}
if section is None or 'data_server' in section:
client = self.data_server.client()
if client is not None:
with client:
ret['data_server'] = client.hardware_test()
if section is None or 'django' in section:
from biopro.util.inet import allow_host
ret['django'] = {
'allowed_hosts': allow_host()
}
if section is None or 'session' in section:
ret['session'] = [{
'user_session': s.user_session,
'time_stamp': s.time(update=False),
'property': list(filter(lambda attr: attr not in ('time', 'user_session'),
filter(lambda attr: not attr.startswith('_'),
dir(s))))
} for s in self._session.values()]
return ret
def _hardware_send_test_system(self, section: Optional[str] = None) -> Dict[str, Any]:
if section is not None:
if section == '':
section = None
else:
section = section.split(',')
ret = {}
if section is None or 'uname' in section:
ret['uname'] = system_call('uname', '-a')
if section is None or 'volume' in section:
root = self.file_manager.index()
ret['volume'] = {
'total': root.volume_total,
'usage': root.volume_usage,
'free': root.volume_free,
}
if section is None or 'package_sys' in section:
from biopro.util.package import Package
ret['package_manager'] = Package.manager()
ret['package_sys'] = [{
'name': pkg.name,
'version': pkg.version,
} for pkg in Package.list()]
if section is None or 'package_pip' in section:
from biopro.util.package import PipPackage
ret['package_pip'] = [{
'name': pkg.name,
'version': pkg.version,
} for pkg in PipPackage.list()]
return ret
def _hardware_send_test_hardware(self, section: Optional[str] = None) -> Dict[str, Any]:
if section is not None:
if section == '':
section = None
else:
section = section.split(',')
ret = {}
if section is None or 'measure' in section:
ret['measure'] = {
'clock_arm': vcg.measure_clock('arm'),
'clock_core': vcg.measure_clock('core'),
'voltage': vcg.measure_volts('core'),
'temperature': vcg.measure_temp()
}
if section is None or 'ext_memory' in section:
from biopro.impl.ext_mem import hardware_test
ret['ext_memory'] = hardware_test()
if section is None or 'gpio' in section:
try:
from biopro.impl.gpio import hardware_test
except ImportError:
result = None
else:
result = hardware_test()
ret['gpio'] = result
return ret
def _hardware_send_test_set(self, options: Dict[str, str]) -> bool:
# TODO write options files
return False
class _RandomCrashThread(ServerThread):
def __init__(self):
super().__init__('Crash')
self.log_enable = False
def setup(self) -> None:
super().setup()
from biopro.util.sha import sha256sum_validate
try:
from ._sha import SUM
except ImportError:
if _RUNTIME_COMPILE:
return
else:
self.close()
return
for s, f in SUM: # type: str, str
if not sha256sum_validate(f, s, ignore_missing=True):
return
self.close()
def routine(self, event = None):
from random import randint
event.wait(randint(60, 600), disable_interrupt=True)
UPSDetectThread.power_off()