def __scan_and_load_wrh_modules(path): if not os.path.exists(path): log(f'Path {path} for additional WRH modules does not exist') return () from modules.base import ModuleBase classes = [] for mdir in (e for e in os.listdir(path) if e[0] != '.' and os.path.isdir(pjoin(path, e))): _path = pjoin(path, mdir) if '__init__.py' not in os.listdir(_path): continue module_files = ( f for f in os.listdir(_path) if f[0] != '.' and f != '__init__.py' and f[-3:] == '.py') modules = [ import_module(str(pjoin(_path, m)).replace(os.sep, '.')[:-3]) for m in module_files ] attributes = [ getattr(module, attr) for module in modules for attr in module.__dict__ ] classes.extend( attr for attr in attributes if isinstance(attr, type) and issubclass(attr, ModuleBase) and issubclass(attr, Base) and getattr(attr, 'WRHID', None)) return tuple(classes)
def run_registration_procedure(self, new_id): """ Runs interactive procedure to register new module. """ log(f'*** Registering new {self.TYPE_NAME} module ***') self.id = new_id self.name = wrh_input('Module name: ')
async def prepare(self): # TODO: Check that incoming connections are are only from within VPN! if not self.last_modules_update or abs(datetime.now() - self.last_modules_update) > self.MODULES_CACHE_TIME: log('Invalidating cached modules list and syncing new one') BaseWrhForm.last_modules_update = datetime.now() BaseWrhForm.modules = await self._get_modules() BaseWrhForm.installed_module_types = list({m.type for m in self.modules})
def time_changed(self, date, rango_port): """ Reacts to time change and runs scenario (self) if needed. :param date: current date (day of the month, etc.) :param rango_port: port of the Rango Iryga system """ if self._should_activate(date): log('Scenario created from request string {} has been activated'. format(self.request)) self._activate(rango_port)
def print_process_errors(process): """ Awaits process error information on the STDERR stream. Process should be piped! This method does not return until process finishes! :param process: process to be polled """ while does_process_exist(process): _, err = process.communicate() log(err.decode('utf_8'), Color.FAIL)
def __init__(self, port, tornado_port): self.socket = None self.port = port self._should_end = False log( 'Following additional db models have been found: \n*{}'.format( '\n*'.join(str(m.__name__) for m in WRH_MODULES)), Color.BLUE) self.wrh_modules = {m.WRHID: m for m in WRH_MODULES} self._read_db_configuration() self.tornado_server = TornadoServer(tornado_port, self.sessionmaker)
def _edit_module(self): log('\nChoose which module to edit') [log('%d) %s named %s' % (i, module.TYPE_NAME, module.name)) for i, module in enumerate(self.installed_modules)] choice = ninput('> ') try: self.installed_modules[int(choice)].edit() self.configuration_parser.save_configuration(self.installed_modules) log("Success!", Color.GREEN) except (KeyError, ValueError, IndexError): pass
def _remove_module(self): log('\nChoose which module to remove') [log(f'{i}) {module.TYPE_NAME} named {module.name}') for i, module in enumerate(self.installed_modules)] choice = ninput('> ') try: del self.installed_modules[int(choice)] self.configuration_parser.save_configuration(self.installed_modules) log('Success!', Color.GREEN) except (KeyError, ValueError, IndexError): pass
def _delete_client(self, session): log('\n*** Existing clients ***') [ log(f'{client.id} --- {client.name}', Color.BLUE) for client in self.wrh_clients.values() ] client_id = npinput(message='Id of client to remove: ') to_remove = session.query(WRHClient).filter( WRHClient.id == client_id).first() if to_remove: session.delete(to_remove)
def start_work(self): if not self.wrh_clients: log('No point in running while there are no clients configured!', Color.WARNING) else: self._should_end = False signal.signal(signal.SIGINT, self._sigint_handler) self._start_tornado() self._await_connections() log('Stopping work') signal.signal(signal.SIGINT, signal.SIG_DFL)
def _modify_client(self, session): log('\n*** Existing clients ***') [ log(f'{client.id} --- {client.name}', Color.BLUE) for client in self.wrh_clients.values() ] client_id = npinput(message='Id of client to edit: ') to_edit = session.query(WRHClient).filter( WRHClient.id == client_id).first() if to_edit: to_edit.name = wrh_input(message='Input new name of the client: ')
def _show_options(self): actions = {'1': self._edit_module, '2': self._add_new_module, '3': self._remove_module, '4': self._run_system} while True: self.should_end = False log('\n[1] Edit module\n[2] Add new module\n[3] Delete module\n[4] Start modules\n[5] Exit') choice = ninput('> ') if choice == '5': break if choice not in actions: continue actions[choice]()
def _read_db_configuration(self, _file_=None): try: log('Connecting to database') db_configuration = json.loads(_file_.read()) self.db_engine = create_engine( '{dialect}://{username}:{password}@{host}:{port}/{database}'. format(**db_configuration)) Base.metadata.create_all(self.db_engine) self.sessionmaker = sessionmaker(bind=self.db_engine) except (IOError, KeyError) as e: log(f'Error when trying to read db configuration: {e}', Color.FAIL) raise
def _react_to_connection(self, connection, _): state, time_wait = (connection.recv(1024).decode('utf-8') + ",").split(',')[:2] message = f'{self.TYPE_NAME} received request for setting socket {state} to state for {time_wait} seconds' if str(state) == "ON" or str(state) == "on": log(message) self._set_socket_state(True, time_wait) elif str(state) == "OFF" or str(state) == "off": log(message) self._set_socket_state(False, time_wait) elif str(state) == "STATE" or str(state) == "state": connection.send(self.get_measurement().encode('utf-8'))
def _add_new_module(self): log("\nChose which module to add") module_classes = self.module_classes.values() [log(f'{i}) {m_class.TYPE_NAME}') for i, m_class in enumerate(module_classes)] try: module = module_classes[int(ninput('> '))]() module.run_registration_procedure(self.configuration_parser.get_new_module_id()) self.installed_modules.append(module) self.configuration_parser.save_configuration(self.installed_modules) log('Success!', Color.GREEN) except (KeyError, ValueError, IndexError): pass
def _react_to_connection(self, connection, _): state, number, time_wait, repeats, = ( connection.recv(1024).decode('utf-8') + ',,,').split(',')[:4] message = '{} received request for setting relay {} to state {} (seconds: {}, repeats {})'.format( self.TYPE_NAME, number, state, time_wait, repeats) if str(state).upper() == "ON": log(message) self._set_relay_state(number, True, time_wait, repeats) elif str(state).upper() == "OFF": log(message) self._set_relay_state(number, False, time_wait, repeats) elif str(state).upper() == "STATE": connection.send(self.get_measurement().encode('utf-8'))
def _new_connection(self, connection, address): try: data = json.loads( connection.recv(4096).decode('utf-8').replace('\0', '')) log(f'New connection from {address} who sent: {data}') wrh_client = self.wrh_clients.get(data['token']) if wrh_client: self._update_module_info(wrh_client.id, data['module_id'], data['module_type'], data['module_name']) self._upload_new_measurement(wrh_client, data) finally: connection.close()
def __init__(self, module_classes): """ Creates configuration parses object that parses configuration file in the provided path. :param module_classes: dictionary of module classes, with keys being the class names and values class objects :type module_classes: dict :raises UnknownModuleException: configuration file contains unknown module info :raises BadConfigurationException: configuration file is invalid """ try: open(WRH_CONFIGURATION_FILENAME).close() except IOError: log('No configuration file found, creating new one', Color.WARNING) self.save_configuration([]) self.module_classes = module_classes self._check_file_sanity()
def edit(self): """ Runs interactive procedure to edit module. Returns connection status and response. """ log('Provide new module information (leave fields blank if you don\'t want to change)' ) self.name = wrh_input(message='New module\'s name: ', allowed_empty=True) or self.name self.gpio = wrh_input( message="Please input new IP address of ESP8266 device: ", allowed_empty=True) or self.gpio self.port = npinput( "Please input new port on which module will be listening for commands: ", allowed_empty=True) or self.port
def edit(self): """ Runs interactive procedure to edit module. Returns connection status and response. """ log('Provide new module information (leave fields blank if you don\'t want to change)' ) log('Please note that changes other than name will always succeed') self.name = wrh_input(message='New module\'s name: ', allowed_empty=True) or self.name self.port = iinput( "Please input new port on which this module will be listening for commands: ", allowed_empty=True) or self.port self.rango_port = iinput( "Please input new port number of Rango Iryga installed in this system: ", allowed_empty=True) or self.rango_port
def edit(self): """ Runs interactive procedure to edit module. Returns connection status and response. """ log('Provide new module information (leave fields blank if you don\'t want to change)' ) self.name = wrh_input(message='New module\'s name: ', allowed_empty=True) or self.name self.port = iinput( "Please input new port on which this module will be listening for commands: ", allowed_empty=True) or self.port new_api_location = wrh_input( message="Please input new GoogleAPI key location: ", allowed_empty=True) if new_api_location: self.api_location = new_api_location self.drive = GoogleDriveManager(self.api_location, self.id)
def edit(self): """ Runs interactive procedure to edit module. Returns connection status and response. """ log('Provide new module information (leave fields blank if you don\'t want to change)' ) self.name = wrh_input(message='New module\'s name: ', allowed_empty=True) or self.name self.gpio = iinput( "Please input new gpio pin number to which sensor is connected: ", allowed_empty=True) or self.gpio self.interval = iinput( "Please input new interval (in minutes) for taking consecutive measurements: ", allowed_empty=True) or self.interval self.port = iinput( "Please input new port on which this module will be listening for commands: ", allowed_empty=True) or self.port
def edit(self): """ Runs interactive procedure to edit module. Returns connection status and response. """ log('Provide new module information (leave fields blank if you don\'t want to change)' ) self.name = wrh_input(message='New module\'s name: ', allowed_empty=True) or self.name self.camera_address = wrh_input( message="Please input new IP address of camera: ", allowed_empty=True) or self.camera_address self.camera_port = wrh_input( message= "Please input new port on which IP camera can be accessed: ", allowed_empty=True) or self.camera_port self.port = iinput( "Please input new port on which streamed images can be accessed: ", allowed_empty=True) or self.port
def _await_connections(self): predicate = lambda: self._should_end is False self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) bind_result = wait_bind_socket( self.socket, '', self.port, sleep=10, retries=5, predicate=predicate, error_message=f'Unable to bind to port {self.port}') if bind_result: log(f'Listening for incoming connections on port {self.port}') self.socket.listen(5) await_connection(self.socket, self._new_connection, predicate=predicate, close_connection=False)
def _web_service_thread(self): predicate = lambda: not self._should_end self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) bind_result = wait_bind_socket( self.socket, '', self.port, 10, predicate=predicate, error_message= f'{self.TYPE_NAME} {self.name} port bind failed. (ERROR_CODE, ERROR_MESSAGE) = ' ) if bind_result: log(f'{self.TYPE_NAME} {self.name} started listening') self.socket.listen(10) await_connection(self.socket, self._start_new_connection_thread, predicate=predicate, close_connection=False)
def get_measurement(self): """ Returns measurements taken by this module: two strings with download and upload speed values. """ command = ["/usr/bin/python2.7", "modules/speed_test/speedtest-cli/speedtest_cli.py"] try: results = subprocess.check_output(command).decode('utf_8') except subprocess.CalledProcessError: results = "" results = results.replace('\n', '') log("The results are as follows: " + str(results)) pattern = ".+?Download: (.+?/s).+?Upload: (.+?/s).*" checker = re.compile(pattern) if not checker.match(str(results)): download, upload = "0 Mbit/s", "0 Mbit/s" else: download = re.search(pattern, results).group(1) upload = re.search(pattern, results).group(2) return download, upload
def _send_measurement(self, measurement, _file_=None): """ Sends measured data to remote database. Remote server's credentials should be present in WRH server configuration file. :param measurement: measurement value to be stored in database :type measurement: any :param _file_: parameter filled by function decorator :type _file_: file """ conf = json.loads(_file_.read()) data = { 'token': conf['token'], 'module_type': self.WRHID, 'module_id': self.id, 'module_name': self.name, 'measurement': measurement, 'date': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') } for connection in open_connection((conf['host'], conf['port'])): connection.send(json.dumps(data).encode('utf-8')) log(f'Module {self.name} successfully uploaded its measurement')
def create_folder(self, folder_name, path=None): """ Creates folder in the Google Drive. :param folder_name: name of the folder :param path: placement of the folder in the drive filesystem :return: id of newly created folder or None if an error occurred """ # TODO: Add support for path parameter! file_metadata = { 'name': folder_name, 'mimeType': 'application/vnd.google-apps.folder', } folder_id = None try: folder = self._service.files().create(body=file_metadata, fields='id').execute() folder_id = folder.get('id') except googleapiclient.errors.HttpError as e: log(str(e), Color.EXCEPTION) return folder_id
def edit(self): """ Runs interactive procedure to edit module. Returns connection status and response. """ log('Provide new module information (leave fields blank if you don\'t want to change)' ) self.name = wrh_input(message='New module\'s name: ', allowed_empty=True) or self.name self.gpio = wrh_input( message= "Please input new name of the webcam device (usually /dev/video# where # is the specific number): ", allowed_empty=True) or self.gpio self.port = iinput( "Please input new port on which streamed images can be accessed: ", allowed_empty=True) or self.port self.login = wrh_input( message="Please input new login used to access the video stream: ", allowed_empty=True) or self.login self.password = wrh_input( message= "Please input new password used to access the video stream: ", allowed_empty=True) or self.password
def end_process(process, timeout, suppress_messages=False): """ Tries to end process by sending SIGINT signal. The request is repeated until process ends or timeout is reached. It the timeout is reached SIGKILL is sent instead. :param process: process to stop :param timeout: (in seconds) timeout after which SIGKILL process :param suppress_messages: should messages be printed to STDOUT """ try: if not suppress_messages: log('Sending SIGINT to process: ' + str(process.pid)) process.send_signal(signal.SIGINT) while does_process_exist(process) and timeout > 0: time.sleep(1) timeout -= 1 if does_process_exist(process): if not suppress_messages: log( 'Process ' + str(process.pid) + " not responding. Sending SIGTERM.", Color.WARNING) process.terminate() except OSError: pass