def __init__(self, app=None, db=None): super().__init__() self._app = app self._db = db self._dc = DataConverter() self._database_path = None self._conn = None self._cursor = None
def __init__(self): super().__init__() self.add_role_name(name="settings") self.add_role_name(name="parameter") self.add_role_name(name="type") self.add_role_name(name="value") self.add_role_name(name="is_active") self.add_role_name(name="new_value") self.add_role_name(name="delegate_type") self.add_role_name(name="model_type") self._dc = DataConverter() self.populate_model()
def __init__(self): super().__init__() qInstallMessageHandler(FramUtil.qt_msg_handler) # self.app = QApplication(sys.argv) appGuid = 'F3FF80BA-BA05-4277-8063-82A6DB9245A2' self.app = QtSingleApplication(appGuid, sys.argv) self.app.setWindowIcon(QtGui.QIcon("resources/ico/hooklogger_v2.ico")) splash = FpcSplash() splash.show() if self.app.isRunning(): sys.exit(0) # qmlRegisterType(FramTreeItem, 'FramTreeItem', 1, 0, 'FramTreeItem') self.engine = QQmlApplicationEngine() qmlRegisterType(TextFieldDoubleValidator, "FRAM", 1, 0, "TextFieldDoubleValidator") self.context = self.engine.rootContext() fl = FramLog() self.context.setContextProperty('framLog', fl) db = HookandlineFpcDB() self.context.setContextProperty('db', db) # self.textfield_double_validator = TextFieldDoubleValidator() # self.context.setContextProperty('TextFieldDoubleValidator', self.textfield_double_validator) # PyQt5 Threading approach self._rpc_thread = QThread() self._rpc_worker = RpcServer() self._rpc_worker.moveToThread(self._rpc_thread) self._rpc_worker.speciesChanged.connect(self._species_changed) self._rpc_thread.started.connect(self._rpc_worker.run) self._rpc_thread.start() logging.info(f"\tRPC thread and worker established") # Technique that works - traditional python threading, although the queue is not needed anymore # self._queue = Queue() # self._rpc_server = threading.Thread(target=RpcServer, kwargs={'queue': self._queue}) # self._rpc_server.setDaemon(True) # self._rpc_server.start() self.fpc_main = FpcMain(app=self) self.settings = Settings(db=db) self.serial_port_manager = SerialPortManager(app=self, db=db) self.serial_port_simulator = SerialPortSimulator(app=self, db=db) self.data_converter = DataConverter(app=self, db=db) self.sensor_data_feeds = SensorDataFeeds(app=self, db=db) self.species_review = SpeciesReview(app=self, db=db) self.end_of_site_validation = EndOfSiteValidation(app=self, db=db) logging.info(f"\tComponent classes all initialized") self.context.setContextProperty('fpcMain', self.fpc_main) self.context.setContextProperty('settings', self.settings) self.context.setContextProperty('serialPortManager', self.serial_port_manager) self.context.setContextProperty('serialPortSimulator', self.serial_port_simulator) self.context.setContextProperty('dataConverter', self.data_converter) self.context.setContextProperty('sensorDataFeeds', self.sensor_data_feeds) self.context.setContextProperty('speciesReview', self.species_review) self.context.setContextProperty('endOfSiteValidation', self.end_of_site_validation) logging.info(f"\tContext Properties all set") # self.widget = self.app.instance() # self.widget.setAttribute(QtCore.Qt.WA_OpaquePaintEvent) # self.widget.setAttribute(QtCore.Qt.WA_NoSystemBackground) # self.view = QQuickView() # self.view.setSource(QUrl('qrc:/qml/hookandline/main_fpc.qml')) # self.view.show() try: self.engine.load(QUrl('qrc:/qml/hookandline/main_fpc.qml')) splash.close() self.win = self.engine.rootObjects()[0] self.msg_box = self.win.findChild(QObject, "dlgUnhandledException") logging.info(f"\tmain_fpc.qml loaded") self.engine.quit.connect(self.app.quit) sys.exit(self.app.exec_()) except Exception as ex: logging.error(f"error loading the application: {ex}")
class SettingsModel(FramListModel): def __init__(self): super().__init__() self.add_role_name(name="settings") self.add_role_name(name="parameter") self.add_role_name(name="type") self.add_role_name(name="value") self.add_role_name(name="is_active") self.add_role_name(name="new_value") self.add_role_name(name="delegate_type") self.add_role_name(name="model_type") self._dc = DataConverter() self.populate_model() def populate_model(self): """" Method to initially populate the model on startup """ self.clear() results = SettingsTable.select().where(SettingsTable.is_active == "True")\ .order_by(SettingsTable.parameter.asc()) for result in results: # item = dict() # item["settings_id"] = result.settings # item["parameter"] = result.parameter # item["value"] = result.value item = model_to_dict(result) item["delegate_type"] = "TextField" if item["parameter"] in [ "Leg 1 Start", "Leg 1 End", "Leg 2 Start", "Leg 2 End" ]: # Convert to mm/dd/yyyy format item["value"] = self._dc.iso_to_common_date_format( item["value"]) elif any(substring in item["parameter"] for substring in [ "Leg 1 FPC", "Leg 2 FPC", "Scientist 1", "Scientist 2", "Scientist 3", "Captain", "Second Captain", "Cook", "Deckhand 1", "Deckhand 2", "Deckhand 3" ]): item["delegate_type"] = "ComboBox" if any(substring in item["parameter"] for substring in ["FPC", "Scientist 1", "Scientist 2", "Scientist 3"]): item["model_type"] = "Scientists" is_science_team = "True" else: item["model_type"] = "Crew" is_science_team = "False" try: person = Personnel.get( Personnel.personnel == item["value"], Personnel.is_science_team == is_science_team) item["value"] = person.last_name + ", " + person.first_name except Exception as ex: item["value"] = "" elif item["parameter"] in ["Vessel"]: item["delegate_type"] = "ComboBox" item["model_type"] = "Vessels" try: lookup = Lookups.get(Lookups.lookup == item["value"]) item["value"] = lookup.description except Exception as ex: item["value"] = "" elif item["parameter"] in ["Backup Folder"]: item["delegate_type"] = "FolderBrowser" elif item["parameter"] == "Depth Output Serial Port": item["model_type"] = "ComPorts" item["delegate_type"] = "DialogListView" elif item["parameter"] == "Depth Output Serial Port Baud Rate": item["model_type"] = "BaudRates" item["delegate_type"] = "DialogListView" elif item["parameter"] == "Depth Output Serial Port Status": item["delegate_type"] = "Switch" item["new_value"] = item["value"] self.appendItem(item) @pyqtSlot(int, QVariant) def update_row(self, index, value): """ Method to update one of the settings rows :param index: :param value: :return: """ if not isinstance(index, int) or index < 0 or index >= self.count: logging.error("Error updating a settings row: {0}".format(value)) return if isinstance(value, QJSValue): value = value.toVariant() # Convert all boolean objects to strings value = str(value) if isinstance(value, bool) else value # Set to empty string as need and then update the model value = value if value else "" # Update the model - two fields to update self.setProperty(index=index, property="value", value=value) self.setProperty(index=index, property="new_value", value=value) # Database Storage Conversions parameter = self.get(index=index)["parameter"] # Set update_db flag update_db = True # Database Storage Conversions - Dates/Times if parameter in [ "Leg 1 Start", "Leg 1 End", "Leg 2 Start", "Leg 2 End" ]: if "end" in parameter.lower(): value += " 23:59:59" value = self._dc.time_to_iso_format(value) # Names to Foreign Keys in Personnel Table elif any(substring in parameter for substring in [ "Leg 1 FPC", "Leg 2 FPC", "Scientist 1", "Scientist 2", "Scientist 3", "Captain", "Second Captain", "Deckhand 1", "Deckhand 2", "Deckhand 3" ]): try: if value: logging.info( f"Personnel found: {parameter} >>> value: {value}") last_name, first_name = value.split(",") person = Personnel.get(Personnel.first_name == first_name, Personnel.last_name == last_name) value = person.personnel else: update_db = False except Exception as ex: value = None update_db = False logging.error( "Error getting the proper personnel name: {0}".format(ex)) # Names to Foreign Keys in Lookups Table elif parameter in ["Vessel"]: try: if value: vessel = Lookups.get(Lookups.description == value) value = vessel.lookup else: update_db = False except Exception as ex: value = None update_db = False logging.error( "Error getting the proper vessel name: {0}".format(ex)) # Update the database try: if update_db: settings_id = self.get(index=index)["settings"] SettingsTable.update(value=value).where( SettingsTable.settings == settings_id).execute() except Exception as ex: logging.error( "Exception updating the settings table: {0}".format(ex))
class SensorDatabase(QObject): errorReceived = pyqtSignal(str, arguments=[ "msg", ]) def __init__(self, app=None, db=None): super().__init__() self._app = app self._db = db self._dc = DataConverter() self._database_path = None self._conn = None self._cursor = None def get_sensor_database_old(self, datetime=None): """ Method to retrieve the current sensors database. Look backwards up to 10 days to try and find a sensors database that mataches the given datetime. Note that a user could start HookLogger and leaving it running for multiple days and a sensors database is only created when HookLogger is launched. :return: """ if not datetime: return db_found = False datetime = arrow.get(datetime) try: i = 0 while i > -10: test_day = datetime.shift(days=i) file_name = f"sensors_{test_day.format('YYYYMMDD')}.db" dir = os.path.abspath(os.path.dirname(sys.argv[0])) full_path = os.path.join(dir, "data", file_name) logging.info(f"trying to find sensors db at: {full_path}") if os.path.exists(full_path): logging.info(f"\t\tsensors db found: {full_path}") self._dbpath = full_path db_found = True break i -= 1 except Exception as ex: logging.info(f"Error is getting the date: {ex}") if db_found: self._conn = sqlite.Connection(self._dbpath) self._conn.setbusytimeout(10000) self._cursor = self._conn.cursor() else: logging.info(f"sensor database was not found") def get_sensor_database(self, datetime=None): """ Method to retrieve the current sensors database. Look backwards up to 10 days to try and find a sensors database that mataches the given datetime. Note that a user could start HookLogger and leaving it running for multiple days and a sensors database is only created when HookLogger is launched. :return: """ if not datetime: return try: status = False datetime = arrow.get(datetime).format('YYYYMMDD') file_name = f"sensors_{datetime}.db" dir = os.path.abspath(os.path.dirname(sys.argv[0])) full_path = os.path.join(dir, "data", file_name) logging.info(f"trying to find sensors db at: {full_path}") if os.path.exists(full_path): logging.info(f"\t\tsensors db found: {full_path}") self._database_path = full_path self._conn = sqlite.Connection(self._database_path) self._conn.setbusytimeout(10000) self._cursor = self._conn.cursor() status = True except Exception as ex: logging.info(f"Error is getting the date: {ex}") return status def query_by_datetime(self, datetime, sentences): """ Method to get the given sentence closest to the datetime provided. :param datetime: :param sentence: :return: """ if not self._cursor or not datetime or not sentences: logging.info(f"cursor does not exist, returning...") return try: results = [] msg = "" # Craft the sql clause = "AND (" for i in range(len(sentences)): clause += f"RAW_SENTENCE LIKE ? OR " clause = f"{clause[:-4]})" # sql = f"SELECT * FROM RAW_SENTENCES WHERE STRFTIME('%Y-%m-%dT%H:%M:%S', DATE_TIME) = ? {clause}" sql = f"SELECT * FROM RAW_SENTENCES WHERE DATE_TIME BETWEEN ? AND ? {clause} ORDER BY DATE_TIME ASC;" # Craft the parameters if isinstance(datetime, str): datetime = arrow.get(datetime).replace( tzinfo='US/Pacific') # 20190921 - Added the replace clause start_date_time = datetime.isoformat() end_date_time = datetime.shift(seconds=+10).isoformat() sentences = [f"{x}%" for x in sentences] params = [start_date_time, end_date_time] + sentences logging.info(f"sql = {sql}") logging.info(f"params = {params}") results = list(self._cursor.execute(sql, params)) status = True except Exception as ex: msg = f"Error querying the sensors db: {ex}" logging.error(msg) self.errorReceived.emit(msg) return results def get_updated_event_data(self, datetime): """ Method to retrieve new data for an event. An event is a drop and this method is called when a user decides that the start or end time of a drop is incorrect and it needs to be updated. This method will then find the appropriate sensor database that contains latitude, longitude, and depth information corresponding to this provided datetime :param datetime: :return: """ if not datetime: return logging.info(f"new datetime for getting sensor data: {datetime}") status = False # Define the measurements that will be updated by the change in the event start/end time measurement_list = ["Latitude - Vessel", "Longitude - Vessel", "Depth"] values = {x: {} for x in measurement_list} # Get the parsing rules for these particular measurements, i.e. which sentences to retrieve from the sensors db measurements = ParsingRules.select(ParsingRules, Lookups) \ .join(Lookups, on=(ParsingRules.measurement_lu == Lookups.lookup).alias('types')) \ .where(Lookups.value << measurement_list) # Get a unique list of the sentences required to get the items listed in measurement_list sentences = [] for measurement in measurements: # logging.info(f"measurement = {measurement.types.value} > {measurement.line_starting}") values[measurement.types. value]["sentence"] = measurement.line_starting values[measurement.types. value]["position"] = measurement.field_position if measurement.line_starting not in sentences: sentences.append(measurement.line_starting) """ Find the relevant sensor database for the given datetime. Remember, sensor databases are daily sqlite files with the naming convention sensors_YYYYMMDD.db, however, if someone has left HookLogger running for multiple days without shutting it down, the current sensor database could have a name from many days ago, i.e. when HookLoggger was last started. I have actually corrected for this situation such that the database is shifted to a new one at midnight """ dir = os.path.abspath(os.path.dirname(sys.argv[0])) data_path = os.path.join(dir, "data") db_search_str = r'sensors_.*\.(db)$' available_dbs = [ f for f in os.listdir(data_path) if re.search(db_search_str, f) ] available_dbs = sorted(available_dbs, reverse=True) date_of_interest = arrow.get(datetime).replace(hour=0, minute=0, second=0, tzinfo="US/Pacific") for file in available_dbs: current_day = arrow.get( file.strip("sensors_").strip(".db"), "YYYYMMDD").replace(tzinfo="US/Pacific") diff = current_day - date_of_interest logging.info( f"current_day = {current_day}, DOI = {date_of_interest} >>>> diff.days = {diff.days}" ) if diff.days > 0: continue full_path = os.path.join(dir, "data", file) self._database_path = full_path self._conn = sqlite.Connection(self._database_path) self._conn.setbusytimeout(10000) self._cursor = self._conn.cursor() # Query the current sensor database for the sentences listed in sentences results = self.query_by_datetime(datetime=datetime, sentences=sentences) logging.info(f"results count = {len(results)}") if results: results = [x[1] for x in results ] # only need the sentences from the results # Populate values to be returned for k, v in values.items(): result = [x for x in results if v["sentence"] in x] if len(result) > 0: logging.info(f"{k} > {result[0]}") fields = result[0].split(",") v["text value"] = fields[v["position"] - 1] if k in ["Latitude - Vessel", "Longitude - Vessel"]: v["hemisphere"] = fields[v["position"]] v["value"] = self._dc.gps_lat_or_lon_to_dd( value_str=v["text value"], hemispshere=v["hemisphere"]) else: v["value"] = float(v["text value"]) status = True break if self._cursor: self._cursor.close() if self._conn: self._conn.close() return status, values