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 from biopro.project.project_manager import ProjectManager ### 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): """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), '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) # 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') # 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) self._project_manager = ProjectManager(self.device_manager, self.mqtt_thread) 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 # 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 @property def project_manager(self) -> ProjectManager: return self._project_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._setup_controller() # self.run_thread(self._detect_device_thread) # broadcast greeting self.broadcast_command(DataMessage.greeting()) # remote server if self.mqtt_thread is not None: self.mqtt_thread.start() 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'] or hardware_info['wifi_name'] != controller[0]['wifi_name']: ControllerAPI.updateByAttrs ( 'mac_address', hardware_info['mac_address'], { 'software_version': hardware_info['software_version'], 'mqtt_id': mqtt_id, 'name': hardware_info['name'], 'wifi_name': hardware_info['wifi_name'] } ) 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.device_manager.shutdown() self.file_manager.shutdown(umount_external=True) self.led_thread.close() if self.mqtt_thread is not None: self.mqtt_thread.shutdown() 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_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: 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() @logging_info def run_project(self, project) -> bool: if project is not None: project = self._project_manager.create(project) self._project_manager.run_project(project) return project.as_json() @logging_info def stop_project(self, project) -> bool: if project is not None: self._project_manager.stop_project(project) return True @logging_info def get_running_project(self) -> bool: return self._project_manager.get() @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: 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: 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()) if self._device_instruction_group is None: client.start_sync(device) device.status = 1 else: 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) device.status = 0 # 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) # 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) # 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 'mqtt' in section: ret['mqtt'] = None if self.mqtt_thread is None else self.mqtt_thread.hardware_test() 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 @logging_info def show_device_data(self, device: int): client = self.data_server.client() if client is not None: with client: client.show_data(device) 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()