コード例 #1
0
class DevicesListWidget(QWidget):
    def __init__(self, parent, *args, **kwargs):
        super(DevicesListWidget, self).__init__(*args, **kwargs)
        self.setWindowTitle("Devices list")
        self.setWindowState(Qt.WindowMaximized)
        self.setLayout(VLayout(margin=0, spacing=0))

        self.mqtt = parent.mqtt
        self.mdi = parent.mdi
        self.idx = None

        self.nam = QNetworkAccessManager()
        self.backup = bytes()

        self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()),
                                  QSettings.IniFormat)
        self.hidden_columns = self.settings.value("hidden_columns", [1, 2])

        self.tb = Toolbar(Qt.Horizontal, 16, Qt.ToolButtonTextBesideIcon)
        self.tb.addAction(QIcon("GUI/icons/add.png"), "Add", self.device_add)

        self.layout().addWidget(self.tb)

        self.device_list = TableView()
        self.model = parent.device_model
        self.telemetry_model = parent.telemetry_model
        self.sorted_device_model = QSortFilterProxyModel()
        self.sorted_device_model.setSourceModel(parent.device_model)
        self.device_list.setModel(self.sorted_device_model)
        self.device_list.setupColumns(columns, self.hidden_columns)
        self.device_list.setSortingEnabled(True)
        self.device_list.setWordWrap(True)
        self.device_list.setItemDelegate(DeviceDelegate())
        self.device_list.sortByColumn(DevMdl.TOPIC, Qt.AscendingOrder)
        self.device_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.layout().addWidget(self.device_list)

        self.device_list.clicked.connect(self.select_device)
        self.device_list.doubleClicked.connect(self.device_config)
        self.device_list.customContextMenuRequested.connect(
            self.show_list_ctx_menu)

        self.device_list.horizontalHeader().setContextMenuPolicy(
            Qt.CustomContextMenu)
        self.device_list.horizontalHeader().customContextMenuRequested.connect(
            self.show_header_ctx_menu)

        self.ctx_menu = QMenu()
        self.ctx_menu_relays = None
        self.create_actions()

        self.build_header_ctx_menu()

    def create_actions(self):
        self.ctx_menu.addAction(QIcon("GUI/icons/configure.png"), "Configure",
                                self.device_config)
        self.ctx_menu.addAction(QIcon("GUI/icons/delete.png"), "Remove",
                                self.device_delete)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/refresh.png"), "Refresh",
                                self.ctx_menu_refresh)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/on.png"), "Power ON",
                                lambda: self.ctx_menu_power(state="ON"))
        self.ctx_menu.addAction(QIcon("GUI/icons/off.png"), "Power OFF",
                                lambda: self.ctx_menu_power(state="OFF"))

        self.ctx_menu_relays = QMenu("Relays")
        self.ctx_menu_relays.setIcon(QIcon("GUI/icons/switch.png"))
        relays_btn = self.ctx_menu.addMenu(self.ctx_menu_relays)
        self.ctx_menu_relays.setEnabled(False)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/clear.png"), "Clear retained",
                                self.ctx_menu_clean_retained)
        self.ctx_menu.addSeparator()

        self.ctx_menu_copy = QMenu("Copy")
        self.ctx_menu_copy.setIcon(QIcon("GUI/icons/copy.png"))
        copy_btn = self.ctx_menu.addMenu(self.ctx_menu_copy)

        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction("Set teleperiod", self.ctx_menu_teleperiod)
        self.ctx_menu.addAction(QIcon("GUI/icons/restart.png"), "Restart",
                                self.ctx_menu_restart)
        self.ctx_menu.addAction(QIcon("GUI/icons/web.png"), "Open WebUI",
                                self.ctx_menu_webui)
        self.ctx_menu.addSeparator()

        self.ctx_menu_ota = QMenu("OTA upgrade")
        self.ctx_menu_ota.addAction("Set OTA URL", self.ctx_menu_ota_set_url)
        self.ctx_menu_ota.addAction("Upgrade", self.ctx_menu_ota_set_upgrade)
        ota_btn = self.ctx_menu.addMenu(self.ctx_menu_ota)

        self.ctx_menu.addAction("Config backup", self.ctx_menu_config_backup)

        self.ctx_menu_copy.addAction(
            "IP", lambda: self.ctx_menu_copy_value(DevMdl.IP))
        self.ctx_menu_copy.addAction(
            "MAC", lambda: self.ctx_menu_copy_value(DevMdl.MAC))
        self.ctx_menu_copy.addAction("BSSID", self.ctx_menu_copy_bssid)
        self.ctx_menu_copy.addSeparator()
        self.ctx_menu_copy.addAction(
            "Topic", lambda: self.ctx_menu_copy_value(DevMdl.TOPIC))
        self.ctx_menu_copy.addAction(
            "FullTopic", lambda: self.ctx_menu_copy_value(DevMdl.FULL_TOPIC))
        self.ctx_menu_copy.addAction(
            "STAT topic", lambda: self.ctx_menu_copy_prefix_topic("STAT"))
        self.ctx_menu_copy.addAction(
            "CMND topic", lambda: self.ctx_menu_copy_prefix_topic("CMND"))
        self.ctx_menu_copy.addAction(
            "TELE topic", lambda: self.ctx_menu_copy_prefix_topic("TELE"))

        self.tb.addActions(self.ctx_menu.actions())
        self.tb.widgetForAction(ota_btn).setPopupMode(QToolButton.InstantPopup)
        self.tb.widgetForAction(relays_btn).setPopupMode(
            QToolButton.InstantPopup)
        self.tb.widgetForAction(copy_btn).setPopupMode(
            QToolButton.InstantPopup)

    def ctx_menu_copy_value(self, column):
        if self.idx:
            row = self.idx.row()
            value = self.model.data(self.model.index(row, column))
            QApplication.clipboard().setText(value)

    def ctx_menu_copy_bssid(self):
        if self.idx:
            QApplication.clipboard().setText(self.model.bssid(self.idx))

    def ctx_menu_copy_prefix_topic(self, prefix):
        if self.idx:
            if prefix == "STAT":
                topic = self.model.statTopic(self.idx)
            elif prefix == "CMND":
                topic = self.model.commandTopic(self.idx)
            elif prefix == "TELE":
                topic = self.model.teleTopic(self.idx)
            QApplication.clipboard().setText(topic)

    def ctx_menu_clean_retained(self):
        if self.idx:
            relays = self.model.data(
                self.model.index(self.idx.row(), DevMdl.POWER))
            if relays and len(relays.keys()) > 0:
                cmnd_topic = self.model.commandTopic(self.idx)

                for r in relays.keys():
                    self.mqtt.publish(cmnd_topic + r, retain=True)
                QMessageBox.information(self, "Clear retained",
                                        "Cleared retained messages.")

    def ctx_menu_power(self, relay=None, state=None):
        if self.idx:
            relays = self.model.data(
                self.model.index(self.idx.row(), DevMdl.POWER))
            cmnd_topic = self.model.commandTopic(self.idx)
            if relay:
                self.mqtt.publish(cmnd_topic + relay, payload=state)

            elif relays:
                for r in relays.keys():
                    self.mqtt.publish(cmnd_topic + r, payload=state)

    def ctx_menu_restart(self):
        if self.idx:
            self.mqtt.publish("{}/restart".format(
                self.model.commandTopic(self.idx)),
                              payload="1")

    def ctx_menu_refresh(self):
        if self.idx:
            for q in initial_queries:
                self.mqtt.publish("{}/status".format(
                    self.model.commandTopic(self.idx)),
                                  payload=q)

    def ctx_menu_teleperiod(self):
        if self.idx:
            teleperiod, ok = QInputDialog.getInt(
                self, "Set telemetry period",
                "Input 1 to reset to default\n[Min: 10, Max: 3600]",
                int(
                    self.model.data(
                        self.model.index(self.idx.row(),
                                         DevMdl.TELEPERIOD))), 1, 3600)
            if ok:
                if teleperiod != 1 and teleperiod < 10:
                    teleperiod = 10
            self.mqtt.publish("{}/teleperiod".format(
                self.model.commandTopic(self.idx)),
                              payload=teleperiod)

    def ctx_menu_telemetry(self):
        if self.idx:
            self.mqtt.publish("{}/status".format(
                self.model.commandTopic(self.idx)),
                              payload=8)

    def ctx_menu_webui(self):
        if self.idx:
            QDesktopServices.openUrl(
                QUrl("http://{}".format(self.model.ip(self.idx))))

    def ctx_menu_config_backup(self):
        if self.idx:
            self.backup = bytes()
            ip = self.model.data(self.model.index(self.idx.row(), DevMdl.IP))
            self.dl = self.nam.get(
                QNetworkRequest(QUrl("http://{}/dl".format(ip))))
            self.dl.readyRead.connect(self.get_dump)
            self.dl.finished.connect(self.save_dump)

    def ctx_menu_ota_set_url(self):
        if self.idx:
            current_url = self.model.data(
                self.model.index(self.idx.row(), DevMdl.OTA_URL))
            url, ok = QInputDialog.getText(
                self,
                "Set OTA URL",
                '100 chars max. Set to "1" to reset to default.',
                text=current_url)
            if ok:
                self.mqtt.publish("{}/otaurl".format(
                    self.model.commandTopic(self.idx)),
                                  payload=url)

    def ctx_menu_ota_set_upgrade(self):
        if self.idx:
            current_url = self.model.data(
                self.model.index(self.idx.row(), DevMdl.OTA_URL))
            if QMessageBox.question(
                    self, "OTA Upgrade",
                    "Are you sure to OTA upgrade from\n{}".format(current_url),
                    QMessageBox.Yes | QMessageBox.No) == QMessageBox.Yes:
                self.model.setData(
                    self.model.index(self.idx.row(), DevMdl.FIRMWARE),
                    "Upgrade in progress")
                self.mqtt.publish("{}/upgrade".format(
                    self.model.commandTopic(self.idx)),
                                  payload="1")

    def show_list_ctx_menu(self, at):
        self.select_device(self.device_list.indexAt(at))
        self.ctx_menu.popup(self.device_list.viewport().mapToGlobal(at))

    def build_header_ctx_menu(self):
        self.hdr_ctx_menu = QMenu()
        for c in columns.keys():
            a = self.hdr_ctx_menu.addAction(columns[c][0])
            a.setData(c)
            a.setCheckable(True)
            a.setChecked(not self.device_list.isColumnHidden(c))
            a.toggled.connect(self.header_ctx_menu_toggle_col)

    def show_header_ctx_menu(self, at):
        self.hdr_ctx_menu.popup(
            self.device_list.horizontalHeader().viewport().mapToGlobal(at))

    def header_ctx_menu_toggle_col(self, state):
        self.device_list.setColumnHidden(self.sender().data(), not state)
        hidden_columns = [
            int(c) for c in columns.keys()
            if self.device_list.isColumnHidden(c)
        ]
        self.settings.setValue("hidden_columns", hidden_columns)
        self.settings.sync()

    def select_device(self, idx):
        self.idx = self.sorted_device_model.mapToSource(idx)
        self.device = self.model.data(self.model.index(idx.row(),
                                                       DevMdl.TOPIC))

        relays = self.model.data(self.model.index(self.idx.row(),
                                                  DevMdl.POWER))
        if relays and len(relays.keys()) > 1:
            self.ctx_menu_relays.setEnabled(True)
            self.ctx_menu_relays.setEnabled(True)
            self.ctx_menu_relays.clear()

            for r in relays.keys():
                actR = self.ctx_menu_relays.addAction("{} ON".format(r))
                actR.triggered.connect(
                    lambda st, x=r: self.ctx_menu_power(x, "ON"))
                actR = self.ctx_menu_relays.addAction("{} OFF".format(r))
                actR.triggered.connect(
                    lambda st, x=r: self.ctx_menu_power(x, "OFF"))
                self.ctx_menu_relays.addSeparator()
        else:
            self.ctx_menu_relays.setEnabled(False)
            self.ctx_menu_relays.clear()

    def device_config(self, idx=None):
        if self.idx:
            dev_cfg = DevicesConfigWidget(self, self.model.topic(self.idx))
            self.mdi.addSubWindow(dev_cfg)
            dev_cfg.setWindowState(Qt.WindowMaximized)

    def device_add(self):
        rc = self.model.rowCount()
        self.model.insertRow(rc)
        dlg = DeviceEditDialog(self.model, rc)
        dlg.full_topic.setText("%prefix%/%topic%/")

        if dlg.exec_() == QDialog.Accepted:
            self.model.setData(
                self.model.index(rc, DevMdl.FRIENDLY_NAME),
                self.model.data(self.model.index(rc, DevMdl.TOPIC)))
            topic = dlg.topic.text()
            tele_dev = self.telemetry_model.addDevice(TasmotaDevice, topic)
            self.telemetry_model.devices[topic] = tele_dev
        else:
            self.model.removeRow(rc)

    def device_delete(self):
        if self.idx:
            topic = self.model.topic(self.idx)
            if QMessageBox.question(
                    self, "Confirm",
                    "Do you want to remove '{}' from devices list?".format(
                        topic)) == QMessageBox.Yes:
                self.model.removeRows(self.idx.row(), 1)
                tele_idx = self.telemetry_model.devices.get(topic)
                if tele_idx:
                    self.telemetry_model.removeRows(tele_idx.row(), 1)

    def get_dump(self):
        self.backup += self.dl.readAll()

    def save_dump(self):
        fname = self.dl.header(QNetworkRequest.ContentDispositionHeader)
        if fname:
            fname = fname.split('=')[1]
            save_file = QFileDialog.getSaveFileName(
                self, "Save config backup",
                "{}/TDM/{}".format(QDir.homePath(), fname))[0]
            if save_file:
                with open(save_file, "wb") as f:
                    f.write(self.backup)

    def closeEvent(self, event):
        event.ignore()
コード例 #2
0
ファイル: DevicesList.py プロジェクト: qbss/tdm
class DevicesListWidget(QWidget):
    def __init__(self, parent, *args, **kwargs):
        super(DevicesListWidget, self).__init__(*args, **kwargs)
        self.setWindowTitle("Devices list")
        self.setWindowState(Qt.WindowMaximized)
        self.setLayout(VLayout(margin=0, spacing=0))

        self.mqtt = parent.mqtt
        self.mdi = parent.mdi
        self.idx = None

        self.settings = QSettings()
        self.hidden_columns = self.settings.value("hidden_columns", [1, 2])

        self.tb = Toolbar(Qt.Horizontal, 16, Qt.ToolButtonTextBesideIcon)
        self.tb.addAction(QIcon("GUI/icons/add.png"), "Add", self.device_add)

        self.layout().addWidget(self.tb)

        self.device_list = TableView()
        self.model = parent.device_model
        self.telemetry_model = parent.telemetry_model
        self.sorted_device_model = QSortFilterProxyModel()
        self.sorted_device_model.setSourceModel(parent.device_model)
        self.device_list.setModel(self.sorted_device_model)
        self.device_list.setupColumns(columns, self.hidden_columns)
        self.device_list.setSortingEnabled(True)
        self.device_list.setWordWrap(True)
        self.device_list.setItemDelegate(DeviceDelegate())
        self.device_list.sortByColumn(DevMdl.TOPIC, Qt.AscendingOrder)
        self.device_list.setContextMenuPolicy(Qt.CustomContextMenu)
        self.layout().addWidget(self.device_list)

        self.device_list.clicked.connect(self.select_device)
        self.device_list.doubleClicked.connect(self.device_config)
        self.device_list.customContextMenuRequested.connect(self.show_list_ctx_menu)

        self.device_list.horizontalHeader().setContextMenuPolicy(Qt.CustomContextMenu)
        self.device_list.horizontalHeader().customContextMenuRequested.connect(self.show_header_ctx_menu)

        self.ctx_menu = QMenu()
        self.ctx_menu_relays = None
        self.create_actions()

        self.build_header_ctx_menu()

    def create_actions(self):
        self.ctx_menu.addAction(QIcon("GUI/icons/configure.png"), "Configure", self.device_config)
        self.ctx_menu.addAction(QIcon("GUI/icons/delete.png"), "Remove", self.device_delete)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/refresh.png"), "Refresh", self.ctx_menu_refresh)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/on.png"), "Power ON", lambda: self.ctx_menu_power(state="ON"))
        self.ctx_menu.addAction(QIcon("GUI/icons/off.png"), "Power OFF", lambda: self.ctx_menu_power(state="OFF"))

        self.ctx_menu_relays = QMenu("Relays")
        self.ctx_menu_relays.setIcon(QIcon("GUI/icons/switch.png"))
        relays_btn = self.ctx_menu.addMenu(self.ctx_menu_relays)

        self.ctx_menu_relays.setEnabled(False)
        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/clear.png"), "Clear retained", self.ctx_menu_clean_retained)
        self.ctx_menu.addSeparator()

        self.ctx_menu_copy = QMenu("Copy")
        self.ctx_menu_copy.setIcon(QIcon("GUI/icons/copy.png"))
        copy_btn = self.ctx_menu.addMenu(self.ctx_menu_copy)

        self.ctx_menu.addSeparator()
        self.ctx_menu.addAction(QIcon("GUI/icons/restart.png"), "Restart", self.ctx_menu_restart)
        self.ctx_menu.addAction(QIcon("GUI/icons/web.png"), "Open WebUI", self.ctx_menu_webui)

        self.ctx_menu_copy.addAction("IP", lambda: self.ctx_menu_copy_value(DevMdl.IP))
        self.ctx_menu_copy.addAction("MAC", lambda: self.ctx_menu_copy_value(DevMdl.MAC))
        self.ctx_menu_copy.addAction("BSSID", lambda: self.ctx_menu_copy_value(DevMdl.BSSID))
        self.ctx_menu_copy.addSeparator()
        self.ctx_menu_copy.addAction("Topic", lambda: self.ctx_menu_copy_value(DevMdl.TOPIC))
        self.ctx_menu_copy.addAction("FullTopic", lambda: self.ctx_menu_copy_value(DevMdl.FULL_TOPIC))
        self.ctx_menu_copy.addAction("STAT topic", lambda: self.ctx_menu_copy_prefix_topic("STAT"))
        self.ctx_menu_copy.addAction("CMND topic", lambda: self.ctx_menu_copy_prefix_topic("CMND"))
        self.ctx_menu_copy.addAction("TELE topic", lambda: self.ctx_menu_copy_prefix_topic("TELE"))

        self.tb.addActions(self.ctx_menu.actions())
        self.tb.widgetForAction(relays_btn).setPopupMode(QToolButton.InstantPopup)
        self.tb.widgetForAction(copy_btn).setPopupMode(QToolButton.InstantPopup)

    def ctx_menu_copy_value(self, column):
        if self.idx:
            row = self.idx.row()
            value = self.model.data(self.model.index(row, column))
            QApplication.clipboard().setText(value)

    def ctx_menu_copy_prefix_topic(self, prefix):
        if self.idx:
            if prefix == "STAT":
                topic = self.model.statTopic(self.idx)
            elif prefix == "CMND":
                topic = self.model.commandTopic(self.idx)
            elif prefix == "TELE":
                topic = self.model.teleTopic(self.idx)
            QApplication.clipboard().setText(topic)

    def ctx_menu_clean_retained(self):
        if self.idx:
            relays = self.model.data(self.model.index(self.idx.row(), DevMdl.POWER))
            if relays and len(relays.keys()>1):
                cmnd_topic = self.model.cmndTopic(self.idx)

                for r in relays.keys():
                    self.mqtt.publish(cmnd_topic + r, retain=True)
                QMessageBox.information(self, "Clear retained", "Cleared reatined messages.")

    def ctx_menu_power(self, relay=None, state=None):
        if self.idx:
            relays = self.model.data(self.model.index(self.idx.row(), DevMdl.POWER))
            cmnd_topic = self.model.commandTopic(self.idx)
            if relay:
                self.mqtt.publish(cmnd_topic+relay, payload=state)

            elif relays:
                for r in relays.keys():
                    self.mqtt.publish(cmnd_topic+r, payload=state)

    def ctx_menu_restart(self):
        if self.idx:
            self.mqtt.publish("{}/restart".format(self.model.commandTopic(self.idx)), payload="1")

    def ctx_menu_refresh(self):
        if self.idx:
            for q in initial_queries:
                self.mqtt.publish("{}/status".format(self.model.commandTopic(self.idx)), payload=q)

    def ctx_menu_telemetry(self):
        if self.idx:
            self.mqtt.publish("{}/status".format(self.model.commandTopic(self.idx)), payload=8)

    def ctx_menu_bssid(self):
        if self.idx:
            bssid = self.model.bssid(self.idx)
            current = self.settings.value("BSSID/{}".format(bssid), "")
            alias, ok = QInputDialog.getText(self, "BSSID alias", "Alias for {}. Clear to remove.".format(bssid), text=current)
            if ok:
                self.settings.setValue("BSSID/{}".format(bssid), alias)
                self.model.refreshBSSID()

    def ctx_menu_webui(self):
        if self.idx:
            QDesktopServices.openUrl(QUrl("http://{}".format(self.model.ip(self.idx))))

    def show_list_ctx_menu(self, at):
        self.select_device(self.device_list.indexAt(at))
        self.ctx_menu.popup(self.device_list.viewport().mapToGlobal(at))

    def build_header_ctx_menu(self):
        self.hdr_ctx_menu = QMenu()
        for c in columns.keys():
            a = self.hdr_ctx_menu.addAction(columns[c][0])
            a.setData(c)
            a.setCheckable(True)
            a.setChecked(not self.device_list.isColumnHidden(c))
            a.toggled.connect(self.header_ctx_menu_toggle_col)

    def show_header_ctx_menu(self, at):
        self.hdr_ctx_menu.popup(self.device_list.horizontalHeader().viewport().mapToGlobal(at))

    def header_ctx_menu_toggle_col(self, state):
        self.device_list.setColumnHidden(self.sender().data(), not state)
        hidden_columns = [int(c) for c in columns.keys() if self.device_list.isColumnHidden(c)]
        self.settings.setValue("hidden_columns", hidden_columns)
        self.settings.sync()

    def select_device(self, idx):
        self.idx = self.sorted_device_model.mapToSource(idx)
        self.device = self.model.data(self.model.index(idx.row(), DevMdl.TOPIC))

        relays = self.model.data(self.model.index(self.idx.row(), DevMdl.POWER))
        if relays and len(relays.keys()) > 1:
            self.ctx_menu_relays.setEnabled(True)
            self.ctx_menu_relays.setEnabled(True)
            self.ctx_menu_relays.clear()

            for r in relays.keys():
                actR = self.ctx_menu_relays.addAction("{} ON".format(r))
                actR.triggered.connect(lambda st, x=r: self.ctx_menu_power(x, "ON"))
                actR = self.ctx_menu_relays.addAction("{} OFF".format(r))
                actR.triggered.connect(lambda st, x=r: self.ctx_menu_power(x, "OFF"))
                self.ctx_menu_relays.addSeparator()
        else:
            self.ctx_menu_relays.setEnabled(False)
            self.ctx_menu_relays.clear()

    def device_config(self, idx=None):
        dev_cfg = DevicesConfigWidget(self, self.model.topic(self.idx))
        self.mdi.addSubWindow(dev_cfg)
        dev_cfg.setWindowState(Qt.WindowMaximized)

    def device_add(self):
        rc = self.model.rowCount()
        self.model.insertRow(rc)
        dlg = DeviceEditDialog(self.model, rc)
        dlg.full_topic.setText("%prefix%/%topic%/")

        if dlg.exec_() == QDialog.Accepted:
            self.model.setData(self.model.index(rc, DevMdl.FRIENDLY_NAME), self.model.data(self.model.index(rc, DevMdl.TOPIC)))
            topic = dlg.topic.text()
            tele_dev = self.telemetry_model.addDevice(TasmotaDevice, topic)
            self.telemetry_model.devices[topic] = tele_dev
        else:
            self.model.removeRow(rc)

    def device_delete(self):
        if self.idx:
            topic = self.model.topic(self.idx)
            if QMessageBox.question(self, "Confirm", "Do you want to remove '{}' from devices list?".format(topic)) == QMessageBox.Yes:
                self.model.removeRows(self.idx.row(),1)
                tele_idx = self.telemetry_model.devices.get(topic)
                if tele_idx:
                    self.telemetry_model.removeRows(tele_idx.row(),1)

    def closeEvent(self, event):
        event.ignore()