Esempio n. 1
0
class ExportViewTarget(object):
  def __init__(self, project, filename, parent):
    self.mutex = QMutex()
    self.project = project
    self.filename = filename
    self.parent = parent
    self.surface = QOffscreenSurface()
    self.surface.setFormat(QSurfaceFormat.defaultFormat())
    self.surface.create()

  def destroy(self):
    self.surface.destroy()

  def render(self, view, uniforms=dict()):
    self.project.setCameraMode('view')
    self.project.setCameraViewRef(view)
    self.project.setCameraAtOrigin()
    self.project.render(self.ctx.functions(), view.info.width, view.info.height, uniforms)
    depth = self.project.renderer.depthmap(self.ctx.functions(), view.info.width, view.info.height)
    return depth

  def __enter__(self):
    self.mutex.lock()
    self.ctx = QOpenGLContext()
    self.ctx.setShareContext(self.parent)
    self.ctx.create()
    if not self.ctx.makeCurrent(self.surface):
      raise Exception("cannot make context current")
    self.project.renderer.lock()

  def __exit__(self, type, value, tb):
    self.project.renderer.unlock()
    self.ctx.doneCurrent()
    del self.ctx
    self.mutex.unlock()
Esempio n. 2
0
class Barrier:
    """
    Implement a barrier, such that threads block until other monitored threads reach a specific location.
    The barrier can be used multiple times (it is reinitialized after the threads passed).

    See https://stackoverflow.com/questions/9637374/qt-synchronization-barrier/9639624#9639624
    """
    def __init__(self, count):
        self.count = count
        self.origCount = count
        self.mutex = QMutex()
        self.condition = QWaitCondition()

    def wait(self):
        """
        Wait until all monitored threads called wait.
        :return: None
        """
        self.mutex.lock()
        self.count -= 1
        if self.count > 0:
            self.condition.wait(self.mutex)
        else:
            self.count = self.origCount
            self.condition.wakeAll()
        self.mutex.unlock()
class TestThread(QThread):
    startRecording = Signal()

    def __init__(self, config, aruco_tester_widget, acs_control, parent=None):
        QThread.__init__(self, parent)
        self.config = config
        self.aruco_tester_widget = aruco_tester_widget
        self.image_miner = aruco_tester_widget.image_miner
        self.acs_control = acs_control

        self.startRecording.connect(self.aruco_tester_widget.onRecordClicked)

        self.recordingStopped = QWaitCondition()
        self.mutex = QMutex()

    @Slot()
    def wake(self):
        self.recordingStopped.wakeAll()
        pass

    def run(self):
        print('starting...')
        base_dir = os.path.dirname(os.path.realpath(__file__))
        config = self.config['config']
        angles = self.config['angles']
        j = 0
            
        self.image_miner.set_video_capture_property('CAP_PROP_BRIGHTNESS', 50.5/100.)
        self.image_miner.set_video_capture_property('CAP_PROP_CONTRAST', 50.5/100.)
        self.image_miner.set_video_capture_property('CAP_PROP_SATURATION', 50.5/100.)

        prop = 'CAP_PROP_BRIGHTNESS'

        print(prop)
        start = config[prop]['start']
        end = config[prop]['end']
        step = config[prop]['step']
        for i in range(start, end+1, step):
            print(i)
            self.image_miner.set_video_capture_property(prop, i/100.)
            series_dir = os.path.join(base_dir, str(prop), str(i), str(self.aruco_tester_widget.use_board))
            j += 1
            
            for angle in angles:
                fileName = os.path.join(series_dir, str(angle))
                print(angle)
                self.acs_control.pa(angle)
                time.sleep(5)
                self.mutex.lock()
                self.startRecording.emit()
                print('wait for recording to stop')
                self.recordingStopped.wait(self.mutex)
                print('saving data to {}'.format(fileName))
                self.aruco_tester_widget.broadcaster.saveToFile(fileName)
                self.mutex.unlock()

            self.acs_control.pa(0)
            time.sleep(10)
Esempio n. 4
0
class SingleRunnableManager(QObject):
    def __init__(self, thread_pool: QThreadPool):
        """
        Runs a runnable on a thread pool, canceling the previous runnable if it is still enqueued.

        Parameters
        ----------
        thread_pool
            The thread pool that will be used to run the runnable.
        """
        super().__init__()
        self._thread_pool = thread_pool
        self._current_runnable = None
        self._lock = QMutex()

    def remove_runnable(self) -> None:
        """
        Removes the current runnable without canceling its execution.
        """
        self._lock.lock()
        try:
            del self._current_runnable
            self._current_runnable = None
            gc.collect()
        finally:
            self._lock.unlock()

    def _cancel(self) -> None:
        if self._current_runnable is not None:
            self._thread_pool.cancel(self._current_runnable)
        del self._current_runnable
        self._current_runnable = None
        gc.collect()

    def start(self, runnable: QRunnable) -> None:
        """
        Cancel the current runnable (if any) and enqueue the runnable for execution on the thread pool.

        Parameters
        ----------
        runnable
            The runnable to run

        """
        self._lock.lock()
        try:
            self._cancel()
            self._thread_pool.start(runnable)
        finally:
            self._lock.unlock()
Esempio n. 5
0
class QIpScanner(QObject):
    networkIpReceived = Signal(str, str, str)

    def __init__(self, parent=None):
        super().__init__(parent)

        self.isScanning = False
        self.worker = _Worker(self.networkIpReceived, self)
        self.mutex = QMutex()

    @Slot(result=bool)
    def isRunning(self):
        return self.worker.isRunning()

    @Slot()
    def scan(self):
        self.mutex.lock()
        if not self.worker.isRunning():
            self.worker.start()
        self.mutex.unlock()
Esempio n. 6
0
class SignalThread(QThread):
    def __init__(self, parent=None):
        QThread.__init__(self, parent)

        self.waitcond = QWaitCondition()
        self.mutex = QMutex()
        self.isstopped = False

    def trigger(self):
        """lock first to make sure the QThread is actually waiting for a signal"""
        self.mutex.lock()
        self.waitcond.wakeOne()
        self.mutex.unlock()

    def stopThread(self):
        self.mutex.lock()
        self.isstopped = True
        self.waitcond.wakeOne()
        self.mutex.unlock()

    def run(self):
        self.mutex.lock()
        while not self.isstopped:
            # just wait, and trigger every time we receive a signal
            self.waitcond.wait(self.mutex)
            if not self.isstopped:
                self.emit(SIGNAL("triggerSignal()"))
        self.mutex.unlock()
Esempio n. 7
0
class DetailViewer(QMainWindow):
    def __init__(self, parent=None):
        super(DetailViewer, self).__init__(parent)

        self.ui = detailViewer_ui.Ui_MainWindow()
        self.ui.setupUi(self)

        self.__deviceModel = DeviceModel(self)
        self.__folderModel = FolderModel(self)
        self.ui.deviceTreeView.setModel(self.__deviceModel)
        self.ui.folderTreeView.setModel(self.__folderModel)

        self.__deviceEventCatched = ServerEventCatcher(
            [sth.DevicesConfigurationEvent])
        self.__deviceEventCatched.event_arrived.connect(self.process_event)
        self.__folderEventCatched = ServerEventCatcher(
            [sth.FoldersConfigurationEvent])
        self.__folderEventCatched.event_arrived.connect(self.process_event)

        self.__setterMutex = QMutex()
        self.__server = None

    def set_server(self, server: lserver.Server):
        self.__setterMutex.lock()
        try:
            if self.__server is not None:
                self.__server.eventQueueEater.remove_event_provessor(
                    self.__deviceEventCatched)
                self.__server.eventQueueEater.remove_event_provessor(
                    self.__folderEventCatched)
                self.__deviceModel.clear_elements()
            self.__server = server
            if self.__server is not None:
                try:
                    for devid, device in self.__server.syncthingHandler.get_devices(
                    ).result().items():
                        self.__deviceModel.add_element(device)
                    for fid, folder in self.__server.syncthingHandler.get_folders(
                    ).result().items():
                        self.__folderModel.add_element(folder)
                except sth.ConfigNotInSyncError:
                    pass
                self.__server.eventQueueEater.add_event_processor(
                    self.__deviceEventCatched)
                self.__server.eventQueueEater.add_event_processor(
                    self.__folderEventCatched)
        finally:
            self.__setterMutex.unlock()

    @Slot(object)
    def process_event(self, event):
        """
        make sure to
        :param event:
        :return:
        """
        print('all is good', event)
        if isinstance(event, sth.DevicesAddedEvent):
            for device in event.devices():
                self.__deviceModel.add_element(device)
        elif isinstance(event, sth.DevicesRemovedEvent):
            for device in event.devices():
                self.__deviceModel.remove_element(device)
        elif isinstance(event, sth.DevicesChangedEvent) or isinstance(
                event, sth.DevicesVolatileDataChangedEvent):
            for device in event.devices():
                self.__deviceModel.update_element(device)

        elif isinstance(event, sth.FoldersAddedEvent):
            for folder in event.folders():
                self.__folderModel.add_element(folder)
        elif isinstance(event, sth.FoldersRemovedEvent):
            for folder in event.folders():
                self.__folderModel.remove_element(folder)
        elif isinstance(event,
                        sth.FoldersConfigurationChangedEvent) or isinstance(
                            event, sth.FoldersVolatileDataChangedEvent):
            for folder in event.folders():
                self.__folderModel.update_element(folder)
Esempio n. 8
0
class Client_API(QObject):
    loggedIn = pyqtSignal(dict)
    login_failed = pyqtSignal(str, str)
    registered = pyqtSignal()
    registration_failed = pyqtSignal(str, str)
    timeout_error = pyqtSignal()

    request_timeout = 10
    read_timeout = 60

    def __init__(self,
                 cfg,
                 tracker=None,
                 server_addr=None,
                 events_server_addr=None,
                 sharing_server_addr=None,
                 upload_server_addr=None,
                 parent=None):

        logger.debug("Initializing API server client...")
        QObject.__init__(self, parent=parent)

        self._sessions = dict()

        self.server_addr = server_addr if server_addr \
            else API_URI.format(cfg.host)
        self.events_server_addr = events_server_addr if events_server_addr \
            else API_EVENTS_URI.format(cfg.host)
        self.sharing_server_addr = sharing_server_addr if sharing_server_addr \
            else API_SHARING_URI.format(cfg.host)
        self.upload_server_addr = upload_server_addr if upload_server_addr \
            else API_UPLOAD_URI.format(cfg.host)
        self._tracker = tracker
        self.ip_addr = None
        self.cfg = cfg
        self.node_sign = None
        self._ip_lock = QMutex(parent=self)
        self._os_name, self._is_server = get_os_name_and_is_server()

    def emit_loggedIn(self, user_email, password_hash, user_id, node_id,
                      servers, license_type, remote_actions, last_event_uuid):
        '''
        Prepares data and emits loggedIn signal

        @param server Info on servers as returned by API server [dict]
        '''

        data = {
            'user_email': user_email,
            'password_hash': password_hash,
            'user_hash': self.cfg.user_hash,
            'node_hash': self.cfg.node_hash,
            'user_id': user_id,
            'node_id': node_id,
            'servers': servers,
            'license_type': license_type,
            'remote_actions': remote_actions,
            'last_event_uuid': last_event_uuid,
        }

        # Emit signal
        self.loggedIn.emit(data)

    def signup(self, fullname, email, password):
        """
        Registers new user on the API server

        @param fullname Users full name [unicode]
        @param email Users email address [str]
        @param password Users password [str]
        """
        signed, res = self._signup(fullname, email, password)

        if not signed:
            if res and 'errcode' in res and \
                    res['errcode'] == 'ERROR_NODEHASH_EXIST':
                self.cfg.set_settings(
                    {'node_hash': sha512(str(uuid4())).hexdigest()})
                return self.signup(fullname, email, password)

        if signed:
            self.registered.emit()

            if self._tracker:
                self._tracker.session_signup()
        else:
            error = 'Network Error' if res is None else res['errcode']
            self.registration_failed.emit(
                error,
                res.get('info', 'Unknown error') if res else 'Unknown error')

            if self._tracker:
                self._tracker.session_signup_failed(error)

        return signed, res

    def _signup(self, fullname, email, password):
        if self._tracker:
            self._tracker.session_signup_start()

        data = {
            'node_devicetype': 'desktop',
            'node_ostype': get_platform(),
            'node_osname': self._os_name,
            'node_name': get_device_name(),
            'fullname': fullname,
            'user_email': email,
            'user_password': password,
        }
        signed = False

        _res = self.create_request(action='signup', data=data)

        if _res and "success" in _res['result'] and 'user_hash' in _res:
            self.cfg.set_settings({'user_hash': _res['user_hash']})
            signed = True
            logger.info("Registered successfully")

        return signed, _res

    def login(self, login=None, password=None, user_hash=None):
        """
        Authorizes user on the API server

        @param login Users email address [str]
        @param password Users password [str]
        """
        logged_in, res = self._login(login, password, user_hash)

        if logged_in:
            license_type = res.get('license_type', None)
            license_type = license_type_constant_from_string(license_type)
            remote_actions = res.get('remote_actions', None)
            servers = list(map(self._strip_server_info, res.get('servers',
                                                                [])))
            try:
                logger.verbose("Servers: '%s'", servers)
            except AttributeError:
                pass
            res['servers'] = servers
            # Signalize on successful logging in
            self.emit_loggedIn(
                user_email=login if login else self.cfg.user_email,
                password_hash=password
                if password else self.cfg.user_password_hash,
                user_id=res.get('user_id', None),
                node_id=res.get('node_id', None),
                servers=servers,
                license_type=license_type,
                remote_actions=remote_actions,
                last_event_uuid=res.get('last_event_uuid', None))

            if self._tracker:
                self._tracker.session_login(license_type)
        else:
            error = 'Network Error' if res is None else res['errcode']
            self.login_failed.emit(
                error,
                res.get('info', 'Unknown error') if res else 'Unknown error')

            if self._tracker:
                self._tracker.session_login_failed(error)

        return logged_in, res

    def _login(self, login, password, user_hash):
        data = {
            'node_devicetype': 'desktop',
            'node_ostype': get_platform(),
            'node_osname': self._os_name,
            'node_name': get_device_name(),
            'user_email': login,
            'user_password': password,
            'user_hash': user_hash,
            'is_server': self._is_server,
        }

        # Signalize login attempt started (for statistics)
        if self._tracker:
            self._tracker.session_login_start()

        logged_in = False
        _res = self.create_request(action='login', data=data)
        if _res is None:
            if self._tracker:
                self._tracker.session_login_failed("response is None")
            return logged_in, _res
        if 'user_hash' not in _res:
            if self._tracker:
                self._tracker.session_login_failed("missing user_hash")
            logger.error("Server not returned user_hash")
            return logged_in, _res
        # Registered successfully
        if "success" in _res['result']:
            logged_in = True
            self.cfg.set_settings({'user_hash': _res['user_hash']})
            logger.info("Logged in successfully")

        return logged_in, _res

    def _strip_server_info(self, server_info):
        return dict(map(self._strip_info, server_info.items()))

    def _strip_info(self, item):
        key, value = item
        if isinstance(value, str):
            value = value.strip()
        return key, value

    def logout(self):
        self.create_request(action='logout')

    def generate_node_sign(self):
        """
        Returns node_sign parameter to be passed to API server

        @return Node sign value [str]
        """

        s = str(self.cfg.node_hash + str(self.ip_addr))
        s = s.encode()
        s = sha512(s)
        return s.hexdigest()

    def update_node_sign(self):
        self.update_ip()
        self.node_sign = self.generate_node_sign()

    def change_password(self, old_password, new_password):
        data = {'old_password': old_password, 'new_password': new_password}
        return self.create_request(action='changepassword', data=data)

    def get_ip(self):
        data = {'get': 'candidate'}
        encoded = self.make_json_data(action='stun', data=data)
        response = self.make_post(url=self.server_addr, data=encoded)
        try:
            response = json.loads(response)
            if response['result'] != 'success':
                return None
        except:
            return None
        try:
            return int(response['info'])
        except Exception as e:
            logger.error("Invalid response '%s' (%s)", e, response)
            return None

    def update_ip(self):
        self._ip_lock.lock()
        try:
            self.ip_addr = self.get_ip()
        finally:
            self._ip_lock.unlock()

    @staticmethod
    def make_json_data(action, data=()):
        '''
        Retuns complete request data encoded in JSON format
        @param action Name of request action [str]
        @param data Request data [dict]
        @return JSON encoded request data [string]
        '''

        data = {} if not data else data
        try:
            encoded = json.dumps({'action': action, 'data': data})
        except Exception as e:
            logger.error(
                "Failed to encoded request '%s' data %s into JSON format (%s)",
                action, repr(data), e)
            raise
        return encoded

    def get_request_data(self, action, data=(), force_update=False):
        """
        Add auth data to request data given and return its JSON encoded version

        @param action Request data 'action' field [string]
        @param data  Request data 'data' field [dict]
        @return JSON-encoded request data [string]
        """
        data = {} if not data else data
        if not self.node_sign or force_update:
            self.update_node_sign()

        # Add auth data
        if action not in ('signup', 'login'):
            data['user_hash'] = self.cfg.user_hash

        data['node_hash'] = self.cfg.node_hash
        data['node_sign'] = self.node_sign

        return self.make_json_data(action=action, data=data)

    def get_upload_request_data(self, fields, callback, force_update=False):
        """
        Add auth data to upload request fields given and return
        multipart/form data object

        @param fields Request data 'fields' [dict]
        @param callback Callback to be called on file chunk read [Function]
        @param force_update Instructs to update node sign [bool]
        @return multipart/form data object [MultipartEncoderMonitor]
        """

        if not self.node_sign or force_update:
            self.update_node_sign()

        # Add auth data
        fields['user_hash'] = self.cfg.user_hash
        fields['node_hash'] = self.cfg.node_hash
        fields['node_sign'] = self.node_sign

        e = encoder.MultipartEncoder(fields)
        m = encoder.MultipartEncoderMonitor(e, callback)
        return m

    def _create_request(self,
                        action,
                        server_addr,
                        data=(),
                        recurse_max=3,
                        enrich_data=True,
                        headers=()):
        data = {} if not data else data
        encoded = self.get_request_data(action, data) \
            if enrich_data else data

        try:
            response = self.make_post(url=server_addr,
                                      data=encoded,
                                      headers=headers)
            response = json.loads(response)
        except Exception as e:
            logger.error("Server response parsing failed with '%s'", e)
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, str(e))
            return None
        except:
            return None
        if 'result' not in response:
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, 'Result not in response')
            if recurse_max > 0:
                self.update_node_sign()
                return self._create_request(action=action,
                                            server_addr=server_addr,
                                            data=data,
                                            recurse_max=recurse_max - 1)
            else:
                return None
        if response['result'] == 'error':
            info = response.get('info', "")
            errcode = response.get('errcode', "")
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                error = 'info: {}, debug: {}'.format(info,
                                                     response.get('debug', ""))
                self._tracker.error(tb, error)
            if info == 'flst' and recurse_max > 0:
                self.update_node_sign()
                return self._create_request(action=action,
                                            server_addr=server_addr,
                                            data=data,
                                            recurse_max=recurse_max - 1)
            if errcode in ('SIGNATURE_INVALID', 'NODE_SIGN_NOT_FOUND') and \
                    recurse_max > 0:
                self.update_node_sign()
                return self._create_request(action=action,
                                            server_addr=server_addr,
                                            data=data,
                                            recurse_max=recurse_max - 1)
            if errcode == 'TIMEOUT':
                self.timeout_error.emit()
                return None
            else:
                return response
        return response

    def create_request(self, action, data=(), recurse_max=5):
        return self._create_request(action, self.server_addr, data,
                                    recurse_max)

    def create_event_request(self, action, data=(), recurse_max=3):
        return self._create_request(action, self.events_server_addr, data,
                                    recurse_max)

    def create_sharing_request(self, action, data=(), recurse_max=3):
        return self._create_request(action, self.sharing_server_addr, data,
                                    recurse_max)

    def create_upload_request(self, data, recurse_max=3):
        headers = {'Content-Type': data.content_type}
        return self._create_request("",
                                    self.upload_server_addr,
                                    data,
                                    recurse_max,
                                    enrich_data=False,
                                    headers=headers)

    def make_post(self, url, data, headers=()):
        try:
            logger.verbose("Sending POST request to '%s' with data '%s'", url,
                           data)
        except AttributeError:
            pass
        session = self._get_or_create_session(current_thread())
        try:
            kwargs = dict(timeout=(self.request_timeout, self.read_timeout))
            if headers:
                kwargs['headers'] = headers
            _res = session.post(url, data, **kwargs).text
            try:
                logger.verbose("Server replied: '%s'", _res)
            except AttributeError:
                pass
        except exceptions.Timeout as e:
            logger.error("Request failed due to timeout")
            _res = '{"result":"error","errcode":"TIMEOUT"}'
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, str(e))
        except Exception as e:
            logger.error("Request failed due to %s", e)
            _res = 'failure'
            if self._tracker:
                tb = traceback.format_list(traceback.extract_stack())
                self._tracker.error(tb, str(e))
        return _res

    def _get_or_create_session(self, thread):
        session = self._sessions.get(thread, None)
        if not session:
            session = Session()
            if self.cfg.host == REGULAR_URI:
                session.mount(self.cfg.host, SslPinningAdapter())
            self._sessions[thread] = session
        return session

    def sharing_enable(self, uuid, share_ttl):
        """
        'sharing_enable' request of share registration API.
        Registers file or folder sharing on API server

        @param uuid [str]
        @param share_ttl Time sharing be valid
            (in seconds) [int]
        @return (share_link [str], share_hash [str])
        @raise Client_APIError
        """

        data = {
            'uuid': uuid,
            'share_ttl': str(share_ttl),
            'share_password': None
        }
        response = self.create_sharing_request(action='sharing_enable',
                                               data=data)

        if response \
                and 'result' in response \
                and 'success' in response['result'] \
                and 'data' in response \
                and 'share_link' in response['data'] \
                and 'share_hash' in response['data']:
            share_link = str(response['data']['share_link'])
            share_hash = str(response['data']['share_hash'])
            return share_link, share_hash, ''
        elif response \
                and 'result' in response \
                and 'error' in response['result']:
            error_info = response.get('info', '')
            logger.error("Sharing failed, server response: '%s'", response)
            return None, None, error_info
        else:
            logger.error("Sharing failed, server response: '%s'", response)
            raise Client_APIError("Sharing failed", response)

    def sharing_disable(self, uuid):
        """
        'sharing_disable' request of share registration API.
        Registers file folder sharing cancelling on API server

        @param uuid [str]
        @raise Client_APIError
        """

        data = {
            'uuid': uuid,
        }
        response = self.create_sharing_request(action='sharing_disable',
                                               data=data)
        if not (response and 'result' in response
                and 'success' in response['result']):
            raise Client_APIError("Sharing failed", response)

    def file_event_create(self, event_uuid, file_name, file_size, folder_uuid,
                          diff_file_size, file_hash):
        '''
        'file_event_create' request of file event registration API.
        Registers file creation on API server

        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_name": ensure_unicode(file_name),
            "file_size": file_size,
            "folder_uuid": folder_uuid if folder_uuid else "",
            "diff_file_size": diff_file_size,
            "hash": file_hash,
        }

        return self.create_event_request(action='file_event_create', data=data)

    def file_event_update(self, event_uuid, file_uuid, file_size,
                          last_event_id, diff_file_size, rev_diff_file_size,
                          file_hash):
        '''
        'file_event_update' request of file event registration API.
        Registers file update on API server

        @param file_uuid UUID of file assigned on event creation [string]
        @param last_event_id Maximum ID of file event on the file being
            processed known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_uuid": file_uuid,
            "file_size": file_size,
            "last_event_id": str(last_event_id),
            "diff_file_size": diff_file_size,
            "rev_diff_file_size": rev_diff_file_size,
            "hash": file_hash,
        }
        return self.create_event_request(action='file_event_update', data=data)

    def file_event_delete(self, event_uuid, file_uuid, last_event_id):
        '''
        'file_event_delete' request of file event registration API.
        Registers file deletion on API server

        @param file_uuid UUID of file assigned on event creation [string]
        @param last_event_id Maximum ID of file event on the file being
            processed known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_uuid": file_uuid,
            "last_event_id": str(last_event_id)
        }
        return self.create_event_request(action='file_event_delete', data=data)

    def file_event_move(self, event_uuid, file_uuid, last_event_id,
                        new_file_name, new_folder_uuid):
        '''
        'file_event_move' request of file event registration API.
        Registers file moving on API server

        @param file_uuid UUID of file assigned on event creation [string]
        @param last_event_id Maximum ID of file event on the file being
            processed known to the node
        @param new_file_name New name of file [unicode]
        @param new_folder_uuid uuid of a folder where file will be placed
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {
            "event_uuid": event_uuid,
            "file_uuid": file_uuid,
            "new_folder_uuid": new_folder_uuid if new_folder_uuid else "",
            "new_file_name": new_file_name,
            "last_event_id": str(last_event_id)
        }
        return self.create_event_request(action='file_event_move', data=data)

    def folder_event_create(self, event_uuid, folder_name, parent_folder_uuid):

        if parent_folder_uuid is None:
            parent_folder_uuid = ""

        data = {
            "event_uuid": event_uuid,
            "folder_name": folder_name,
            "parent_folder_uuid": parent_folder_uuid,
        }
        return self.create_event_request(action='folder_event_create',
                                         data=data)

    def folder_event_move(self, event_uuid, folder_uuid, last_event_id,
                          new_folder_name, new_parent_folder_uuid):

        if new_parent_folder_uuid is None:
            new_parent_folder_uuid = ""

        data = {
            "event_uuid": event_uuid,
            "folder_uuid": folder_uuid,
            "new_folder_name": new_folder_name,
            "new_parent_folder_uuid": new_parent_folder_uuid,
            "last_event_id": str(last_event_id),
        }
        return self.create_event_request(action='folder_event_move', data=data)

    def folder_event_delete(self, event_uuid, folder_uuid, last_event_id):
        data = {
            "event_uuid": event_uuid,
            "folder_uuid": folder_uuid,
            "last_event_id": str(last_event_id),
        }
        return self.create_event_request(action='folder_event_delete',
                                         data=data)

    def file_event_get_filelist(self, last_event_id):
        '''
        'file_list' request of file event registration API.
        Obtains list of files created/updated after event with given ID

        @param last_event_id Maximum ID of file event known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {"last_event_id": str(last_event_id)}

        return self.create_event_request(action='file_list', data=data)

    def file_event_get_events(self, last_event_id):
        '''
        'file_events' request of file event registration API.
        Obtains events registered on server after event with given ID

        @param last_event_id Maximum ID of file event known to the node
        @return Server reply in the form
            {'result': status, 'info': server_message, data: useful_data}
        '''

        data = {"last_event_id": str(last_event_id)}

        return self.create_event_request(action='file_events', data=data)

    def start_http_download(self, upload_id):
        """
        'download' request for starting of uploaded file download via HTTP
        protocol. After node auth checking the server should return redirect to
        actual file URL

        @param upload_id ID of uploaded file [int]
        """

        data = {"upload_id": str(upload_id)}

        return self.create_event_request(action='download', data=data)

    def remote_action_done(self, remote_action_uuid):
        return self.create_request(action='remote_action_done',
                                   data=dict(action_uuid=remote_action_uuid))

    def patch_ready(self, patch_uuid, patch_size):
        return self.create_event_request(action='patch_ready',
                                         data=dict(diff_uuid=patch_uuid,
                                                   diff_size=patch_size))

    def get_token_login_link(self):
        return self.create_request(action='get_token_login_link')

    def node_management_action(self, action, node_id, action_type=""):
        if action_type:
            data = dict(target_node_id=node_id, action_type=action_type)
        else:
            data = dict(node_id=node_id)
        return self.create_request(action=action, data=data)

    def get_notifications(self, limit, from_id):
        data = {'from': from_id, 'limit': limit}
        return self.create_request(action="getNotifications", data=data)

    def accept_invitation(self, colleague_id):
        data = dict(colleague_id=colleague_id)
        return self.create_sharing_request(action="collaboration_join",
                                           data=data)

    def set_uris(self):
        self.server_addr = API_URI.format(self.cfg.host)
        self.events_server_addr = API_EVENTS_URI.format(self.cfg.host)
        self.sharing_server_addr = API_SHARING_URI.format(self.cfg.host)
        self.upload_server_addr = API_UPLOAD_URI.format(self.cfg.host)

    # Collaboration settings requests

    def collaboration_info(self, uuid):
        data = dict(uuid=uuid)
        return self.create_sharing_request(action="collaboration_info",
                                           data=data)

    def colleague_delete(self, uuid, colleague_id):
        data = dict(uuid=uuid, colleague_id=colleague_id)
        return self.create_sharing_request(action="colleague_delete",
                                           data=data)

    def colleague_edit(self, uuid, colleague_id, access_type):
        data = dict(uuid=uuid,
                    colleague_id=colleague_id,
                    access_type=access_type)
        return self.create_sharing_request(action="colleague_edit", data=data)

    def colleague_add(self, uuid, colleague_email, access_type):
        data = dict(uuid=uuid,
                    colleague_email=colleague_email,
                    access_type=access_type)
        return self.create_sharing_request(action="colleague_add", data=data)

    def collaboration_cancel(self, uuid):
        data = dict(uuid=uuid)
        return self.create_sharing_request(action="collaboration_cancel",
                                           data=data)

    def collaboration_leave(self, uuid):
        data = dict(uuid=uuid)
        return self.create_sharing_request(action="collaboration_leave",
                                           data=data)

    # Support requests

    def send_support_message(self, subject, body, log_file_name):
        data = dict(subject=subject, body=body)
        if log_file_name:
            data['log_file_name'] = log_file_name
        return self.create_request(action="support", data=data)

    def upload_file(self, path, mime_type, callback=lambda m: None):
        filename = op.basename(path)
        with open(path, 'rb') as f:
            fields = {'UploadLogsForm[uploadedFile]': (filename, f, mime_type)}
            data = self.get_upload_request_data(fields, callback)
            return self.create_upload_request(data)
Esempio n. 9
0
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.__activeCount: int = 0  # number of operations in progress
        centralWidget = MainWidget()
        self.setCentralWidget(centralWidget)
        self.notifier = None  # Set by main script
        # Initialise a thread pool
        self.threadPool = QThreadPool.globalInstance()
        logging.info('Multithreading with maximum {} threads'.format(
            self.threadPool.maxThreadCount()))
        self.setWindowTitle('dataMole')

        self.setUpMenus()
        self.__spinnerMutex = QMutex()

        centralWidget.frameInfoPanel.operationRequest.connect(
            self.executeOperation)
        centralWidget.workbenchView.selectedRowChanged[str, str].connect(
            self.changedSelectedFrame)

    def moveEvent(self, event: QtGui.QMoveEvent) -> None:
        self.notifier.mNotifier.updatePosition()
        super().moveEvent(event)

    def resizeEvent(self, event: QtGui.QResizeEvent) -> None:
        self.notifier.mNotifier.updatePosition()
        super().resizeEvent(event)

    @Slot()
    def openComparePanel(self) -> None:
        w = self.centralWidget().workbenchModel
        dv = DataframeSideBySideView(self)
        dv.setWindowFlags(Qt.Window)
        dv.setAttribute(Qt.WA_DeleteOnClose)
        dv.setWindowTitle('Side by side view')
        dv.dataWidgetL.setWorkbench(w)
        dv.dataWidgetR.setWorkbench(w)
        if w.rowCount():
            dv.dataWidgetL.setDataframe(dv.dataWidgetL.inputCB.currentText())
            dv.dataWidgetR.setDataframe(dv.dataWidgetR.inputCB.currentText())
        dv.show()

    # @Slot()
    # def openDiffPanel(self) -> None:
    #     TODO: remove this?
    #     dv = DiffDataframeWidget(self)
    #     dv.setWindowFlags(Qt.Window)
    #     dv.setAttribute(Qt.WA_DeleteOnClose)
    #     dv.setWindowTitle('Diff view')
    #     dv.setWorkbench(self.centralWidget().workbenchModel)
    #     dv.show()

    @Slot(str, str)
    def changedSelectedFrame(self, newName: str, _: str) -> None:
        # Slot called when workbench selection change
        self.aWriteCsv.setOperationArgs(w=self.centralWidget().workbenchModel,
                                        frameName=newName)
        self.aWritePickle.setOperationArgs(
            w=self.centralWidget().workbenchModel, frameName=newName)

    @Slot(int, str)
    def operationStateChanged(self, uid: int, state: str) -> None:
        if state == 'success':
            logging.info('Operation uid={:d} succeeded'.format(uid))
            self.statusBar().showMessage('Operation succeeded', 10000)
        elif state == 'error':
            logging.error('Operation uid={:d} stopped with errors'.format(uid))
            self.statusBar().showMessage('Operation stopped with errors',
                                         10000)
        elif state == 'start':
            self.__spinnerMutex.lock()
            self.__activeCount += 1
            self.__spinnerMutex.unlock()
            self.statusBar().startSpinner()
            logging.info('Operation uid={:d} started'.format(uid))
            self.statusBar().showMessage('Executing...', 10000)
        elif state == 'finish':
            logging.info('Operation uid={:d} finished'.format(uid))
            self.__spinnerMutex.lock()
            self.__activeCount -= 1
            if self.__activeCount == 0:
                self.statusBar().stopSpinner()
            self.__spinnerMutex.unlock()
        # print('Emit', uid, state, 'count={}'.format(self.__activeCount))

    @Slot(type)
    def executeOperation(self, opType: type) -> None:
        action = OperationAction(opType, self, opType.name(),
                                 self.rect().center(),
                                 self.centralWidget().workbenchModel)
        # Set selected frame in the input combo box of the action
        selection = self.centralWidget().workbenchView.selectedIndexes()
        if selection:
            selectedFrame: str = selection[0].data(Qt.DisplayRole)
            action.setSelectedFrame(selectedFrame)
        # Delete action when finished
        action.stateChanged.connect(self.operationStateChanged)
        action.stateChanged.connect(self.deleteAction)
        # Start operation
        action.trigger()

    @Slot(int, str)
    def deleteAction(self, uid: int, state: str):
        if state == 'finish':
            action: QAction = self.sender()
            action.deleteLater()
            logging.info(
                'Action for operation uid={:d} scheduled for deletion'.format(
                    uid))

    def setUpMenus(self) -> None:
        menuBar = QMenuBar()
        fileMenu = menuBar.addMenu('File')
        importMenu = fileMenu.addMenu('Import')
        exportMenu = fileMenu.addMenu('Export')
        flowMenu = menuBar.addMenu('Flow')
        viewMenu = menuBar.addMenu('View')
        helpMenu = menuBar.addMenu('Help')
        aAppendEmpty = QAction('Add frame', fileMenu)
        aAppendEmpty.setStatusTip('Create an empty dataframe in the workbench')
        aQuit = QAction('Quit', fileMenu)
        aLoadCsv = OperationAction(CsvLoader, fileMenu, 'From csv',
                                   self.rect().center(),
                                   self.centralWidget().workbenchModel)
        self.aWriteCsv = OperationAction(CsvWriter,
                                         fileMenu,
                                         'To csv',
                                         self.rect().center(),
                                         w=self.centralWidget().workbenchModel)
        aLoadPickle = OperationAction(PickleLoader, fileMenu, 'From pickle',
                                      self.rect().center(),
                                      self.centralWidget().workbenchModel)
        self.aWritePickle = OperationAction(
            PickleWriter,
            fileMenu,
            'To pickle',
            self.mapToGlobal(self.rect().center()),
            w=self.centralWidget().workbenchModel)
        aCompareFrames = QAction('Compare dataframes', viewMenu)
        aLogDir = QAction('Open log directory', helpMenu)
        aClearLogs = QAction('Delete old logs', helpMenu)
        fileMenu.addActions([aAppendEmpty, aQuit])
        exportMenu.addActions([self.aWriteCsv, self.aWritePickle])
        importMenu.addActions([aLoadCsv, aLoadPickle])

        self._aStartFlow = QAction('Execute', flowMenu)
        self._aResetFlow = QAction('Reset', flowMenu)
        aSaveFlow = QAction('Save', flowMenu)
        aLoadFlow = QAction('Load', flowMenu)
        flowMenu.addActions(
            [self._aStartFlow, self._aResetFlow, aSaveFlow, aLoadFlow])
        viewMenu.addAction(aCompareFrames)
        helpMenu.addActions([aLogDir, aClearLogs])

        self.setMenuBar(menuBar)

        # Tips
        aLoadCsv.setStatusTip('Load a csv file in the workbench')
        aLoadPickle.setStatusTip('Load a Pickle file in the workbench')
        self.aWriteCsv.setStatusTip('Write a dataframe to a csv file')
        self.aWritePickle.setStatusTip(
            'Serializes a dataframe into a pickle file')
        aCompareFrames.setStatusTip('Open two dataframes side by side')
        self._aStartFlow.setStatusTip('Start flow-graph execution')
        self._aResetFlow.setStatusTip('Reset the node status in flow-graph')
        aLogDir.setStatusTip('Open the folder containing all logs')
        aClearLogs.setStatusTip('Delete older logs and keep the last 5')

        # Connect
        aAppendEmpty.triggered.connect(
            self.centralWidget().workbenchModel.appendEmptyRow)
        aQuit.triggered.connect(self.close)
        self._aStartFlow.triggered.connect(
            self.centralWidget().controller.executeFlow)
        self._aResetFlow.triggered.connect(
            self.centralWidget().controller.resetFlowStatus)
        aCompareFrames.triggered.connect(self.openComparePanel)
        aLogDir.triggered.connect(self.openLogDirectory)
        aClearLogs.triggered.connect(self.clearLogDir)
        aSaveFlow.triggered.connect(self.saveFlow)
        aLoadFlow.triggered.connect(self.readFlow)

        aLoadCsv.stateChanged.connect(self.operationStateChanged)
        aLoadPickle.stateChanged.connect(self.operationStateChanged)
        self.aWriteCsv.stateChanged.connect(self.operationStateChanged)
        self.aWritePickle.stateChanged.connect(self.operationStateChanged)

    @Slot()
    def openLogDirectory(self) -> None:
        QDesktopServices.openUrl(
            QUrl(os.path.join(os.getcwd(), flogging.LOG_FOLDER)))

    @Slot()
    def clearLogDir(self) -> None:
        flogging.deleteOldLogs()
        gui.statusBar.showMessage('Logs cleared')

    @Slot()
    def saveFlow(self):
        """ Dump a pipeline in a pickle file """
        path, ext = QFileDialog.getSaveFileName(self, 'Save flow graph')
        if path:
            path = getFileNameWithExtension(path, ext)
            serialization = self.centralWidget().graph.serialize()
            # Add info about scene position to node data
            nodeSerialization: Dict[int, Dict] = serialization['nodes']
            for nodeId, data in nodeSerialization.items():
                data['pos'] = self.centralWidget(
                ).graphScene.nodesDict[nodeId].scenePos().toTuple()
            with open(path, 'wb') as file:
                pickle.dump(serialization, file)
            gui.statusBar.showMessage(
                'Pipeline was successfully exported in {:s}'.format(path), 15)

    @Slot()
    def readFlow(self):
        """ Load a pipeline from a pickle file """
        path, ext = QFileDialog.getOpenFileName(
            self, 'Open flow graph', filter='Pickle (*.pickle);;All files (*)')
        if path:
            try:
                with open(path, 'rb') as file:
                    serialization: Dict = pickle.load(file)
                graph = flow.dag.OperationDag.deserialize(serialization)
            except pickle.PickleError as e:
                gui.notifier.addMessage('Pickle error', str(e),
                                        QMessageBox.Critical)
            except exc.DagException as e:
                gui.notifier.addMessage('Error while creating pipeline',
                                        str(e), QMessageBox.Critical)
            else:
                # Add the workbench
                g = graph.getNxGraph()
                for nodeId in g.nodes:
                    g.nodes[nodeId][
                        'op'].operation._workbench = self.centralWidget(
                        ).workbenchModel
                self.centralWidget().createNewFlow(graph)
                self.centralWidget().controller.showGraphInScene()
                # Set position of every node
                nodeDict = serialization['nodes']
                for nodeId, node in self.centralWidget(
                ).graphScene.nodesDict.items():
                    pos: Tuple[float, float] = nodeDict[nodeId]['pos']
                    node.setPos(*pos)
                    node.refresh()  # Update connected edges
                # Reconnect actions to the new controller
                self._aStartFlow.triggered.connect(
                    self.centralWidget().controller.executeFlow)
                self._aResetFlow.triggered.connect(
                    self.centralWidget().controller.resetFlowStatus)
                gui.statusBar.showMessage('Pipeline was successfully imported',
                                          15)
Esempio n. 10
0
class FrameModel(QAbstractTableModel):
    """ Table model for a single dataframe """

    # This role returns frame header data as a tuple (Name, Type)
    DataRole = Qt.UserRole
    statisticsComputed = Signal(tuple)
    statisticsError = Signal(tuple)

    def __init__(self,
                 parent: QWidget = None,
                 frame: Union[Frame, Shape] = Frame()):
        super().__init__(parent)
        if isinstance(frame, Frame):
            self.__frame: Frame = frame
            self.__shape: Shape = self.__frame.shape
        elif isinstance(frame, Shape):  # it's a Shape
            self.__frame: Frame = Frame()
            self.__shape: Shape = frame
        else:
            self.__frame: Frame = Frame()
            self.__shape: Shape = Shape()
        # Dictionary { attributeIndex: value }
        self._statistics: Dict[int, Dict[str, object]] = dict()
        self._histogram: Dict[int, Dict[Any, int]] = dict()
        # Dataframe name
        self.name: str = ''
        # Set of alive workers by identifier (attribute number, type, operation)
        self._runningWorkers: Set[Tuple] = set()
        self._dataAccessMutex = QMutex()

    @property
    def frame(self) -> Frame:
        return self.__frame

    @property
    def shape(self) -> Shape:
        return self.__shape

    def setFrame(self, frame: Frame) -> None:
        self.beginResetModel()
        self.__frame = frame
        self.__shape: Shape = self.__frame.shape
        self._statistics = dict()
        self._histogram = dict()
        self.endResetModel()

    def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
        if parent.isValid():
            return 0
        return self.frame.nRows  # True number of rows

    def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
        if parent.isValid():
            return 0
        return self.shape.nColumns

    def data(self, index: QModelIndex, role: int = ...) -> Any:
        if index.isValid():
            if role == Qt.DisplayRole:
                return str(self.__frame.getRawFrame().iloc[index.row(),
                                                           index.column()])
        return None

    def headerData(self,
                   section: int,
                   orientation: Qt.Orientation,
                   role: int = Qt.DisplayRole) -> Any:
        if orientation == Qt.Horizontal:
            if role == Qt.DisplayRole:
                return self.__shape.colNames[
                    section] + '\n' + self.__shape.colTypes[section].name
            elif role == FrameModel.DataRole:
                return self.__shape.colNames[section], self.__shape.colTypes[
                    section]
        # Vertical header shows row number
        elif orientation == Qt.Vertical and role == Qt.DisplayRole:
            return section
        return None

    def setHeaderData(self, section: int, orientation: Qt.Orientation, value: Any, role: int = ...) \
            -> bool:
        """ Change column name """
        if orientation == Qt.Horizontal and role == Qt.EditRole and section < self.columnCount(
        ):
            names = self.__frame.colnames
            names[section] = value
            self.__frame = self.__frame.rename({
                self.headerData(section, orientation, FrameModel.DataRole)[0]:
                value
            })
            self.__shape.colNames[section] = value
            self.headerDataChanged.emit(orientation, section, section)
            return True
        return False

    @property
    def statistics(self) -> Dict[int, Dict[str, object]]:
        return self._statistics

    @property
    def histogram(self) -> Dict[int, Dict[Any, int]]:
        return self._histogram

    def existsRunningTask(self, identifier) -> bool:
        # Check if a task is already running for this attribute
        exists: bool = False
        if identifier in self._runningWorkers:
            exists = True
        return exists

    def computeStatistics(self, attribute: int) -> None:
        """ Compute statistics for a given attribute """
        flogging.appLogger.debug(
            'computeStatistics() called, attribute {:d}'.format(attribute))
        attType = self.__shape.colTypes[attribute]
        identifier = (attribute, attType, 'stat')
        if self.__frame.nRows == 0:
            return self.onWorkerError((attribute, attType, 'stat'), tuple())
        # Check if a task is already running for this attribute
        if self.existsRunningTask(identifier):
            return
        # Create a new task
        stats = AttributeStatistics()
        stats.setOptions(attribute=attribute)
        statWorker = Worker(stats,
                            args=(self.__frame, ),
                            identifier=identifier)
        rc = statWorker.signals.result.connect(self.onWorkerSuccess,
                                               Qt.DirectConnection)
        ec = statWorker.signals.error.connect(self.onWorkerError,
                                              Qt.DirectConnection)
        fc = statWorker.signals.finished.connect(self.onWorkerFinished,
                                                 Qt.DirectConnection)
        flogging.appLogger.debug(
            'Connected stat worker: {:b}, {:b}, {:b}'.format(rc, ec, fc))
        # Remember which computations are already in progress
        self._runningWorkers.add(identifier)
        QThreadPool.globalInstance().start(statWorker)

    def computeHistogram(self, attribute: int, histBins: int) -> None:
        flogging.appLogger.debug(
            'computeHistogram() called, attribute {:d}'.format(attribute))
        attType = self.__shape.colTypes[attribute]
        identifier = (attribute, attType, 'hist')
        if self.__frame.nRows == 0:
            return self.onWorkerError((attribute, attType, 'hist'), tuple())
        # Check if a task is already running for this attribute
        if self.existsRunningTask(identifier):
            return
        # Create a new task
        hist = Hist()
        hist.setOptions(attribute=attribute, attType=attType, bins=histBins)
        histWorker = Worker(hist, args=(self.__frame, ), identifier=identifier)
        rc = histWorker.signals.result.connect(self.onWorkerSuccess,
                                               Qt.DirectConnection)
        ec = histWorker.signals.error.connect(self.onWorkerError,
                                              Qt.DirectConnection)
        fc = histWorker.signals.finished.connect(self.onWorkerFinished,
                                                 Qt.DirectConnection)
        flogging.appLogger.debug(
            'Connected hist worker: {:b}, {:b}, {:b}'.format(rc, ec, fc))
        # Remember computations in progress
        self._runningWorkers.add(identifier)
        QThreadPool.globalInstance().start(histWorker)

    @Slot(object, object)
    def onWorkerSuccess(self, identifier: Tuple[int, Type, str],
                        result: Dict[Any, Any]) -> None:
        attribute, attType, mode = identifier
        if mode == 'stat':
            self._dataAccessMutex.lock()
            self._statistics[attribute] = result
            self._dataAccessMutex.unlock()
            flogging.appLogger.debug('Statistics computation succeeded')
        elif mode == 'hist':
            self._dataAccessMutex.lock()
            self._histogram[attribute] = result
            self._dataAccessMutex.unlock()
            flogging.appLogger.debug('Histogram computation succeeded')
        self.statisticsComputed.emit(identifier)

    @Slot(object, tuple)
    def onWorkerError(self, identifier: Tuple[int, Type, str],
                      error: Tuple[type, Exception, str]) -> None:
        if error:
            flogging.appLogger.error(
                'Statistics computation (mode "{}") failed with {}: {}\n{}'.
                format(identifier[2], *error))
        self.statisticsError.emit(identifier)

    @Slot(object)
    def onWorkerFinished(self, identifier: Tuple[int, Type, str]) -> None:
        flogging.appLogger.debug('Worker (mode "{}") finished'.format(
            identifier[2]))
        self._runningWorkers.remove(identifier)
Esempio n. 11
0
class RenderThread(QThread):
    ColormapSize = 512

    renderedImage = Signal(QImage, float)

    def __init__(self, parent=None):
        super(RenderThread, self).__init__(parent)

        self.mutex = QMutex()
        self.condition = QWaitCondition()
        self.centerX = 0.0
        self.centerY = 0.0
        self.scaleFactor = 0.0
        self.resultSize = QSize()
        self.colormap = []

        self.restart = False
        self.abort = False

        for i in range(RenderThread.ColormapSize):
            self.colormap.append(
                self.rgbFromWaveLength(380.0 + (i * 400.0 /
                                                RenderThread.ColormapSize)))

    def stop(self):
        self.mutex.lock()
        self.abort = True
        self.condition.wakeOne()
        self.mutex.unlock()

        self.wait(2000)

    def render(self, centerX, centerY, scaleFactor, resultSize):
        locker = QMutexLocker(self.mutex)

        self.centerX = centerX
        self.centerY = centerY
        self.scaleFactor = scaleFactor
        self.resultSize = resultSize

        if not self.isRunning():
            self.start(QThread.LowPriority)
        else:
            self.restart = True
            self.condition.wakeOne()

    def run(self):
        while True:
            self.mutex.lock()
            resultSize = self.resultSize
            scaleFactor = self.scaleFactor
            centerX = self.centerX
            centerY = self.centerY
            self.mutex.unlock()

            halfWidth = resultSize.width() // 2
            halfHeight = resultSize.height() // 2
            image = QImage(resultSize, QImage.Format_RGB32)

            NumPasses = 8
            curpass = 0

            while curpass < NumPasses:
                MaxIterations = (1 << (2 * curpass + 6)) + 32
                Limit = 4
                allBlack = True

                for y in range(-halfHeight, halfHeight):
                    if self.restart:
                        break
                    if self.abort:
                        return

                    ay = 1j * (centerY + (y * scaleFactor))

                    for x in range(-halfWidth, halfWidth):
                        c0 = centerX + (x * scaleFactor) + ay
                        c = c0
                        numIterations = 0

                        while numIterations < MaxIterations:
                            numIterations += 1
                            c = c * c + c0
                            if abs(c) >= Limit:
                                break
                            numIterations += 1
                            c = c * c + c0
                            if abs(c) >= Limit:
                                break
                            numIterations += 1
                            c = c * c + c0
                            if abs(c) >= Limit:
                                break
                            numIterations += 1
                            c = c * c + c0
                            if abs(c) >= Limit:
                                break

                        if numIterations < MaxIterations:
                            image.setPixel(
                                x + halfWidth, y + halfHeight,
                                self.colormap[numIterations %
                                              RenderThread.ColormapSize])
                            allBlack = False
                        else:
                            image.setPixel(x + halfWidth, y + halfHeight,
                                           qRgb(0, 0, 0))

                if allBlack and curpass == 0:
                    curpass = 4
                else:
                    if not self.restart:
                        self.renderedImage.emit(image, scaleFactor)
                    curpass += 1

            self.mutex.lock()
            if not self.restart:
                self.condition.wait(self.mutex)
            self.restart = False
            self.mutex.unlock()

    def rgbFromWaveLength(self, wave):
        r = 0.0
        g = 0.0
        b = 0.0

        if wave >= 380.0 and wave <= 440.0:
            r = -1.0 * (wave - 440.0) / (440.0 - 380.0)
            b = 1.0
        elif wave >= 440.0 and wave <= 490.0:
            g = (wave - 440.0) / (490.0 - 440.0)
            b = 1.0
        elif wave >= 490.0 and wave <= 510.0:
            g = 1.0
            b = -1.0 * (wave - 510.0) / (510.0 - 490.0)
        elif wave >= 510.0 and wave <= 580.0:
            r = (wave - 510.0) / (580.0 - 510.0)
            g = 1.0
        elif wave >= 580.0 and wave <= 645.0:
            r = 1.0
            g = -1.0 * (wave - 645.0) / (645.0 - 580.0)
        elif wave >= 645.0 and wave <= 780.0:
            r = 1.0

        s = 1.0
        if wave > 700.0:
            s = 0.3 + 0.7 * (780.0 - wave) / (780.0 - 700.0)
        elif wave < 420.0:
            s = 0.3 + 0.7 * (wave - 380.0) / (420.0 - 380.0)

        r = pow(r * s, 0.8)
        g = pow(g * s, 0.8)
        b = pow(b * s, 0.8)

        return qRgb(r * 255, g * 255, b * 255)
Esempio n. 12
0
class SerialPortThread(QThread):
    on_data = Signal(str)
    on_error = Signal(str)
    on_stop = Signal()

    def __init__(self):
        super(SerialPortThread, self).__init__()
        self.device_path: str = ''

        self.commands_mutex = QMutex()
        self.commands: List[bytes] = []

    def run(self):
        print(self.device_path)
        try:
            s = serial.Serial(self.device_path, 115200, timeout=0.1)
            while s.isOpen():
                d = s.readline()
                try:
                    if d is not bytes():
                        print(d)
                        self.on_data.emit(d.decode('ascii'))

                    self.__send_command(s)
                except:
                    print('Error')
        except (SerialException) as e:
            self.on_error.emit(f'Serial error {e}')
        except (UnicodeDecodeError) as e:
            self.on_error.emit(f'Unicode error {e}, {traceback.format_exc()}')
        self.on_stop.emit()

    def __send_command(self, serial_port):
        self.commands_mutex.lock()
        if len(self.commands) > 0:
            command = self.commands[0]
            rest_commands = self.commands[1:]

            print(f'send command:  {command}')
            print(f'rest commands: {rest_commands}')

            self.commands = rest_commands
            print(f'1')
            serial_port.write(command)
            print(f'2')
        self.commands_mutex.unlock()

    @Slot()
    def abort(self):
        pass

    @Slot()
    def select_port(self, device_path: str):
        self.device_path = device_path

    @Slot()
    def send_command(self, command: bytes):
        self.commands_mutex.lock()
        print(f'Send command: {command}')
        self.commands += [command]
        self.commands_mutex.unlock()
Esempio n. 13
0
class FortuneThread(QThread):
    newFortune = Signal(str)

    error = Signal(int, str)

    def __init__(self, parent=None):
        super(FortuneThread, self).__init__(parent)

        self.quit = False
        self.hostName = ''
        self.cond = QWaitCondition()
        self.mutex = QMutex()
        self.port = 0

    def __del__(self):
        self.mutex.lock()
        self.quit = True
        self.cond.wakeOne()
        self.mutex.unlock()
        self.wait()

    def requestNewFortune(self, hostname, port):
        locker = QMutexLocker(self.mutex)
        self.hostName = hostname
        self.port = port
        if not self.isRunning():
            self.start()
        else:
            self.cond.wakeOne()

    def run(self):
        self.mutex.lock()
        serverName = self.hostName
        serverPort = self.port
        self.mutex.unlock()

        while not self.quit:
            Timeout = 5 * 1000

            socket = QTcpSocket()
            socket.connectToHost(serverName, serverPort)

            if not socket.waitForConnected(Timeout):
                self.error.emit(socket.error(), socket.errorString())
                return

            while socket.bytesAvailable() < 2:
                if not socket.waitForReadyRead(Timeout):
                    self.error.emit(socket.error(), socket.errorString())
                    return

            instr = QDataStream(socket)
            instr.setVersion(QDataStream.Qt_4_0)
            blockSize = instr.readUInt16()

            while socket.bytesAvailable() < blockSize:
                if not socket.waitForReadyRead(Timeout):
                    self.error.emit(socket.error(), socket.errorString())
                    return

            self.mutex.lock()
            fortune = instr.readQString()
            self.newFortune.emit(fortune)

            self.cond.wait(self.mutex)
            serverName = self.hostName
            serverPort = self.port
            self.mutex.unlock()
Esempio n. 14
0
class Motor:
    """
    Class for a motor which has a position, a name and a status
    """
    def __init__(self, mainWindow=None, name="", goalPosition=0, status=False):
        """
        Initialization
        :param mainWindow: The main window of the ui
        :param name: The name of the motor
        :param pos: The position of the motor
        :param status: the status of the motor
        """
        ## The main window of the ui
        self.__name = name
        ## The goal position of the motor
        self.__goalPosition = goalPosition
        ## The current position of the motor
        self.__currentPosition = 0
        ## The status of the motor
        self.__status = status
        ## The main window of the ui
        self.__window = mainWindow
        self.mu = QMutex()

    def setGoalPosition(self, pos):
        """
        Setter of the goal positon
        :param pos: the position
        :return: No return
        """
        self.mu.lock()
        self.__goalPosition = pos
        self.mu.unlock()
        print("%s: %d" % (self.__name, pos))
        self.__window.sendMessage('a')

    def getGoalPosition(self):
        """
        Accessor of the goal position
        :return: The goal position of the motor
        """
        self.mu.lock()
        pos = self.__goalPosition
        self.mu.unlock()
        return pos

    def setCurrentPosition(self, pos):
        """
        Setter of the current position
        :return: No return
        """
        self.mu.lock()
        self.__currentPosition = pos
        self.mu.unlock()

    def getCurrentPosition(self):
        """
        Accessor of the current position
        :return: The current position of the motor
        """
        self.mu.lock()
        pos = self.__currentPosition
        self.mu.unlock()
        return pos

    def setName(self, name):
        """
        Setter of the name of the motor
        :param name: The name of the motor
        :return: No return
        """
        self.__name = name

    def getName(self):
        """
        Accessor of the name
        :return: The name of the motor
        """
        return self.__name

    def setStatus(self, status):
        """
        Setter of the status of the motor
        :param status: The status
        :return: No return
        """
        self.__status = status

    def isEnabled(self):
        """
        Accessor of the status
        :return: The status
        """
        return self.__status
Esempio n. 15
0
class RenderThread(QThread):
    ColormapSize = 512

    renderedImage = Signal(QImage, float)

    def __init__(self, parent=None):
        super(RenderThread, self).__init__(parent)

        self.mutex = QMutex()
        self.condition = QWaitCondition()
        self.centerX = 0.0
        self.centerY = 0.0
        self.scaleFactor = 0.0
        self.resultSize = QSize()
        self.colormap = []

        self.restart = False
        self.abort = False

        for i in range(RenderThread.ColormapSize):
            self.colormap.append(self.rgbFromWaveLength(380.0 + (i * 400.0 / RenderThread.ColormapSize)))

    def stop(self):
        self.mutex.lock()
        self.abort = True
        self.condition.wakeOne()
        self.mutex.unlock()

        self.wait(2000)

    def render(self, centerX, centerY, scaleFactor, resultSize):
        locker = QMutexLocker(self.mutex)

        self.centerX = centerX
        self.centerY = centerY
        self.scaleFactor = scaleFactor
        self.resultSize = resultSize

        if not self.isRunning():
            self.start(QThread.LowPriority)
        else:
            self.restart = True
            self.condition.wakeOne()

    def run(self):
        while True:
            self.mutex.lock()
            resultSize = self.resultSize
            scaleFactor = self.scaleFactor
            centerX = self.centerX
            centerY = self.centerY
            self.mutex.unlock()

            halfWidth = resultSize.width() // 2
            halfHeight = resultSize.height() // 2
            image = QImage(resultSize, QImage.Format_RGB32)

            NumPasses = 8
            curpass = 0

            while curpass < NumPasses:
                MaxIterations = (1 << (2 * curpass + 6)) + 32
                Limit = 4
                allBlack = True

                for y in range(-halfHeight, halfHeight):
                    if self.restart:
                        break
                    if self.abort:
                        return

                    ay = 1j * (centerY + (y * scaleFactor))

                    for x in range(-halfWidth, halfWidth):
                        c0 = centerX + (x * scaleFactor) + ay
                        c = c0
                        numIterations = 0

                        while numIterations < MaxIterations:
                            numIterations += 1
                            c = c*c + c0
                            if abs(c) >= Limit:
                                break
                            numIterations += 1
                            c = c*c + c0
                            if abs(c) >= Limit:
                                break
                            numIterations += 1
                            c = c*c + c0
                            if abs(c) >= Limit:
                                break
                            numIterations += 1
                            c = c*c + c0
                            if abs(c) >= Limit:
                                break

                        if numIterations < MaxIterations:
                            image.setPixel(x + halfWidth, y + halfHeight,
                                           self.colormap[numIterations % RenderThread.ColormapSize])
                            allBlack = False
                        else:
                            image.setPixel(x + halfWidth, y + halfHeight, qRgb(0, 0, 0))

                if allBlack and curpass == 0:
                    curpass = 4
                else:
                    if not self.restart:
                        self.renderedImage.emit(image, scaleFactor)
                    curpass += 1

            self.mutex.lock()
            if not self.restart:
                self.condition.wait(self.mutex)
            self.restart = False
            self.mutex.unlock()

    def rgbFromWaveLength(self, wave):
        r = 0.0
        g = 0.0
        b = 0.0

        if wave >= 380.0 and wave <= 440.0:
            r = -1.0 * (wave - 440.0) / (440.0 - 380.0)
            b = 1.0
        elif wave >= 440.0 and wave <= 490.0:
            g = (wave - 440.0) / (490.0 - 440.0)
            b = 1.0
        elif wave >= 490.0 and wave <= 510.0:
            g = 1.0
            b = -1.0 * (wave - 510.0) / (510.0 - 490.0)
        elif wave >= 510.0 and wave <= 580.0:
            r = (wave - 510.0) / (580.0 - 510.0)
            g = 1.0
        elif wave >= 580.0 and wave <= 645.0:
            r = 1.0
            g = -1.0 * (wave - 645.0) / (645.0 - 580.0)
        elif wave >= 645.0 and wave <= 780.0:
            r = 1.0

        s = 1.0
        if wave > 700.0:
            s = 0.3 + 0.7 * (780.0 - wave) / (780.0 - 700.0)
        elif wave < 420.0:
            s = 0.3 + 0.7 * (wave - 380.0) / (420.0 - 380.0)

        r = pow(r * s, 0.8)
        g = pow(g * s, 0.8)
        b = pow(b * s, 0.8)

        return qRgb(r*255, g*255, b*255)
class FortuneThread(QThread):
    newFortune = Signal(str)

    error = Signal(int, str)

    def __init__(self, parent=None):
        super(FortuneThread, self).__init__(parent)

        self.quit = False
        self.hostName = ''
        self.cond = QWaitCondition()
        self.mutex = QMutex()
        self.port = 0

    def __del__(self):
        self.mutex.lock()
        self.quit = True
        self.cond.wakeOne()
        self.mutex.unlock()
        self.wait()

    def requestNewFortune(self, hostname, port):
        locker = QMutexLocker(self.mutex)
        self.hostName = hostname
        self.port = port
        if not self.isRunning():
            self.start()
        else:
            self.cond.wakeOne()

    def run(self):
        self.mutex.lock()
        serverName = self.hostName
        serverPort = self.port
        self.mutex.unlock()

        while not self.quit:
            Timeout = 5 * 1000

            socket = QTcpSocket()
            socket.connectToHost(serverName, serverPort)

            if not socket.waitForConnected(Timeout):
                self.error.emit(socket.error(), socket.errorString())
                return

            while socket.bytesAvailable() < 2:
                if not socket.waitForReadyRead(Timeout):
                    self.error.emit(socket.error(), socket.errorString())
                    return

            instr = QDataStream(socket)
            instr.setVersion(QDataStream.Qt_4_0)
            blockSize = instr.readUInt16()

            while socket.bytesAvailable() < blockSize:
                if not socket.waitForReadyRead(Timeout):
                    self.error.emit(socket.error(), socket.errorString())
                    return

            self.mutex.lock()
            fortune = instr.readQString()
            self.newFortune.emit(fortune)

            self.cond.wait(self.mutex)
            serverName = self.hostName
            serverPort = self.port
            self.mutex.unlock()
Esempio n. 17
0
class Operator(QObject):

    currentConfig = None
    processHandles = {}
    command = Signal(object)  # command

    def __init__(self):
        super(Operator, self).__init__()

        logging.debug('Operator::__init__() called')
        self.threadpool = QThreadPool.globalInstance()
        self.procHandleMutex = QMutex()

        self._startAll = OperatorStartAll(self)

        self.identGenMutex = QMutex()
        self.n_ident = 0

    def start(self, config):

        logging.info('<#>DAEMON STARTED<#>')

        if not config:  # return here when there is no config file
            return

        self.currentConfig = config

        # check for autostart elements

        startElements = [
            x for x in self.currentConfig
            if not x['Socket'] and x['Config']['GeneralConfig']['Autostart']
        ]

        for startElement in startElements:
            logging.info("Autostart " + startElement['ObjectName'])
            self.createProcHandle(startElement)

    def updateConfig(self, config):

        logging.debug('Operator::updateConfig() called')
        self.currentConfig = config

    def getIdent(self):
        # generate a new process handle identifier
        self.identGenMutex.lock()

        self.n_ident += 1
        # check if current ident is already in use
        while list(
                filter(lambda item: item[0] == self.n_ident,
                       self.processHandles.items())):
            self.n_ident += 1

        if not self.n_ident & 0x7fff:
            self.n_ident = 0
        self.identGenMutex.unlock()

        return self.n_ident

    def startExec(self, id):
        logging.debug('Operator::startExec() called - id: 0x{:08x}'.format(id))
        ## create processor and forward config and start filename

        # self.currentConfig = config
        # https://stackoverflow.com/questions/34609935/passing-a-function-with-two-arguments-to-filter-in-python

        # return first element which matches the ID
        startElement = [x for x in self.currentConfig if x['Id'] == id][0]

        self.createProcHandle(startElement)

    def createProcHandle(self, element, inputData=None):
        #logging.debug('Operator::startExec() called - id: 0x{:08x}'.format(id))

        self.procHandleMutex.lock()
        processHandles = self.processHandles.copy()
        self.procHandleMutex.unlock()

        procHandle = OperatorCreateProcHandle(element, inputData,
                                              processHandles, self)

        self.threadpool.start(procHandle)

    def addHandle(self, identifier, handle):

        self.procHandleMutex.lock()
        self.processHandles[identifier] = handle
        self.procHandleMutex.unlock()

    def stopExec(self, id):
        logging.debug('Operator::stopExec() called - id: 0x{:08x}'.format(id))

        self.procHandleMutex.lock()
        processHandles = self.processHandles.copy()
        self.procHandleMutex.unlock()

        stopOperator = OperatorStopExec(processHandles, id, self)

        self.threadpool.start(stopOperator)

    def stopAll(self):

        logging.debug('Operator::stopAll() called')
        logging.info('User command: Stop All')
        self.procHandleMutex.lock()
        for threadIdentifier, processHandle in self.processHandles.items():
            processHandle.stop()
        self.procHandleMutex.unlock()

    def startAll(self, config):

        logging.info('User command: Start All')
        self.currentConfig = config
        self.threadpool.start(self._startAll)

    def killAll(self):

        logging.debug('Operator::killAll() called')
        logging.info('User command: Kill All Processes')
        # Separate dict must be created because call to removerOperatorThreads
        # modifies self.processHandles
        self.procHandleMutex.lock()
        processes = dict(
            filter(lambda item: item[1].pid, self.processHandles.items()))
        self.procHandleMutex.unlock()
        for threadIdentifier, processHandle in processes.items():
            os.kill(processHandle.pid, signal.SIGTERM)
            # removeOperatorThread is called in run() function of ProcessHandler

    def getElementStates(self):

        logging.debug('Operator::getElementStates() called')

        self.procHandleMutex.lock()
        processHandles = self.processHandles.copy()
        self.procHandleMutex.unlock()

        stateOperator = OperatorReturnElementState(processHandles, self)

        self.threadpool.start(stateOperator)

    def updateStatus(self, element, status):

        #start highlight
        # area
        # id
        # target = "Element"
        # cmd = UpdateElementStatus
        #logging.debug('Operator::updateStatus() called - {} - id: 0x{:08x}'.format(status, element['Id']))

        address = {
            'target': 'Element',
            'area': element['AreaNo'],
            'id': element['Id']
        }

        cmd = {
            'cmd': 'UpdateElementStatus',
            'address': address,
            'data': status
        }

        self.command.emit(cmd)

    def highlightConnection(self, parentId, childId, wrkArea):

        logging.debug('Operator::updateStatus() called')
        address = {'target': 'WorkingArea', 'area': wrkArea}

        data = {'parentId': parentId, 'childId': childId}

        cmd = {'cmd': 'HighlightConnection', 'address': address, 'data': data}

        self.command.emit(cmd)

    def emitCommand(self, command):

        self.command.emit(command)

    def operationDone(self, id, area, record, identifier):

        logging.debug(
            'Operator::operationDone() result received - id: 0x{:08x}, ident: {:04d}'
            .format(id, identifier))

        if isinstance(record, GuiCMD):

            address = {'target': 'Element', 'id': id, 'area': area}
            cmd = {
                'cmd': 'ElementText',
                'address': address,
                'data': record.text
            }
            self.command.emit(cmd)
            return

        if isinstance(record, GuiException):

            address = {'target': 'Element', 'id': id, 'area': area}
            cmd = {'cmd': 'ElementException', 'address': address}
            # log critical exceptions from multiprocessed elements
            logging.error(repr(record.e))
            self.command.emit(cmd)
            return

        operationDoneRunnable = OperatorElementOpDone(self.currentConfig, id,
                                                      record, identifier, self)

        self.threadpool.start(operationDoneRunnable)

    def removeOperatorThread(self, id, identifier):

        logging.debug(
            'Operator::removeOperatorThread() called - id: 0x{:08x}, ident: {:04d}'
            .format(id, identifier))

        self.procHandleMutex.lock()
        procHandle = self.processHandles[identifier]
        if procHandle.element["HighlightState"]:
            self.updateStatus(procHandle.element, False)

        del self.processHandles[identifier]

        self.procHandleMutex.unlock()
Esempio n. 18
0
class MainWindow(QMainWindow):
    """
    Main window class
    """
    def __init__(self, app):
        """
        MainWindow initialization
        """
        super(MainWindow, self).__init__()
        ## app
        self.app = app
        ## ui object
        self.ui = QUiLoader().load("mainwindow.ui")
        self.ui.show()
        self.ui.setWindowTitle("RoboAide")
        self.setMinimumHeight(100)
        self.setMinimumWidth(250)
        # self.setMaximumHeight(200)
        # self.setMaximumWidth(800)
        self.setIcon()
        self.msgMu = QMutex()

        self.numberOfMotors = 6
        self.s, self.messageSize = makeStruct()

        # Connect button signals
        self.ui.calibrateVerticalAxisButton.clicked.connect(
            self.calibrateVerticalAxis)

        self.ports_list = scanAvailablePorts()
        self.populatePortsList()

        # ---------------
        ## Dictionnary of all motor objects
        self.dictMot = dict()
        for i in range(1, self.numberOfMotors + 1):
            mot = Motor(self, "motor" + str(i))
            self.dictMot[mot.getName()] = mot
        # ---------------

        ## List of drawers
        self.drawersList = []
        for i in range(3):
            self.drawersList.append(Drawer(self, "drawer" + str(i + 1)))

        self.updateSliderPositions()

        ## ListOfSequencesHandler object
        self.listOfSequencesHandler = ListOfSequencesHandler(
            self, self.dictMot)

        # load the last save
        loadSequences(self.listOfSequencesHandler, self.dictMot)

        # Connect the slider signals
        self.ui.slider_mot1.sliderMoved.connect(lambda: self.dictMot[
            "motor1"].setGoalPosition(self.ui.slider_mot1.value()))
        self.ui.slider_mot2.sliderMoved.connect(lambda: self.dictMot[
            "motor2"].setGoalPosition(self.ui.slider_mot2.value()))
        self.ui.slider_mot3.sliderMoved.connect(lambda: self.dictMot[
            "motor3"].setGoalPosition(self.ui.slider_mot3.value()))
        self.ui.slider_mot4.sliderMoved.connect(lambda: self.dictMot[
            "motor4"].setGoalPosition(self.ui.slider_mot4.value()))
        self.ui.slider_mot5.sliderMoved.connect(lambda: self.dictMot[
            "motor5"].setGoalPosition(self.ui.slider_mot5.value()))
        self.ui.slider_mot6.sliderMoved.connect(lambda: self.dictMot[
            "motor6"].setGoalPosition(self.ui.slider_mot6.value()))

        # Connect button signals
        self.ui.calibrateVerticalAxisButton.clicked.connect(
            self.calibrateVerticalAxis)

        # Connect drawer buttons signals
        self.ui.drawer1Open.clicked.connect(self.drawersList[0].open)
        self.ui.drawer2Open.clicked.connect(self.drawersList[1].open)
        self.ui.drawer3Open.clicked.connect(self.drawersList[2].open)
        self.ui.drawer1Close.clicked.connect(self.drawersList[0].close)
        self.ui.drawer2Close.clicked.connect(self.drawersList[1].close)
        self.ui.drawer3Close.clicked.connect(self.drawersList[2].close)

        # Connect the tab changed with updating the sliders
        self.ui.tabWidget.currentChanged.connect(self.updateSliderPositions)

        # Serial communication
        ## Outgoing message deque
        self.msgDeque = deque(maxlen=3)
        ## Stop indicator for the motors
        self.shouldStop = False
        ## Message reception QThread object
        self.msgReception = MessageReception(self)
        ## Message transmission QThread object
        self.msgTransmission = MessageTransmission(self)
        self.app.aboutToQuit.connect(self.msgReception.stop)
        self.app.aboutToQuit.connect(self.msgTransmission.stop)
        self.comm = None
        self.serialConnected = None
        try:
            with open('SavePort.json') as save:
                savedPort = json.load(save)
                for index in range(1, len(self.ports_list)):
                    if self.ports_list[index].device == savedPort:
                        self.ui.portselection.setCurrentIndex(index)
                        self.connect_port(savedPort)
                    else:
                        print("The last port is not available")
        except FileNotFoundError:
            print("SavePort file not found")
        self.ui.portselection.currentIndexChanged.connect(self.connect_port)

    def connect_port(self, lastPort=None):
        """
        Connect the selected port of the controller
        :param: lastPort: name of the port that was last used
        :return: None
        """
        if isinstance(lastPort, str):
            commPort = lastPort
        else:
            commPort = self.ui.portselection.currentText()
        self.comm, self.serialConnected = initSerialConnection(commPort)
        if self.comm is not None:
            self.app.aboutToQuit.connect(self.comm.close)
            self.msgReception.start()
            self.msgTransmission.start()
            self.sendMessage('s')

    def setIcon(self):
        """
        Set main window icon
        :return: None
        """
        appIcon = QIcon(icon)
        self.ui.setWindowIcon(appIcon)

    def updateSliderPositions(self, index=0):
        """
        Update motor slider positions
        :return: None
        """
        if index == 0:
            print("Updating motor slider positions")
            self.ui.slider_mot1.setValue(
                self.dictMot["motor1"].getCurrentPosition())
            self.ui.slider_mot2.setValue(
                self.dictMot["motor2"].getCurrentPosition())
            self.ui.slider_mot3.setValue(
                self.dictMot["motor3"].getCurrentPosition())
            self.ui.slider_mot4.setValue(
                self.dictMot["motor4"].getCurrentPosition())
            self.ui.slider_mot5.setValue(
                self.dictMot["motor5"].getCurrentPosition())
            self.ui.slider_mot6.setValue(
                self.dictMot["motor6"].getCurrentPosition())
            print("Finished initializing slider positions")

    def populatePortsList(self):
        """
        Populate the available serial ports in the drop down menu
        :return: None
        """
        print("Scanning and populating list of available serial ports")
        for index in range(len(self.ports_list)):
            result = isinstance(self.ports_list[index], str)
            if not result:
                self.ui.portselection.addItem(self.ports_list[index].device)
            else:
                self.ui.portselection.addItem(self.ports_list[index])

    def sendMessage(self, mode):
        """
        Package message and send on communication port
        :param mode: mode in which the message should be interpreted by the controller
        :return: None
        """
        if self.serialConnected:
            values = (mode.encode(), self.dictMot["motor1"].getGoalPosition(),
                      self.dictMot["motor2"].getGoalPosition(),
                      self.dictMot["motor3"].getGoalPosition(),
                      self.dictMot["motor4"].getGoalPosition(),
                      self.dictMot["motor5"].getGoalPosition(),
                      self.dictMot["motor6"].getGoalPosition(),
                      self.shouldStop, self.drawersList[0].getState(),
                      self.drawersList[1].getState(),
                      self.drawersList[2].getState(), b'\0')
            print("Outgoing: ", end='')
            print(values)
            packed_data = self.s.pack(*values)
            self.msgMu.lock()
            self.msgDeque.append(packed_data)
            self.msgMu.unlock()
        else:
            print("Error sending message, serial not connected")

    def calibrateVerticalAxis(self):
        """
        Trigger vertical axis calibration
        :return: None
        """
        print("Calibrating vertical axis")
        self.sendMessage('c')

    def stopMotors(self):
        """
        Signal all motors to stop and empty message deque
        """
        self.shouldStop = True
        self.msgDeque.clear()
        self.sendMessage('a')
Esempio n. 19
0
class MainWindow(QMainWindow):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.signals = MainWindowSignals()
        self.save_data_dir = None
        self.current_measurement = 0
        self.max_measurements = 0
        self.collecting = False
        self.time_axis = None
        self.mutex = QMutex()
        self.comp_thread = QThread()
        self.exp_thread = QThread()
        self._connect_components()
        self._set_initial_widget_states()
        self._store_line_objects()
        self._set_plot_mouse_mode()

    def _set_initial_widget_states(self):
        with start_action(action_type="_set_initial_widget_states"):
            self.update_max_measurements(self.ui.measurements.value())
            self.ui.stop_btn.setDisabled(True)
            self.ui.reset_avg_btn.setDisabled(True)
            self.ui.save_loc.setDisabled(True)
            self.ui.save_loc_browse_btn.setDisabled(True)

    def _connect_components(self):
        """Connect widgets and events to make the UI respond to user interaction.
        """
        # Start/Stop Buttons
        self.ui.start_btn.clicked.connect(self.start_collecting)
        self.ui.stop_btn.clicked.connect(self.stop_collecting)
        # Measurement Counter
        self.ui.measurements.valueChanged.connect(self.update_max_measurements)
        # Save data controls
        self.ui.save_data_checkbox.stateChanged.connect(
            self.save_loc_set_state)
        self.ui.save_loc_browse_btn.clicked.connect(self.get_save_location)
        # Start/Stop point
        self.ui.stop_pt_checkbox.stateChanged.connect(self.stop_pt_set_state)
        # Dark current
        self.ui.dark_curr_checkbox.stateChanged.connect(
            self.dark_curr_set_state)

    def closeEvent(self, event):
        """Clean up worker threads if the window is closed while collecting data.

        Notes
        -----
        This overrides the default closeEvent method of QMainWindow.
        """
        with start_action(action_type="close"):
            if self.collecting:
                with start_action(action_type="quit_threads"):
                    self.comp_thread.quit()
                    self.exp_thread.quit()
                with start_action(action_type="wait_comp_thread"):
                    self.comp_thread.wait()
                with start_action(action_type="wait_exp_thread"):
                    self.exp_thread.wait()
            event.accept()

    def _store_line_objects(self):
        """Store references to the lines so the data can be updated later.
        """
        starting_data = (np.arange(100), np.zeros(100))
        self.live_par_line = self.ui.live_par_graph.plot(*starting_data)
        self.live_perp_line = self.ui.live_perp_graph.plot(*starting_data)
        self.live_ref_line = self.ui.live_ref_graph.plot(*starting_data)
        self.live_da_par_line = self.ui.live_da_par_graph.plot(*starting_data)
        self.live_da_perp_line = self.ui.live_da_perp_graph.plot(
            *starting_data)
        self.live_da_cd_line = self.ui.live_da_cd_graph.plot(*starting_data)
        self.avg_da_par_line = self.ui.avg_da_par_graph.plot(*starting_data)
        self.avg_da_perp_line = self.ui.avg_da_perp_graph.plot(*starting_data)
        self.avg_da_cd_line = self.ui.avg_da_cd_graph.plot(*starting_data)

    def _set_plot_mouse_mode(self):
        self.ui.live_par_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.live_perp_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.live_ref_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.live_da_par_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.live_da_perp_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.live_da_cd_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.avg_da_par_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.avg_da_perp_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)
        self.ui.avg_da_cd_graph.getPlotItem().getViewBox().setMouseMode(
            ViewBox.RectMode)

    @Slot(np.ndarray)
    def set_time_axis(self, values):
        with start_action(action_type="set_time_axis"):
            self.time_axis = values * 1e6

    @Slot(int)
    def update_max_measurements(self, x):
        with start_action(action_type="update_max_measurements", new_max=x):
            self.max_measurements = x
            self.ui.measurement_counter_label.setText(
                f"{self.current_measurement}/{self.max_measurements}")

    @Slot(int)
    def update_current_measurement(self, x):
        with start_action(action_type="update_current_measurement",
                          new_meas=x):
            self.current_measurement = x
            self.ui.measurement_counter_label.setText(
                f"{self.current_measurement}/{self.max_measurements}")

    @Slot()
    def start_collecting(self):
        """Begins collecting data when the "Start" button is pressed.
        """
        with start_action(action_type="start_collecting"):
            settings, should_quit = self._collect_settings()
            if should_quit:
                Message.log(should_quit=should_quit)
                return
            with start_action(action_type="create_workers"):
                self.comp_worker = ComputationWorker(self.mutex, settings)
                self.exp_worker = ExperimentWorker(self.mutex, settings)
            self._connect_worker_signals()
            self.comp_worker.moveToThread(self.comp_thread)
            self.exp_worker.moveToThread(self.exp_thread)
            with start_action(action_type="start_threads"):
                self.comp_thread.start()
                self.exp_thread.start()
            self.signals.measure.emit()
            Message.log(signal="measure")
            self._disable_acq_controls()

    def _collect_settings(self):
        """Collect all the settings from the UI.
        """
        with start_action(action_type="collect_settings"):
            settings = UiSettings()
            settings, should_quit = self._collect_meas_settings(settings)
            if should_quit:
                return settings, should_quit
            settings, should_quit = self._collect_instr_settings(settings)
            if should_quit:
                return settings, should_quit
            settings, should_quit = self._collect_save_settings(settings)
            if should_quit:
                return settings, should_quit
            settings, should_quit = self._collect_start_stop_settings(settings)
            if should_quit:
                return settings, should_quit
            settings, should_quit = self._collect_dark_curr_settings(settings)
            return settings, should_quit

    def _collect_dark_curr_settings(self, settings):
        with start_action(action_type="dark_current_settings"):
            quit = False
            use_dark_curr = self.ui.dark_curr_checkbox.isChecked()
            Message.log(checked=use_dark_curr)
            if not use_dark_curr:
                Message.log(quit=quit)
                return settings, quit
            try:
                dark_curr_par = float(self.ui.dark_curr_par.text())
                dark_curr_perp = float(self.ui.dark_curr_perp.text())
                dark_curr_ref = float(self.ui.dark_curr_ref.text())
                settings.dark_curr_par = dark_curr_par
                settings.dark_curr_perp = dark_curr_perp
                settings.dark_curr_ref = dark_curr_ref
            except ValueError:
                quit = True
            Message.log(quit=quit)
            return settings, quit

    def _collect_meas_settings(self, settings):
        """Collect the number of measurements from the UI.
        """
        with start_action(action_type="measurement_settings"):
            quit = False
            settings.num_measurements = self.ui.measurements.value()
            Message.log(quit=quit)
            return settings, quit

    def _collect_instr_settings(self, settings):
        """Collect the settings from the UI related to the instrument.
        """
        with start_action(action_type="instrument_settings"):
            quit = False
            instr_name = self.ui.instr_name.text()
            Message.log(instrument_name=instr_name)
            if instr_name == "":
                Message.log(quit=quit)
                quit = True
            settings.instr_name = instr_name
            Message.log(quit=quit)
            return settings, quit

    def _collect_save_settings(self, settings):
        """Collect the settings from the UI related to saving data.
        """
        with start_action(action_type="save_data_settings"):
            quit = False
            should_save = self.ui.save_data_checkbox.isChecked()
            Message.log(checked=should_save)
            if should_save and not self._saving_should_proceed():
                Message.log(quit=quit)
                quit = True
            settings.save = should_save
            settings.save_loc = self.ui.save_loc.text()
            Message.log(dir=settings.save_loc)
            Message.log(quit=quit)
            return settings, quit

    def _collect_start_stop_settings(self, settings):
        """Collect the settings from the UI related to the Start/Stop points.
        """
        with start_action(action_type="start_stop_settings"):
            quit = False
            start_pt = self.ui.start_pt.value()
            settings.start_pt = start_pt
            Message.log(start=start_pt)
            if not self.ui.stop_pt_checkbox.isChecked():
                stop_pt = self.ui.stop_pt.value()
                settings.stop_pt = stop_pt
                if start_pt >= stop_pt:
                    self._tell_start_greater_than_stop()
                    quit = True
            Message.log(stop=settings.stop_pt)
            Message.log(quit=quit)
            return settings, quit

    def _connect_worker_signals(self):
        """Connect signals for communication between workers and the main window.
        """
        # Produced by the experiment worker
        self.exp_worker.signals.preamble.connect(
            self.comp_worker.store_preamble)
        self.exp_worker.signals.new_data.connect(
            self.comp_worker.compute_signals)
        self.exp_worker.signals.done.connect(self.cleanup_when_done)
        # Produced by the computation worker
        self.comp_worker.signals.time_axis.connect(self.set_time_axis)
        self.comp_worker.signals.new_data.connect(self.update_plots)
        self.comp_worker.signals.meas_num.connect(
            self.update_current_measurement)
        # Produced by the main window
        self.signals.measure.connect(self.exp_worker.measure)
        self.ui.reset_avg_btn.clicked.connect(self.comp_worker.reset_averages)

    def _disable_acq_controls(self):
        """Disable certain controls while collecting data.
        """
        # Disabled
        self.ui.start_btn.setDisabled(True)
        self.ui.instr_name.setDisabled(True)
        self.ui.measurements.setDisabled(True)
        self.ui.save_data_checkbox.setDisabled(True)
        self.ui.save_loc.setDisabled(True)
        self.ui.save_loc_browse_btn.setDisabled(True)
        self.ui.start_pt.setDisabled(True)
        self.ui.stop_pt.setDisabled(True)
        self.ui.stop_pt_checkbox.setDisabled(True)
        # Enabled
        self.ui.stop_btn.setEnabled(True)
        self.ui.reset_avg_btn.setEnabled(True)

    def _enable_acq_controls(self):
        """Enable certain controls after data collection is complete.
        """
        # Enabled
        self.ui.start_btn.setEnabled(True)
        self.ui.instr_name.setEnabled(True)
        self.ui.measurements.setEnabled(True)
        self.ui.save_data_checkbox.setEnabled(True)
        if self.ui.save_data_checkbox.isChecked():
            self.ui.save_loc.setEnabled(True)
            self.ui.save_loc_browse_btn.setEnabled(True)
        self.ui.start_pt.setEnabled(True)
        self.ui.stop_pt_checkbox.setEnabled(True)
        if not self.ui.stop_pt_checkbox.isChecked():
            self.ui.stop_pt.setEnabled(True)
        # Disabled
        self.ui.stop_btn.setDisabled(True)
        self.ui.reset_avg_btn.setDisabled(True)

    @Slot()
    def stop_collecting(self):
        """Stops collecting data when the "Stop" button is pressed.
        """
        with start_action(action_type="stop_collecting"):
            with start_action(action_type="mutex"):
                self.mutex.lock()
                common.SHOULD_STOP = True
                self.mutex.unlock()
            with start_action(action_type="quit_threads"):
                self.comp_thread.quit()
                self.exp_thread.quit()
            with start_action(action_type="wait_comp_thread"):
                self.comp_thread.wait()
            with start_action(action_type="wait_exp_thread"):
                self.exp_thread.wait()
            self._enable_acq_controls()
            self.current_measurement = 0

    @Slot()
    def cleanup_when_done(self):
        """Clean up workers and threads after data collection is complete.
        """
        with start_action(action_type="done_collecting"):
            with start_action(action_type="quit_threads"):
                self.comp_thread.quit()
                self.exp_thread.quit()
            with start_action(action_type="wait_comp_thread"):
                self.comp_thread.wait()
            with start_action(action_type="wait_exp_thread"):
                self.exp_thread.wait()
            with start_action(action_type="mutex"):
                self.mutex.lock()
                common.SHOULD_STOP = False
                self.mutex.unlock()
            self._enable_acq_controls()
            self.current_measurement = 0
            with start_action(action_type="dialog"):
                QMessageBox.information(self, "Done",
                                        "The experiment has finished.",
                                        QMessageBox.StandardButton.Ok)

    @Slot(PlotData)
    def update_plots(self, data):
        """Update the plots in the Live/Average tabs when new data is available.

        Parameters
        ----------
        data : PlotData
            Three live data channels and the signals computed from them.
        """
        with start_action(action_type="update_plots"):
            self.live_par_line.setData(self.time_axis, data.par)
            self.live_perp_line.setData(self.time_axis, data.perp)
            self.live_ref_line.setData(self.time_axis, data.ref)
            if data.da_par is not None:
                with start_action(action_type="update_da_plots"):
                    self.live_da_par_line.setData(self.time_axis, data.da_par)
                    self.live_da_perp_line.setData(self.time_axis,
                                                   data.da_perp)
                    self.live_da_cd_line.setData(self.time_axis, data.da_cd)
                    self.avg_da_par_line.setData(self.time_axis,
                                                 data.avg_da_par)
                    self.avg_da_perp_line.setData(self.time_axis,
                                                  data.avg_da_perp)
                    self.avg_da_cd_line.setData(self.time_axis, data.avg_da_cd)

    @Slot(int)
    def save_loc_set_state(self, state):
        """Enable or disable the save location controls in response to the checkbox.

        Parameters
        ----------
        state : int
            An integer representing the state of the checkbox.

        Notes
        -----
            0 - unchecked
            2 - checked
        """
        with start_action(action_type="save_loc_state", state=state):
            if state == 0:
                self.ui.save_loc.setDisabled(True)
                self.ui.save_loc_browse_btn.setDisabled(True)
                Message.log(save="disabled")
            elif state == 2:
                self.ui.save_loc.setEnabled(True)
                self.ui.save_loc_browse_btn.setEnabled(True)
                Message.log(save="enabled")

    @Slot()
    def get_save_location(self):
        """Get an existing directory in which to store the collected data.
        """
        with start_action(action_type="get_save_location"):
            self.save_data_dir = QFileDialog.getExistingDirectory()
            self.ui.save_loc.setText(self.save_data_dir)
            Message.log(dir=self.save_data_dir)

    def _save_loc_still_valid(self):
        """Ensure that the path to the directory still exists before saving data to it.
        """
        save_dir = Path(self.save_data_dir)
        return save_dir.exists()

    def _tell_save_loc_is_invalid(self):
        """Tell the user that the current save location isn't valid or doesn't exist.
        """
        with start_action(action_type="dialog"):
            QMessageBox.critical(
                self,
                "Invalid Save Location",
                "The current save data location is invalid or doesn't exist. Please choose a new location.",
                QMessageBox.StandardButton.Ok,
            )

    def _save_would_overwrite(self):
        """Returns True if the save directory contains *anything*.
        """
        for item in Path(self.save_data_dir).iterdir():
            return True
        return False

    def _should_overwrite(self):
        """Asks the user whether data in the save directory should be overwritten.
        """
        with start_action(action_type="dialog") as action:
            reply = QMessageBox.warning(
                self,
                "Overwrite?",
                "The current directory contents will be erased. Continue?",
                QMessageBox.StandardButton.Ok
                | QMessageBox.StandardButton.Cancel,
            )
            should_overwrite = reply == QMessageBox.StandardButton.Ok
            action.add_success_fields(overwrite=should_overwrite)
            return should_overwrite

    def _saving_should_proceed(self):
        """Determine whether valid settings have been entered for saving data.
        """
        with start_action(action_type="saving_should_proceed"):
            try:
                loc_is_valid = self._save_loc_still_valid()
            except TypeError:
                loc_is_valid = False
            if not loc_is_valid:
                self._tell_save_loc_is_invalid()
                return False
            would_overwrite = self._save_would_overwrite()
            if would_overwrite and (not self._should_overwrite()):
                return False
            return True

    @Slot(int)
    def stop_pt_set_state(self, state):
        """Enable or disable the "Stop Point" controls in response to the checkbox.

        Parameters
        ----------
        state : int
            An integer representing the state of the checkbox.

        Notes
        -----
            0 - unchecked
            2 - checked
        """
        with start_action(action_type="stop_pt_state", state=state):
            if state == 2:
                self.ui.stop_pt.setDisabled(True)
                Message.log(stop_pt="disabled")
            elif state == 0:
                self.ui.stop_pt.setEnabled(True)
                Message.log(stop_pt="enabled")

    def _tell_start_greater_than_stop(self):
        """Tell the user that the current save location isn't valid or doesn't exist.
        """
        QMessageBox.critical(
            self,
            "Invalid Start/Stop Points",
            "The Start point must be less than the Stop point.",
            QMessageBox.StandardButton.Ok,
        )

    @Slot(int)
    def dark_curr_set_state(self, state):
        """Enable or disable the dark current controls in response to the checkbox.

        Parameters
        ----------
        state : int
            An integer representing the state of the checkbox.

        Notes
        -----
            0 - unchecked
            2 - checked
        """
        with start_action(action_type="dark_curr_state", state=state):
            if state == 0:
                self.ui.dark_curr_par.setDisabled(True)
                self.ui.dark_curr_perp.setDisabled(True)
                self.ui.dark_curr_ref.setDisabled(True)
                Message.log(dark_curr="disabled")
            elif state == 2:
                self.ui.dark_curr_par.setEnabled(True)
                self.ui.dark_curr_perp.setEnabled(True)
                self.ui.dark_curr_ref.setEnabled(True)
                Message.log(dark_curr="enabled")
Esempio n. 20
0
class msv2:
    def __init__(self):
        self.ser = serial.Serial()
        self.escape = 0
        self.connected = 0
        self.state = WAITING_DLE
        self.crc_data = []
        self.data = []
        self.mutex = QMutex()

    def explore(self):
         list = serial.tools.list_ports.comports(include_links=False)
         return [x.device for x in list]

    def connect(self, port):
        self.port = port
        self.ser.baudrate = BAUDRATE
        self.ser.port = port
        self.ser.timeout = 0.2
        try:
            self.ser.open()
            self.connected = 1
            print("connected")
            return 1
        except:
            return 0
    def reconnect(self):
        try:
            self.ser.port = self.port
            self.ser.baudrate = BAUDRATE
            self.ser.timeout = 0.2
            self.ser.open()
            self.connected = 1
            print("connected")
            return 1
        except:
            return 0

    def disconnect(self):
        try:
            self.ser.close()
            self.connected = 0
            print("disconnected")
            return 1
        except:
            return 0

    def is_connected(self):
        return self.connected

    def encode(self, opcode, data):
        bin_data = []
        crc_data = []
        bin_data.append(DLE)
        bin_data.append(STX)
        bin_data.append(opcode)
        bin_data.append(int(len(data)/2))
        crc_data.append(opcode)
        crc_data.append(int(len(data)/2))
        i = 0
        for byte in data:
            bin_data.append(byte)
            crc_data.append(byte)
            if byte == DLE:
                bin_data.append(byte)

        crc_data.append(0)
        crc_data.append(0)
        crc = crc16(crc_data)
        
        bin_data.append(crc & 0x00ff)
        bin_data.append((crc >> 8) & 0x00ff)
        return bin_data


    def decode(self, d):
        #print("decode state:", self.state)
        d = ord(d)
        if (self.escape == 1 and d == STX):
            self.state = WAITING_OPCODE
            self.escape = 0
            return MSV2_PROGRESS

        if (self.state == WAITING_DLE and d == DLE):
            self.crc_data = []
            self.data = []
            self.state = WAITING_STX
            return MSV2_PROGRESS

        if (d == DLE and self.escape == 0):
            self.escape = 1
            return MSV2_PROGRESS

        if (d == DLE and self.escape == 1):
            self.escape = 0

        if (self.state == WAITING_STX and d == STX):
            self.state = WAITING_OPCODE
            return MSV2_PROGRESS

        if (self.state == WAITING_OPCODE):
            self.opcode = d
            self.state = WAITING_LEN
            self.crc_data.append(d)
            return MSV2_PROGRESS

        if (self.state == WAITING_LEN):
            self.data_len = d
            self.length = 2*d
            self.crc_data.append(d)
            self.counter = 0
            self.state = WAITING_DATA
            return MSV2_PROGRESS

        if (self.state == WAITING_DATA):
            self.data.append(d)
            self.crc_data.append(d)
            self.counter += 1
            if (self.counter==self.length):
                self.state = WAITING_CRC1
            return MSV2_PROGRESS

        if (self.state == WAITING_CRC1):
            self.crc = d
            self.state = WAITING_CRC2
            return MSV2_PROGRESS

        if (self.state == WAITING_CRC2):
            self.crc += d<<8
            self.state = WAITING_DLE
            self.crc_data.append(0)
            self.crc_data.append(0)
            if(self.crc == crc16(self.crc_data)):
                return MSV2_SUCCESS
            else:
                return MSV2_WRONG_CRC

        self.state=WAITING_DLE
        return MSV2_PROGRESS

    def send(self, opcode, data):
        if self.connected:
            self.mutex.lock()
            msg = self.encode(opcode, data)
            error = 0
            try:
                self.ser.write(msg)
            except:
                print("WRITE ERROR")
                self.mutex.unlock()
                self.reconnect()
                return -1
            #print('[{}]'.format(', '.join(hex(x) for x in msg)))
            try:
                while 1:
                    byte = self.ser.read(1)
                    #print("dta: {}".format(hex(ord(byte))))
                    if not byte:
                        print("no resp error")
                        self.mutex.unlock()
                        return 0
                    res = self.decode(byte)
                    #print("res:", res)
                    if not res == MSV2_PROGRESS:
                        break
                #print('[{}]'.format(', '.join(hex(x) for x in self.data)))
                if self.data == [0xce, 0xec] or self.data == [0xbe, 0xeb]:
                    self.mutex.unlock()
                    print("CRC_ERROR")
                    return 0
                else:
                    self.mutex.unlock()
                    print("nominal_resp")
                    return self.data
            except:
                print("READ ERROR")
                self.mutex.unlock()
                self.reconnect()
                return -1
        else:
            print("CONN_ERROR")
            return -1
Esempio n. 21
0
class NodeHandler(QObject):

    newNodeDiscovered = Signal(dict)
    nodeConnected = Signal(dict)
    nodeDisconnected = Signal(dict)
    nodeDisappeared = Signal(dict)
    nodeAliveMessage = Signal(dict)
    cleanupDone = Signal()

    threadReady = Signal()

    def __init__(self, mainwindow):
        super(NodeHandler, self).__init__()
        self.visibleNodes = {}
        self.connectedNodes = {}
        self.nodeListMutex = QMutex()
        self.mainwindow = mainwindow

    @Slot()
    def onRun(self):
        # multithreading hack to prevent threading sigsev conditions
        # all the stuff should be executed in this threads event loop
        # if we do stuff here, the thread context could be different
        self.threadReady.connect(self.onThreadReady)
        self.threadReady.emit()

    @Slot()
    def onThreadReady(self):
        self.udpSocket = QUdpSocket(self)
        #self.udpSocket.bind(13370, QUdpSocket.ShareAddress)
        self.udpSocket.bind(13370, QUdpSocket.ReuseAddressHint)
        self.udpSocket.readyRead.connect(self.onSocketReadyRead)

        # check every second
        self.disconnectTimer = QTimer(self)
        self.disconnectTimer.moveToThread(self.thread())
        self.disconnectTimer.timeout.connect(self.onDisconnectTimerFire)
        self.disconnectTimer.start(100)


    @Slot()
    def onSocketReadyRead(self):
        msg = self.udpSocket.readDatagram(self.udpSocket.pendingDatagramSize())
        if msg[0][0:2] == "CB":
            msg_split = msg[0].split('|')
            device_id = msg_split[1].data().decode('ascii')
            device_version = msg_split[2].data().decode('ascii')
            now = datetime.datetime.now()
            ip = QHostAddress(msg[1].toIPv4Address()).toString()
            device = {"id": device_id, "version": device_version, "ip": ip, "last_seen": now}
            self.nodeListMutex.lock()
            if (device_id not in self.connectedNodes.keys()) and \
                    (device_id not in self.visibleNodes.keys()):
                self.visibleNodes[device_id] = device
                self.newNodeDiscovered.emit(device)
                print(f"discovered {device_id}")
            # update timestamps for known visible/connected devices
            if device_id in self.visibleNodes.keys():
                self.visibleNodes[device_id]["last_seen"] = now
                self.nodeAliveMessage.emit(self.visibleNodes[device_id])
            if device_id in self.connectedNodes.keys():
                self.connectedNodes[device_id]["last_seen"] = now
                self.nodeAliveMessage.emit(self.connectedNodes[device_id])
            self.nodeListMutex.unlock()

    @Slot(str)
    def onSocketCanDiscovered(self, vcan):
        now = datetime.datetime.now()
        device = {"id": vcan, "version": "socket_can", "ip": None, "last_seen": now}
        self.nodeListMutex.lock()
        if (vcan not in self.connectedNodes.keys()) and \
                (vcan not in self.visibleNodes.keys()):
            self.visibleNodes[vcan] = device
            self.newNodeDiscovered.emit(device)
        if vcan in self.visibleNodes.keys():
            self.visibleNodes[vcan]["last_seen"] = now
            self.nodeAliveMessage.emit(self.visibleNodes[vcan])
        if vcan in self.connectedNodes.keys():
            self.connectedNodes[vcan]["last_seen"] = now
            self.nodeAliveMessage.emit(self.connectedNodes[vcan])
        self.nodeListMutex.unlock()


    @Slot()
    def onDisconnectTimerFire(self):
        now = datetime.datetime.now()
        self.nodeListMutex.lock()
        ids_to_delete = []
        for id, node in self.visibleNodes.items():
            # check time difference
            if (now - node["last_seen"]) > datetime.timedelta(seconds=6):
                ids_to_delete.append(id)
                self.nodeDisappeared.emit(node)
        for id in ids_to_delete:
            del self.visibleNodes[id]
        ids_to_delete = []
        for id, node in self.connectedNodes.items():
            # check time difference
            if (now - node["last_seen"]) > datetime.timedelta(seconds=10):
                ids_to_delete.append(id)
                self.nodeDisconnected.emit(node)
        for id in ids_to_delete:
            # check for running connection process
            if self.connectedNodes[id]["connection"].isConnected:
                self.connectedNodes[id]["connection"].resetConnection()
            del self.connectedNodes[id]
        self.nodeListMutex.unlock()

    @Slot(dict)
    def onConnectToNode(self, node):
        print(f"Node handler creating connection to {node['id']}.")
        if ("connection" not in node.keys() or node["connection"] is None) and node["ip"] is not None:
            # start a connection to a canbadger
            con = NodeConnection(node)
            node["connection"] = con
            con.connectionSucceeded.connect(self.onConnectionSucceeded)
            con.connectionFailed.connect(self.onConnectionFailed)
            con.newSettingsData.connect(self.mainwindow.onSettingsReceived)
            self.mainwindow.exiting.connect(con.resetConnection)

            con.onRun()

        elif ("connection" not in node.keys() or node["connection"] is None) and node["ip"] is None:
            # start a socketCan connection
            con = SocketCanConnection(node)
            node["connection"] = con
            con.connectionSucceeded.connect(self.onConnectionSucceeded)
            con.connectionFailed.connect(self.onConnectionFailed)
            self.mainwindow.exiting.connect(con.cleanup)

            con.onRun()

    @Slot()
    def onConnectionSucceeded(self):
        node_connection = self.sender()
        node = node_connection.node

        self.nodeListMutex.lock()
        if node["id"] in self.visibleNodes.keys():
            del self.visibleNodes[node["id"]]
        self.connectedNodes[node["id"]] = node
        self.nodeListMutex.unlock()
        self.nodeConnected.emit(node)
        node_connection.nodeDisconnected.connect(self.onDisconnectNode)

    @Slot()
    def onConnectionFailed(self):
        # signal red
        buttonFeedback(False, self.mainwindow.interfaceSelection)


    @Slot(dict)
    def onDisconnectNode(self, node):
        # reset connection and terminate thread
        if "connection" in node.keys():
            node["connection"].resetConnection()
        if "thread" in node.keys():
            node["thread"].exit()
            pass

        # delete the node from dicts
        self.nodeListMutex.lock()
        if node["id"] in self.connectedNodes:
            del self.connectedNodes[node["id"]]
        if node["id"] in self.visibleNodes:
            del self.visibleNodes[node["id"]]
        self.nodeListMutex.unlock()
        self.nodeDisconnected.emit(node)

    @Slot(str, str)
    def onIDChange(self, old, new):
        self.nodeListMutex.lock()
        if old in self.connectedNodes:
            self.connectedNodes[new] = self.connectedNodes[old]
            del self.connectedNodes[old]
        if old in self.visibleNodes:
            self.visibleNodes[new] = self.visibleNodes[old]
            del self.visibleNodes[old]
        self.nodeListMutex.unlock()


    @Slot()
    def onExiting(self):
        self.disconnectTimer.stop()
        self.udpSocket.close()
        self.cleanupDone.emit()