Example #1
0
    def __init__(self, device, *args, **kwargs):
        super(SetOptionsDialog, self).__init__(*args, **kwargs)
        self.setWindowTitle("SetOptions [{}]".format(device.p['FriendlyName1']))
        self.setMinimumWidth(300)
        self.device = device

        self.gbs = []

        vl = VLayout()

        for i, so in setoptions.items():
            gb = GroupBoxV("SetOption{}".format(i))
            cb = DictComboBox(so['parameters'])
            gb.addWidgets([QLabel(so['description']), cb])

            vl.addWidget(gb)

        # self.gb = DictComboBox(self.device.modules)
        # self.gb.setCurrentText(self.device.modules[str(self.device.p['Module'])])
        # gbxModules.addWidget(self.gb)

        btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
        btns.accepted.connect(self.accept)
        btns.rejected.connect(self.reject)

        vl.addWidget(btns)
        self.setLayout(vl)
Example #2
0
    def tabModule(self):
        module = QWidget()
        module.setLayout(HLayout())

        self.gbModule = GroupBoxH("Module")
        self.cbModule = QComboBox()
        self.pbModuleSet = QPushButton("Save and close (device will restart)")
        self.gbModule.addWidgets([self.cbModule, self.pbModuleSet])
        self.pbModuleSet.clicked.connect(self.saveModule)

        self.gbGPIO = QGroupBox("GPIO")
        fl_gpio = QFormLayout()
        pbGPIOSet = QPushButton("Save and close (device will restart)")
        fl_gpio.addWidget(pbGPIOSet)
        pbGPIOSet.clicked.connect(self.saveGPIOs)

        self.gbGPIO.setLayout(fl_gpio)

        mg_vl = VLayout([0, 0, 3, 0])
        mg_vl.addWidgets([self.gbModule, self.gbGPIO])
        mg_vl.setStretch(0, 1)
        mg_vl.setStretch(1, 3)

        self.gbFirmware = GroupBoxV("Firmware", margin=[3, 0, 0, 0])
        lb = QLabel("Feature under development.")
        lb.setAlignment(Qt.AlignCenter)
        lb.setEnabled(False)
        self.gbFirmware.addWidget(lb)

        module.layout().addLayout(mg_vl)
        module.layout().addWidget(self.gbFirmware)
        return module
Example #3
0
    def __init__(self, device, *args, **kwargs):
        super(ModuleDialog, self).__init__(*args, **kwargs)
        self.setWindowTitle("Module [{}]".format(device.name))
        self.setMinimumWidth(300)
        self.device = device

        btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
        btns.accepted.connect(self.accept)
        btns.rejected.connect(self.reject)

        gbxModules = GroupBoxV("Select Module")
        self.gb = DictComboBox(self.device.modules)
        self.gb.setCurrentText(self.device.modules[str(self.device.p['Module'])])
        gbxModules.addWidget(self.gb)

        vl = VLayout()
        vl.addWidgets([gbxModules, btns])
        self.setLayout(vl)
Example #4
0
    def __init__(self, devices):
        super().__init__()
        self.setWindowFlags(Qt.Tool)
        self.setMinimumSize(QSize(640, 480))
        self.devices = devices
        self.command = ""
        vl = VLayout()

        gbxDevice = GroupBoxV("Commands history for:")
        gbDevice = QComboBox()
        gbDevice.addItems([d.p['FriendlyName1'] for d in devices])
        gbxDevice.addWidget(gbDevice)

        self.lwCommands = QListWidget()

        vl.addWidgets([
            gbxDevice, self.lwCommands,
            QLabel("Double-click a command to use it, ESC to close.")
        ])
        self.setLayout(vl)

        gbDevice.currentIndexChanged.connect(self.load_history)
        self.lwCommands.doubleClicked.connect(self.select_command)
Example #5
0
    def __init__(self, device, *args, **kwargs):
        super(RulesWidget, self).__init__(*args, **kwargs)
        self.device = device
        self.setWindowTitle("Rules [{}]".format(
            self.device.p['FriendlyName1']))

        self.poll_timer = QTimer()
        self.poll_timer.timeout.connect(self.poll)
        self.poll_timer.start(1000)

        self.vars = [''] * 5
        self.var = None

        self.mems = [''] * 5
        self.mem = None

        self.rts = [0] * 8
        self.rt = None

        fnt_mono = QFont("asd")
        fnt_mono.setStyleHint(QFont.TypeWriter)

        tb = Toolbar(iconsize=24, label_position=Qt.ToolButtonTextBesideIcon)
        vl = VLayout(margin=0, spacing=0)

        self.cbRule = QComboBox()
        self.cbRule.setMinimumWidth(100)
        self.cbRule.addItems(["Rule{}".format(nr + 1) for nr in range(3)])
        self.cbRule.currentTextChanged.connect(self.load_rule)

        tb.addWidget(self.cbRule)

        self.actEnabled = CheckableAction(QIcon("GUI/icons/off.png"),
                                          "Enabled")
        self.actEnabled.triggered.connect(self.toggle_rule)

        self.actOnce = CheckableAction(QIcon("GUI/icons/once.png"), "Once")
        self.actOnce.triggered.connect(self.toggle_once)

        self.actStopOnError = CheckableAction(QIcon("GUI/icons/stop.png"),
                                              "Stop on error")
        self.actStopOnError.triggered.connect(self.toggle_stop)

        tb.addActions([self.actEnabled, self.actOnce, self.actStopOnError])
        self.cbRule.setFixedHeight(
            tb.widgetForAction(self.actEnabled).height() + 1)

        self.actUpload = tb.addAction(QIcon("GUI/icons/upload.png"), "Upload")
        self.actUpload.triggered.connect(self.upload_rule)

        # tb.addSeparator()
        # self.actLoad = tb.addAction(QIcon("GUI/icons/open.png"), "Load...")
        # self.actSave = tb.addAction(QIcon("GUI/icons/save.png"), "Save...")

        tb.addSpacer()

        self.counter = QLabel("Remaining: 511")
        tb.addWidget(self.counter)

        vl.addWidget(tb)

        hl = HLayout(margin=[3, 0, 0, 0])

        self.gbTriggers = GroupBoxV("Triggers")
        self.triggers = QListWidget()
        self.triggers.setAlternatingRowColors(True)
        self.gbTriggers.addWidget(self.triggers)

        self.gbEditor = GroupBoxV("Rule editor")
        self.editor = QPlainTextEdit()
        self.editor.setFont(fnt_mono)
        self.editor.setPlaceholderText("loading...")
        self.editor.textChanged.connect(self.update_counter)
        self.gbEditor.addWidget(self.editor)

        # hl.addWidgets([self.gbTriggers, self.gbEditor])
        hl.addWidget(self.gbEditor)

        self.rules_hl = RuleHighLighter(self.editor.document())

        vl_helpers = VLayout(margin=[0, 0, 3, 0])

        ###### Polling
        self.gbPolling = GroupBoxH("Automatic polling")
        self.pbPollVars = QPushButton("VARs")
        self.pbPollVars.setCheckable(True)
        self.pbPollMems = QPushButton("MEMs")
        self.pbPollMems.setCheckable(True)
        self.pbPollRTs = QPushButton("RuleTimers")
        self.pbPollRTs.setCheckable(True)

        self.gbPolling.addWidgets(
            [self.pbPollVars, self.pbPollMems, self.pbPollRTs])

        ###### VARS
        # self.gbVars = GroupBoxV("VARs")
        self.lwVars = QListWidget()
        self.lwVars.setAlternatingRowColors(True)
        self.lwVars.addItems(
            ["VAR{}: loading...".format(i) for i in range(1, 6)])
        self.lwVars.clicked.connect(self.select_var)
        self.lwVars.doubleClicked.connect(self.set_var)
        # self.gbVars.addWidget(self.lwVars)

        ###### MEMS
        # self.gbMems = GroupBoxV("MEMs")
        self.lwMems = QListWidget()
        self.lwMems.setAlternatingRowColors(True)
        self.lwMems.addItems(
            ["MEM{}: loading...".format(i) for i in range(1, 6)])
        self.lwMems.clicked.connect(self.select_mem)
        self.lwMems.doubleClicked.connect(self.set_mem)
        # self.gbMems.addWidget(self.lwMems)

        ###### RuleTimers
        # self.gbRTs = GroupBoxV("Rule timers")
        self.lwRTs = QListWidget()
        self.lwRTs.setAlternatingRowColors(True)
        self.lwRTs.addItems(
            ["RuleTimer{}: loading...".format(i) for i in range(1, 9)])
        self.lwRTs.clicked.connect(self.select_rt)
        self.lwRTs.doubleClicked.connect(self.set_rt)
        # self.gbRTs.addWidget(self.lwRTs)

        # vl_helpers.addWidgets([self.gbPolling, self.gbVars, self.gbMems, self.gbRTs])
        vl_helpers.addWidgets(
            [self.gbPolling, self.lwVars, self.lwMems, self.lwRTs])
        hl.addLayout(vl_helpers)
        hl.setStretch(0, 3)
        hl.setStretch(1, 1)
        # hl.setStretch(2, 1)

        vl.addLayout(hl)
        self.setLayout(vl)
Example #6
0
class RulesWidget(QWidget):
    sendCommand = pyqtSignal(str, str)

    def __init__(self, device, *args, **kwargs):
        super(RulesWidget, self).__init__(*args, **kwargs)
        self.device = device
        self.setWindowTitle("Rules [{}]".format(
            self.device.p['FriendlyName1']))

        self.poll_timer = QTimer()
        self.poll_timer.timeout.connect(self.poll)
        self.poll_timer.start(1000)

        self.vars = [''] * 5
        self.var = None

        self.mems = [''] * 5
        self.mem = None

        self.rts = [0] * 8
        self.rt = None

        fnt_mono = QFont("asd")
        fnt_mono.setStyleHint(QFont.TypeWriter)

        tb = Toolbar(iconsize=24, label_position=Qt.ToolButtonTextBesideIcon)
        vl = VLayout(margin=0, spacing=0)

        self.cbRule = QComboBox()
        self.cbRule.setMinimumWidth(100)
        self.cbRule.addItems(["Rule{}".format(nr + 1) for nr in range(3)])
        self.cbRule.currentTextChanged.connect(self.load_rule)

        tb.addWidget(self.cbRule)

        self.actEnabled = CheckableAction(QIcon("GUI/icons/off.png"),
                                          "Enabled")
        self.actEnabled.triggered.connect(self.toggle_rule)

        self.actOnce = CheckableAction(QIcon("GUI/icons/once.png"), "Once")
        self.actOnce.triggered.connect(self.toggle_once)

        self.actStopOnError = CheckableAction(QIcon("GUI/icons/stop.png"),
                                              "Stop on error")
        self.actStopOnError.triggered.connect(self.toggle_stop)

        tb.addActions([self.actEnabled, self.actOnce, self.actStopOnError])
        self.cbRule.setFixedHeight(
            tb.widgetForAction(self.actEnabled).height() + 1)

        self.actUpload = tb.addAction(QIcon("GUI/icons/upload.png"), "Upload")
        self.actUpload.triggered.connect(self.upload_rule)

        # tb.addSeparator()
        # self.actLoad = tb.addAction(QIcon("GUI/icons/open.png"), "Load...")
        # self.actSave = tb.addAction(QIcon("GUI/icons/save.png"), "Save...")

        tb.addSpacer()

        self.counter = QLabel("Remaining: 511")
        tb.addWidget(self.counter)

        vl.addWidget(tb)

        hl = HLayout(margin=[3, 0, 0, 0])

        self.gbTriggers = GroupBoxV("Triggers")
        self.triggers = QListWidget()
        self.triggers.setAlternatingRowColors(True)
        self.gbTriggers.addWidget(self.triggers)

        self.gbEditor = GroupBoxV("Rule editor")
        self.editor = QPlainTextEdit()
        self.editor.setFont(fnt_mono)
        self.editor.setPlaceholderText("loading...")
        self.editor.textChanged.connect(self.update_counter)
        self.gbEditor.addWidget(self.editor)

        # hl.addWidgets([self.gbTriggers, self.gbEditor])
        hl.addWidget(self.gbEditor)

        self.rules_hl = RuleHighLighter(self.editor.document())

        vl_helpers = VLayout(margin=[0, 0, 3, 0])

        ###### Polling
        self.gbPolling = GroupBoxH("Automatic polling")
        self.pbPollVars = QPushButton("VARs")
        self.pbPollVars.setCheckable(True)
        self.pbPollMems = QPushButton("MEMs")
        self.pbPollMems.setCheckable(True)
        self.pbPollRTs = QPushButton("RuleTimers")
        self.pbPollRTs.setCheckable(True)

        self.gbPolling.addWidgets(
            [self.pbPollVars, self.pbPollMems, self.pbPollRTs])

        ###### VARS
        # self.gbVars = GroupBoxV("VARs")
        self.lwVars = QListWidget()
        self.lwVars.setAlternatingRowColors(True)
        self.lwVars.addItems(
            ["VAR{}: loading...".format(i) for i in range(1, 6)])
        self.lwVars.clicked.connect(self.select_var)
        self.lwVars.doubleClicked.connect(self.set_var)
        # self.gbVars.addWidget(self.lwVars)

        ###### MEMS
        # self.gbMems = GroupBoxV("MEMs")
        self.lwMems = QListWidget()
        self.lwMems.setAlternatingRowColors(True)
        self.lwMems.addItems(
            ["MEM{}: loading...".format(i) for i in range(1, 6)])
        self.lwMems.clicked.connect(self.select_mem)
        self.lwMems.doubleClicked.connect(self.set_mem)
        # self.gbMems.addWidget(self.lwMems)

        ###### RuleTimers
        # self.gbRTs = GroupBoxV("Rule timers")
        self.lwRTs = QListWidget()
        self.lwRTs.setAlternatingRowColors(True)
        self.lwRTs.addItems(
            ["RuleTimer{}: loading...".format(i) for i in range(1, 9)])
        self.lwRTs.clicked.connect(self.select_rt)
        self.lwRTs.doubleClicked.connect(self.set_rt)
        # self.gbRTs.addWidget(self.lwRTs)

        # vl_helpers.addWidgets([self.gbPolling, self.gbVars, self.gbMems, self.gbRTs])
        vl_helpers.addWidgets(
            [self.gbPolling, self.lwVars, self.lwMems, self.lwRTs])
        hl.addLayout(vl_helpers)
        hl.setStretch(0, 3)
        hl.setStretch(1, 1)
        # hl.setStretch(2, 1)

        vl.addLayout(hl)
        self.setLayout(vl)

    def load_rule(self, text):
        self.editor.setPlaceholderText("loading...")
        self.sendCommand.emit(self.device.cmnd_topic(text), "")

    def toggle_rule(self, state):
        self.sendCommand.emit(
            self.device.cmnd_topic(self.cbRule.currentText()), str(int(state)))

    def toggle_once(self, state):
        self.sendCommand.emit(
            self.device.cmnd_topic(self.cbRule.currentText()),
            str(4 + int(state)))

    def toggle_stop(self, state):
        self.sendCommand.emit(
            self.device.cmnd_topic(self.cbRule.currentText()),
            str(8 + int(state)))

    def clean_rule(self):
        re_spaces = re.compile(r"\s{2,}")
        rule = self.editor.toPlainText().replace("\t", " ").replace("\n", " ")
        rule = re.sub(re_spaces, ' ', rule)
        return rule

    def upload_rule(self):
        rule = self.clean_rule()
        if len(rule) == 0:
            rule = '""'
        self.sendCommand.emit(
            self.device.cmnd_topic(self.cbRule.currentText()), rule)

    def update_counter(self):
        self.counter.setText("Remaining: {}".format(511 -
                                                    len(self.clean_rule())))

    def poll(self):
        if self.pbPollVars.isChecked():
            self.sendCommand.emit(self.device.cmnd_topic("backlog"),
                                  "var1; var2; var3; var4; var5")

        if self.pbPollMems.isChecked():
            self.sendCommand.emit(self.device.cmnd_topic("backlog"),
                                  "mem1; mem2; mem3; mem4; mem5")

        if self.pbPollRTs.isChecked():
            self.sendCommand.emit(self.device.cmnd_topic("ruletimer"), "")

    def select_var(self, idx):
        self.var = idx.row()

    def set_var(self, idx):
        curr = self.vars[self.var]
        new, ok = QInputDialog.getText(
            self,
            "Set VAR",
            "Set VAR{} value. Empty to clear.".format(self.var + 1),
            text=curr)
        if ok:
            if new == '':
                new = '"'
            self.sendCommand.emit(
                self.device.cmnd_topic("var{}".format(self.var + 1)), new)

    def select_mem(self, idx):
        self.mem = idx.row()

    def set_mem(self, idx):
        curr = self.mems[self.mem]
        new, ok = QInputDialog.getText(
            self,
            "Set mem",
            "Set mem{} value. Empty to clear.".format(self.mem + 1),
            text=curr)
        if ok:
            if new == '':
                new = '"'
            self.sendCommand.emit(
                self.device.cmnd_topic("mem{}".format(self.mem + 1)), new)

    def select_rt(self, idx):
        self.rt = idx.row()

    def set_rt(self, idx):
        curr = self.rts[self.rt]
        new, ok = QInputDialog.getInt(self,
                                      "Set ruletimer",
                                      "Set ruletimer{} value.".format(self.rt +
                                                                      1),
                                      value=curr)
        if ok:
            self.sendCommand.emit(
                self.device.cmnd_topic("ruletimer{}".format(self.rt + 1)),
                str(new))

    @pyqtSlot(str, str)
    def parseMessage(self, topic, msg):
        if self.device.matches(topic):
            if self.device.reply == "RESULT":
                try:
                    payload = loads(msg)
                    first = list(payload)[0]

                    if first.startswith('Rule'):
                        rule = payload['Rules'].replace(
                            " on ",
                            "\non ").replace(" do ", " do\n\t").replace(
                                " endon", "\nendon ").rstrip(" ")
                        if len(rule) == 0:
                            self.editor.setPlaceholderText(
                                "rule buffer is empty")
                        self.editor.setPlainText(rule)

                        self.actEnabled.setChecked(payload[first] == "ON")
                        self.actOnce.setChecked(payload['Once'] == 'ON')
                        self.actStopOnError.setChecked(
                            payload['StopOnError'] == 'ON')

                    elif first == 'Var1':
                        if len(
                                payload
                        ) == 1:  # old firmware, doesn't return all Vars in a dict
                            self.lwVars.item(0).setText("VAR1: {}".format(
                                payload[first]))
                            self.vars[0] = payload[first]
                            for var in range(2, 6):
                                self.sendCommand.emit(
                                    self.device.cmnd_topic(
                                        "var{}".format(var)), "")
                        else:
                            for k, v in payload.items():
                                row = int(k.replace("Var", "")) - 1
                                self.lwVars.item(row).setText(
                                    "VAR{}: {}".format(row + 1, v))
                                self.vars[row] = v

                    elif first.startswith('Var'):
                        row = int(first.replace("Var", "")) - 1
                        self.lwVars.item(row).setText("VAR{}: {}".format(
                            row + 1, payload[first]))
                        self.vars[row] = payload[first]

                    elif first == 'Mem1':
                        if len(
                                payload
                        ) == 1:  # old firmware, doesn't return all Mems in a dict
                            self.lwMems.item(0).setText("MEM1: {}".format(
                                payload[first]))
                            self.mems[0] = payload[first]
                            for mem in range(2, 6):
                                self.sendCommand.emit(
                                    self.device.cmnd_topic(
                                        "mem{}".format(mem)), "")
                        else:
                            for k, v in payload.items():
                                row = int(k.replace("Mem", "")) - 1
                                self.lwMems.item(row).setText(
                                    "MEM{}: {}".format(row + 1, v))
                                self.mems[row] = v

                    elif first.startswith('Mem'):
                        row = int(first.replace("Mem", "")) - 1
                        self.lwMems.item(row).setText("MEM{}: {}".format(
                            row + 1, payload[first]))
                        self.mems[row] = payload[first]

                    elif first == 'T1':
                        for i, rt in enumerate(payload.keys()):
                            self.lwRTs.item(i).setText(
                                "RuleTimer{}: {}".format(i + 1, payload[rt]))
                            self.rts[i] = payload[rt]

                except JSONDecodeError as e:
                    QMessageBox.critical(
                        self, "Rule loading error",
                        "Can't load the rule from device.\n{}".format(e))
Example #7
0
File: Rules.py Project: xninjax/tdm
class RulesWidget(QWidget):
    sendCommand = pyqtSignal(str, str)

    def __init__(self, device, *args, **kwargs):
        super(RulesWidget, self).__init__(*args, **kwargs)
        self.device = device
        self.setWindowTitle("Rules [{}]".format(self.device.p['FriendlyName1']))

        self.poll_timer = QTimer()
        self.poll_timer.timeout.connect(self.poll)
        self.poll_timer.start(1000)

        self.vars = [''] * 16
        self.var = None

        self.mems = [''] * 16
        self.mem = None

        self.rts = [0] * 8
        self.rt = None

        fnt_mono = QFont("asd")
        fnt_mono.setStyleHint(QFont.TypeWriter)

        tb = Toolbar(iconsize=24, label_position=Qt.ToolButtonTextBesideIcon)
        vl = VLayout(margin=0, spacing=0)

        self.cbRule = QComboBox()
        self.cbRule.setMinimumWidth(100)
        self.cbRule.addItems(["Rule{}".format(nr + 1) for nr in range(3)])
        self.cbRule.currentTextChanged.connect(self.load_rule)

        tb.addWidget(self.cbRule)

        self.actEnabled = CheckableAction(QIcon(":/off.png"), "Enabled")
        self.actEnabled.triggered.connect(self.toggle_rule)

        self.actOnce = CheckableAction(QIcon(":/once.png"), "Once")
        self.actOnce.triggered.connect(self.toggle_once)

        self.actStopOnError = CheckableAction(QIcon(":/stop.png"), "Stop on error")
        self.actStopOnError.triggered.connect(self.toggle_stop)

        tb.addActions([self.actEnabled, self.actOnce, self.actStopOnError])
        self.cbRule.setFixedHeight(tb.widgetForAction(self.actEnabled).height()+1)

        self.actUpload = tb.addAction(QIcon(":/upload.png"), "Upload")
        self.actUpload.triggered.connect(self.upload_rule)

        # tb.addSeparator()
        # self.actLoad = tb.addAction(QIcon(":/open.png"), "Load...")
        # self.actSave = tb.addAction(QIcon(":/save.png"), "Save...")

        tb.addSpacer()

        self.counter = QLabel("Remaining: 511")
        tb.addWidget(self.counter)

        vl.addWidget(tb)

        hl = HLayout(margin=[3, 0, 0, 0])

        self.gbTriggers = GroupBoxV("Triggers")
        self.triggers = QListWidget()
        self.triggers.setAlternatingRowColors(True)
        self.gbTriggers.addWidget(self.triggers)

        self.gbEditor = GroupBoxV("Rule editor")
        self.editor = QPlainTextEdit()
        self.editor.setFont(fnt_mono)
        self.editor.setPlaceholderText("loading...")
        self.editor.textChanged.connect(self.update_counter)
        self.gbEditor.addWidget(self.editor)

        # hl.addWidgets([self.gbTriggers, self.gbEditor])
        hl.addWidget(self.gbEditor)

        self.rules_hl = RuleHighLighter(self.editor.document())

        vl_helpers = VLayout(margin=[0, 0, 3, 0])

        ###### Polling
        self.gbPolling = GroupBoxH("Automatic polling")
        self.pbPollVars = QPushButton("VARs")
        self.pbPollVars.setCheckable(True)
        self.pbPollMems = QPushButton("MEMs")
        self.pbPollMems.setCheckable(True)
        self.pbPollRTs = QPushButton("RuleTimers")
        self.pbPollRTs.setCheckable(True)

        self.gbPolling.addWidgets([self.pbPollVars, self.pbPollMems, self.pbPollRTs])

        ###### VARS
        # self.gbVars = GroupBoxV("VARs")
        self.lwVars = QListWidget()
        self.lwVars.setAlternatingRowColors(True)
        self.lwVars.addItems(["VAR{}: <unknown>".format(i) for i in range(1, 17)])
        self.lwVars.clicked.connect(self.select_var)
        self.lwVars.doubleClicked.connect(self.set_var)
        # self.gbVars.addWidget(self.lwVars)

        ###### MEMS
        # self.gbMems = GroupBoxV("MEMs")
        self.lwMems = QListWidget()
        self.lwMems.setAlternatingRowColors(True)
        self.lwMems.addItems(["MEM{}: <unknown>".format(i) for i in range(1, 17)])
        self.lwMems.clicked.connect(self.select_mem)
        self.lwMems.doubleClicked.connect(self.set_mem)
        # self.gbMems.addWidget(self.lwMems)

        ###### RuleTimers
        # self.gbRTs = GroupBoxV("Rule timers")
        self.lwRTs = QListWidget()
        self.lwRTs.setAlternatingRowColors(True)
        self.lwRTs.addItems(["RuleTimer{}: <unknown>".format(i) for i in range(1, 9)])
        self.lwRTs.clicked.connect(self.select_rt)
        self.lwRTs.doubleClicked.connect(self.set_rt)
        # self.gbRTs.addWidget(self.lwRTs)

        # vl_helpers.addWidgets([self.gbPolling, self.gbVars, self.gbMems, self.gbRTs])
        vl_helpers.addWidgets([self.gbPolling, self.lwVars, self.lwMems, self.lwRTs])
        vl_helpers.setStretch(1, 16)
        vl_helpers.setStretch(2, 16)
        vl_helpers.setStretch(3, 8)
        hl.addLayout(vl_helpers)
        hl.setStretch(0, 3)
        hl.setStretch(1, 1)
        # hl.setStretch(2, 1)

        vl.addLayout(hl)
        self.setLayout(vl)

    def load_rule(self, text):
        self.editor.setPlaceholderText("loading...")
        self.sendCommand.emit(self.device.cmnd_topic(text), "")

    def toggle_rule(self, state):
        self.sendCommand.emit(self.device.cmnd_topic(self.cbRule.currentText()), str(int(state)))

    def toggle_once(self, state):
        self.sendCommand.emit(self.device.cmnd_topic(self.cbRule.currentText()), str(4+int(state)))

    def toggle_stop(self, state):
        self.sendCommand.emit(self.device.cmnd_topic(self.cbRule.currentText()), str(8+int(state)))

    def clean_rule(self):
        re_spaces = re.compile(r"\s{2,}")
        rule = self.editor.toPlainText().replace("\t", " ").replace("\n", " ")
        rule = re.sub(re_spaces, ' ', rule)
        return rule

    def upload_rule(self):
        rule = self.clean_rule()
        if len(rule) == 0:
            rule = '""'
        self.sendCommand.emit(self.device.cmnd_topic(self.cbRule.currentText()), rule)

    def update_counter(self):
        self.counter.setText("Remaining: {}".format(511-len(self.clean_rule())))

    def poll(self):
        if self.pbPollVars.isChecked():
            self.sendCommand.emit(self.device.cmnd_topic("var"), "")

        if self.pbPollMems.isChecked():
            self.sendCommand.emit(self.device.cmnd_topic("mem"), "")

        if self.pbPollRTs.isChecked():
            self.sendCommand.emit(self.device.cmnd_topic("ruletimer"), "")

    def select_var(self, idx):
        self.var = idx.row()

    def set_var(self, idx):
        curr = self.vars[self.var]
        new, ok = QInputDialog.getText(self, "Set VAR", "Set VAR{} value. Empty to clear.".format(self.var+1), text=curr)
        if ok:
            if new == '':
                new = '"'
            self.sendCommand.emit(self.device.cmnd_topic("var{}".format(self.var+1)), new)

    def select_mem(self, idx):
        self.mem = idx.row()

    def set_mem(self, idx):
        curr = self.mems[self.mem]
        new, ok = QInputDialog.getText(self, "Set mem", "Set mem{} value. Empty to clear.".format(self.mem+1), text=curr)
        if ok:
            if new == '':
                new = '"'
            self.sendCommand.emit(self.device.cmnd_topic("mem{}".format(self.mem+1)), new)

    def select_rt(self, idx):
        self.rt = idx.row()
        
    def set_rt(self, idx):
        curr = self.rts[self.rt]
        new, ok = QInputDialog.getInt(self, "Set ruletimer", "Set ruletimer{} value.".format(self.rt+1), value=curr)
        if ok:
            self.sendCommand.emit(self.device.cmnd_topic("ruletimer{}".format(self.rt+1)), str(new))

    def display_rule(self, payload, rule):
            rules = payload['Rules'].replace(" on ", "\non ").replace(" do ", " do\n\t").replace(" endon", "\nendon ").rstrip(" ")
            if len(rules) == 0:
                self.editor.setPlaceholderText("rule buffer is empty")
            self.editor.setPlainText(rules)

            self.actEnabled.setChecked(payload[rule] == "ON")
            self.actOnce.setChecked(payload['Once'] == 'ON')
            self.actStopOnError.setChecked(payload['StopOnError'] == 'ON')

    @pyqtSlot(str, str)
    def parseMessage(self, topic, msg):
        if self.device.matches(topic):
            payload = None
            if msg.startswith("{"):
                try:
                    payload = loads(msg)
                except JSONDecodeError as e:
                    # JSON parse exception means that most likely the rule contains an unescaped JSON payload
                    # TDM will attempt parsing using a regex instead of json.loads()
                    parsed_rule = re.match(r"{\"(?P<rule>Rule(\d))\":\"(?P<enabled>ON|OFF)\",\"Once\":\"(?P<Once>ON|OFF)\",\"StopOnError\":\"(?P<StopOnError>ON|OFF)\",\"Free\":\d+,\"Rules\":\"(?P<Rules>.*?)\"}$", msg, re.IGNORECASE)
                    # modify the resulting matchdict to follow the keys of original JSON payload
                    payload = deepcopy(parsed_rule.groupdict())
                    rule = payload.pop("rule")
                    payload[rule] = payload.pop("enabled")
                    self.display_rule(payload, rule)
                else:

                    if payload:
                        keys = list(payload.keys())
                        fk = keys[0]

                        if self.device.reply == 'RESULT' and fk == "T1" or self.device.reply == "RULETIMER":
                            for i, rt in enumerate(payload.keys()):
                                self.lwRTs.item(i).setText("RuleTimer{}: {}".format(i+1, payload[rt]))
                                self.rts[i] = payload[rt]

                        elif self.device.reply == 'RESULT' and fk.startswith("Var") or self.device.reply.startswith("VAR"):
                            for k, v in payload.items():
                                row = int(k.replace("Var", "")) - 1
                                self.lwVars.item(row).setText("VAR{}: {}".format(row + 1, v))
                                self.vars[row] = v

                        elif self.device.reply == 'RESULT' and fk.startswith("Mem") or self.device.reply.startswith("MEM"):
                            for k, v in payload.items():
                                row = int(k.replace("Mem", "")) - 1
                                self.lwMems.item(row).setText("MEM{}: {}".format(row + 1, v))
                                self.mems[row] = v

                        elif self.device.reply == 'RESULT' and fk.startswith("Rule") or self.device.reply.startswith("RULE"):
                            self.display_rule(payload, fk)
Example #8
0
    def __init__(self, device, *args, **kwargs):
        super(TimersDialog, self).__init__(*args, **kwargs)
        self.device = device
        self.timers = {}
        self.setWindowTitle("Timers [{}]".format(
            self.device.p['FriendlyName1']))

        vl = VLayout()

        self.gbTimers = GroupBoxV("Enabled", spacing=5)
        self.gbTimers.setCheckable(True)
        self.gbTimers.toggled.connect(self.toggleTimers)

        self.cbTimer = QComboBox()
        self.cbTimer.addItems(["Timer{}".format(nr + 1) for nr in range(16)])
        self.cbTimer.currentTextChanged.connect(self.loadTimer)

        hl_tmr_arm_rpt = HLayout(0)
        self.cbTimerArm = QCheckBox("Arm")
        self.cbTimerArm.clicked.connect(lambda x: self.describeTimer())
        self.cbTimerRpt = QCheckBox("Repeat")
        self.cbTimerRpt.clicked.connect(lambda x: self.describeTimer())
        hl_tmr_arm_rpt.addWidgets([self.cbTimerArm, self.cbTimerRpt])

        hl_tmr_out_act = HLayout(0)
        self.cbxTimerOut = QComboBox()
        self.cbxTimerOut.addItems(self.device.power().keys())
        self.cbxTimerOut.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        self.cbxTimerAction = QComboBox()
        self.cbxTimerAction.addItems(["Off", "On", "Toggle", "Rule"])
        self.cbxTimerAction.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        hl_tmr_out_act.addWidgets([self.cbxTimerOut, self.cbxTimerAction])

        self.TimerMode = QButtonGroup()
        rbTime = QRadioButton("Time")
        rbSunrise = QRadioButton("Sunrise ({})".format(
            self.device.p['Sunrise']))
        rbSunset = QRadioButton("Sunset ({})".format(self.device.p['Sunset']))
        self.TimerMode.addButton(rbTime, 0)
        self.TimerMode.addButton(rbSunrise, 1)
        self.TimerMode.addButton(rbSunset, 2)
        self.TimerMode.buttonClicked.connect(lambda x: self.describeTimer())
        gbTimerMode = GroupBoxH("Mode")
        gbTimerMode.addWidgets(self.TimerMode.buttons())

        hl_tmr_time = HLayout(0)
        self.cbxTimerPM = QComboBox()
        self.cbxTimerPM.addItems(["+", "-"])
        self.cbxTimerPM.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        self.TimerMode.buttonClicked[int].connect(
            lambda x: self.cbxTimerPM.setEnabled(x != 0))
        self.teTimerTime = QTimeEdit()
        self.teTimerTime.setButtonSymbols(QTimeEdit.NoButtons)
        self.teTimerTime.setAlignment(Qt.AlignCenter)
        self.teTimerTime.timeChanged.connect(lambda x: self.describeTimer())

        lbWnd = QLabel("Window:")
        lbWnd.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        self.cbxTimerWnd = QComboBox()
        self.cbxTimerWnd.addItems([str(x).zfill(2) for x in range(0, 16)])
        self.cbxTimerWnd.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        hl_tmr_days = HLayout(0)
        self.TimerWeekday = QButtonGroup()
        self.TimerWeekday.setExclusive(False)
        for i, wd in enumerate(
            ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]):
            cb = QCheckBox(wd)
            cb.clicked.connect(lambda x: self.describeTimer())
            hl_tmr_days.addWidget(cb)
            self.TimerWeekday.addButton(cb, i)

        gbTimerDesc = GroupBoxV("Timer description", 5)
        gbTimerDesc.setMinimumHeight(200)
        self.lbTimerDesc = QLabel()
        self.lbTimerDesc.setAlignment(Qt.AlignCenter)
        self.lbTimerDesc.setWordWrap(True)
        gbTimerDesc.layout().addWidget(self.lbTimerDesc)

        hl_tmr_time.addWidgets(
            [self.cbxTimerPM, self.teTimerTime, lbWnd, self.cbxTimerWnd])

        self.gbTimers.layout().addWidget(self.cbTimer)
        self.gbTimers.layout().addLayout(hl_tmr_arm_rpt)
        self.gbTimers.layout().addLayout(hl_tmr_out_act)
        self.gbTimers.layout().addWidget(gbTimerMode)
        self.gbTimers.layout().addLayout(hl_tmr_time)
        self.gbTimers.layout().addLayout(hl_tmr_days)

        btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
        reload = btns.addButton("Reload", QDialogButtonBox.ResetRole)

        btns.accepted.connect(self.saveTimer)
        btns.rejected.connect(self.reject)
        reload.clicked.connect(
            lambda: self.loadTimer(self.cbTimer.currentText()))

        vl.addWidgets([self.gbTimers, gbTimerDesc, btns])
        self.setLayout(vl)
Example #9
0
class TimersDialog(QDialog):
    sendCommand = pyqtSignal(str, str)

    def __init__(self, device, *args, **kwargs):
        super(TimersDialog, self).__init__(*args, **kwargs)
        self.device = device
        self.timers = {}
        self.setWindowTitle("Timers [{}]".format(
            self.device.p['FriendlyName1']))

        vl = VLayout()

        self.gbTimers = GroupBoxV("Enabled", spacing=5)
        self.gbTimers.setCheckable(True)
        self.gbTimers.toggled.connect(self.toggleTimers)

        self.cbTimer = QComboBox()
        self.cbTimer.addItems(["Timer{}".format(nr + 1) for nr in range(16)])
        self.cbTimer.currentTextChanged.connect(self.loadTimer)

        hl_tmr_arm_rpt = HLayout(0)
        self.cbTimerArm = QCheckBox("Arm")
        self.cbTimerArm.clicked.connect(lambda x: self.describeTimer())
        self.cbTimerRpt = QCheckBox("Repeat")
        self.cbTimerRpt.clicked.connect(lambda x: self.describeTimer())
        hl_tmr_arm_rpt.addWidgets([self.cbTimerArm, self.cbTimerRpt])

        hl_tmr_out_act = HLayout(0)
        self.cbxTimerOut = QComboBox()
        self.cbxTimerOut.addItems(self.device.power().keys())
        self.cbxTimerOut.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        self.cbxTimerAction = QComboBox()
        self.cbxTimerAction.addItems(["Off", "On", "Toggle", "Rule"])
        self.cbxTimerAction.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        hl_tmr_out_act.addWidgets([self.cbxTimerOut, self.cbxTimerAction])

        self.TimerMode = QButtonGroup()
        rbTime = QRadioButton("Time")
        rbSunrise = QRadioButton("Sunrise ({})".format(
            self.device.p['Sunrise']))
        rbSunset = QRadioButton("Sunset ({})".format(self.device.p['Sunset']))
        self.TimerMode.addButton(rbTime, 0)
        self.TimerMode.addButton(rbSunrise, 1)
        self.TimerMode.addButton(rbSunset, 2)
        self.TimerMode.buttonClicked.connect(lambda x: self.describeTimer())
        gbTimerMode = GroupBoxH("Mode")
        gbTimerMode.addWidgets(self.TimerMode.buttons())

        hl_tmr_time = HLayout(0)
        self.cbxTimerPM = QComboBox()
        self.cbxTimerPM.addItems(["+", "-"])
        self.cbxTimerPM.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        self.TimerMode.buttonClicked[int].connect(
            lambda x: self.cbxTimerPM.setEnabled(x != 0))
        self.teTimerTime = QTimeEdit()
        self.teTimerTime.setButtonSymbols(QTimeEdit.NoButtons)
        self.teTimerTime.setAlignment(Qt.AlignCenter)
        self.teTimerTime.timeChanged.connect(lambda x: self.describeTimer())

        lbWnd = QLabel("Window:")
        lbWnd.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        self.cbxTimerWnd = QComboBox()
        self.cbxTimerWnd.addItems([str(x).zfill(2) for x in range(0, 16)])
        self.cbxTimerWnd.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        hl_tmr_days = HLayout(0)
        self.TimerWeekday = QButtonGroup()
        self.TimerWeekday.setExclusive(False)
        for i, wd in enumerate(
            ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]):
            cb = QCheckBox(wd)
            cb.clicked.connect(lambda x: self.describeTimer())
            hl_tmr_days.addWidget(cb)
            self.TimerWeekday.addButton(cb, i)

        gbTimerDesc = GroupBoxV("Timer description", 5)
        gbTimerDesc.setMinimumHeight(200)
        self.lbTimerDesc = QLabel()
        self.lbTimerDesc.setAlignment(Qt.AlignCenter)
        self.lbTimerDesc.setWordWrap(True)
        gbTimerDesc.layout().addWidget(self.lbTimerDesc)

        hl_tmr_time.addWidgets(
            [self.cbxTimerPM, self.teTimerTime, lbWnd, self.cbxTimerWnd])

        self.gbTimers.layout().addWidget(self.cbTimer)
        self.gbTimers.layout().addLayout(hl_tmr_arm_rpt)
        self.gbTimers.layout().addLayout(hl_tmr_out_act)
        self.gbTimers.layout().addWidget(gbTimerMode)
        self.gbTimers.layout().addLayout(hl_tmr_time)
        self.gbTimers.layout().addLayout(hl_tmr_days)

        btns = QDialogButtonBox(QDialogButtonBox.Save | QDialogButtonBox.Close)
        reload = btns.addButton("Reload", QDialogButtonBox.ResetRole)

        btns.accepted.connect(self.saveTimer)
        btns.rejected.connect(self.reject)
        reload.clicked.connect(
            lambda: self.loadTimer(self.cbTimer.currentText()))

        vl.addWidgets([self.gbTimers, gbTimerDesc, btns])
        self.setLayout(vl)

    def toggleTimers(self, state):
        self.sendCommand.emit(self.device.cmnd_topic('timers'),
                              "ON" if state else "OFF")

    def loadTimer(self, timer=""):
        if not timer:
            timer = self.cbTimer.currentText()
        payload = self.timers[timer]

        if payload:
            self.blockSignals(True)
            self.cbTimerArm.setChecked(payload['Arm'])
            self.cbTimerRpt.setChecked(payload['Repeat'])
            self.cbxTimerAction.setCurrentIndex(payload['Action'])

            output = payload.get('Output')
            if output:
                self.cbxTimerOut.setEnabled(True)
                self.cbxTimerOut.setCurrentIndex(output - 1)
            else:
                self.cbxTimerOut.setEnabled(False)

            mode = payload.get('Mode', 0)
            self.TimerMode.button(mode).setChecked(True)

            h, m = map(int, payload["Time"].split(":"))
            if h < 0:
                self.cbxTimerPM.setCurrentText("-")
                h *= -1
            self.teTimerTime.setTime(QTime(h, m))
            self.cbxTimerWnd.setCurrentText(str(payload['Window']).zfill(2))
            for wd, v in enumerate(payload['Days']):
                self.TimerWeekday.button(wd).setChecked(int(v))

            self.blockSignals(False)
            self.describeTimer()

    def describeTimer(self):
        if self.cbTimerArm.isChecked():
            desc = {
                'days': '',
                'repeat': '',
                'timer': self.cbTimer.currentText().upper()
            }
            repeat = self.cbTimerRpt.isChecked()
            out = self.cbxTimerOut.currentText()
            act = self.cbxTimerAction.currentText()
            mode = self.TimerMode.checkedId()
            pm = self.cbxTimerPM.currentText()
            time = self.teTimerTime.time()
            wnd = int(self.cbxTimerWnd.currentText()) * 60

            if mode == 0:
                if wnd == 0:
                    desc['time'] = "at {}".format(time.toString("hh:mm"))
                else:
                    desc['time'] = "somewhere between {} and {}".format(
                        time.addSecs(wnd * -1).toString("hh:mm"),
                        time.addSecs(wnd).toString("hh:mm"))
            else:
                prefix = "before" if pm == "-" else "after"
                mode_desc = "sunrise" if mode == 1 else "sunset"
                window = "somewhere in a {} minute window centered around ".format(
                    wnd // 30)
                desc['time'] = "{}h{}m {} {}".format(time.hour(),
                                                     time.minute(), prefix,
                                                     mode_desc)

                if wnd > 0:
                    desc['time'] = window + desc['time']

            if repeat:
                day_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
                days = [cb.isChecked() for cb in self.TimerWeekday.buttons()]
                if days.count(True) == 7:
                    desc['days'] = "everyday"
                else:
                    days_list = [day_names[d] for d in range(7) if days[d]]
                    desc['days'] = "on every {}".format(", ".join(days_list))
            else:
                desc['repeat'] = "only ONCE"

            if act == "Rule":
                desc['action'] = "trigger clock#Timer={}".format(
                    self.cbTimer.currentIndex() + 1)
                text = "{timer} will {action} {time} {days} {repeat}".format(
                    **desc)

            elif self.cbxTimerOut.count() > 0:

                if act == "Toggle":
                    desc['action'] = "TOGGLE {}".format(out.upper())
                else:
                    desc['action'] = "set {} to {}".format(
                        out.upper(), act.upper())

                text = "{timer} will {action} {time} {days} {repeat}".format(
                    **desc)
            else:
                text = "{timer} will do nothing because there are no relays configured.".format(
                    **desc)

            self.lbTimerDesc.setText(text)

        else:
            self.lbTimerDesc.setText(
                "{} is not armed, it will do nothing.".format(
                    self.cbTimer.currentText().upper()))

    def saveTimer(self):
        payload = {
            "Arm":
            int(self.cbTimerArm.isChecked()),
            "Mode":
            self.TimerMode.checkedId(),
            "Time":
            self.teTimerTime.time().toString("hh:mm"),
            "Window":
            self.cbxTimerWnd.currentIndex(),
            "Days":
            "".join([
                str(int(cb.isChecked())) for cb in self.TimerWeekday.buttons()
            ]),
            "Repeat":
            int(self.cbTimerRpt.isChecked()),
            "Output":
            self.cbxTimerOut.currentIndex() + 1,
            "Action":
            self.cbxTimerAction.currentIndex()
        }
        self.sendCommand.emit(
            self.device.cmnd_topic(self.cbTimer.currentText()), dumps(payload))
        QMessageBox.information(
            self, "Timer saved",
            "{} data sent to device.".format(self.cbTimer.currentText()))

    @pyqtSlot(str, str)
    def parseMessage(self, topic, msg):
        if self.device.matches(topic):
            if self.device.reply == "RESULT" or self.device.reply == "TIMERS":
                try:
                    payload = loads(msg)
                    first = list(payload)[0]

                except JSONDecodeError as e:
                    error = "Timer loading error", "Can't load the timer from device.\n{}".format(
                        e)
                    logging.critical(error)
                    QMessageBox.critical(self, error)

                else:
                    if first == 'Timers':
                        self.gbTimers.setChecked(payload[first] == "ON")

                    elif first.startswith('Timers'):
                        self.timers.update(payload[first])

                    if first == 'Timers4':
                        self.loadTimer(self.cbTimer.currentText())
Example #10
0
    def tabRules(self):
        rules = QWidget()
        rules.setLayout(VLayout())
        hl = HLayout(0)
        vl_l = VLayout(0)
        self.rg = RuleGroupBox(rules, "Rule editor")
        self.rg.setFlat(True)
        self.rg.cbRule.currentIndexChanged.connect(self.loadRule)
        vl_l.addWidget(self.rg)

        gRT = GroupBoxH("Rule timers")
        vl_RT_func = VLayout(margin=[0, 0, 3, 0])
        self.pbRTPoll = QPushButton("Poll")
        self.pbRTPoll.setCheckable(True)
        self.pbRTSet = QPushButton("Set")

        vl_RT_func.addWidgets([self.pbRTPoll, self.pbRTSet])
        vl_RT_func.addStretch(1)
        gRT.layout().addLayout(vl_RT_func)

        self.twRT = QTableWidget(1, 8)
        self.twRT.setHorizontalHeaderLabels(
            ["T{}".format(i) for i in range(1, 9)])
        for c in range(8):
            self.twRT.horizontalHeader().setSectionResizeMode(
                c, QHeaderView.Stretch)
            self.twRT.setCellWidget(0, c, SpinBox(minimum=0, maximum=32766))
        self.twRT.verticalHeader().hide()

        self.twRT.verticalHeader().setDefaultSectionSize(
            self.twRT.horizontalHeader().height() * 2 + 1)
        self.twRT.setMaximumHeight(self.twRT.horizontalHeader().height() +
                                   self.twRT.rowHeight(0))
        gRT.layout().addWidget(self.twRT)

        gVM = GroupBoxH("VAR/MEM")
        vl_VM_func = VLayout(margin=[3, 0, 0, 0])
        self.pbVMPoll = QPushButton("Poll")
        self.pbVMPoll.setCheckable(True)
        self.pbVMSet = QPushButton("Set")

        vl_VM_func.addWidgets([self.pbVMPoll, self.pbVMSet])
        vl_VM_func.addStretch(1)
        gVM.layout().addLayout(vl_VM_func)

        self.twVM = QTableWidget(2, 5)
        self.twVM.setHorizontalHeaderLabels(
            ["{}".format(i) for i in range(1, 9)])
        self.twVM.setVerticalHeaderLabels(["VAR", "MEM"])
        self.twVM.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        for c in range(5):
            self.twVM.horizontalHeader().setSectionResizeMode(
                c, QHeaderView.Stretch)

        for r in range(2):
            for c in range(5):
                self.twVM.setCellWidget(r, c, QLineEdit())

        self.twVM.verticalHeader().setDefaultSectionSize(
            self.twVM.horizontalHeader().height())
        self.twVM.setMaximumHeight(self.twVM.horizontalHeader().height() +
                                   self.twVM.rowHeight(0) * 2)
        gVM.layout().addWidget(self.twVM)

        hl_rt_vm = HLayout(0)
        hl_rt_vm.addWidgets([gRT, gVM])

        hl.addLayout(vl_l)

        vl_r = VLayout(0)
        self.gbTimers = GroupBoxV("Timers", spacing=5)
        self.gbTimers.setCheckable(True)
        self.gbTimers.setChecked(False)
        self.gbTimers.toggled.connect(self.toggleTimers)

        self.cbTimer = QComboBox()
        self.cbTimer.addItems(["Timer{}".format(nr + 1) for nr in range(16)])
        self.cbTimer.currentIndexChanged.connect(self.loadTimer)

        hl_tmr_arm_rpt = HLayout(0)
        self.cbTimerArm = QCheckBox("Arm")
        self.cbTimerArm.clicked.connect(lambda x: self.describeTimer())
        self.cbTimerRpt = QCheckBox("Repeat")
        self.cbTimerRpt.clicked.connect(lambda x: self.describeTimer())
        hl_tmr_arm_rpt.addWidgets([self.cbTimerArm, self.cbTimerRpt])

        hl_tmr_out_act = HLayout(0)
        self.cbxTimerOut = QComboBox()
        self.cbxTimerOut.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        self.cbxTimerAction = QComboBox()
        self.cbxTimerAction.addItems(["Off", "On", "Toggle", "Rule"])
        self.cbxTimerAction.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        hl_tmr_out_act.addWidgets([self.cbxTimerOut, self.cbxTimerAction])

        self.TimerMode = QButtonGroup()
        rbTime = QRadioButton("Time")
        rbSunrise = QRadioButton("Sunrise ({})")
        rbSunset = QRadioButton("Sunset ({})")
        self.TimerMode.addButton(rbTime, 0)
        self.TimerMode.addButton(rbSunrise, 1)
        self.TimerMode.addButton(rbSunset, 2)
        self.TimerMode.buttonClicked.connect(lambda x: self.describeTimer())
        gbTimerMode = GroupBoxH("Mode")
        gbTimerMode.addWidgets(self.TimerMode.buttons())

        hl_tmr_time = HLayout(0)
        self.cbxTimerPM = QComboBox()
        self.cbxTimerPM.addItems(["+", "-"])
        self.cbxTimerPM.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        self.TimerMode.buttonClicked[int].connect(
            lambda x: self.cbxTimerPM.setEnabled(x != 0))
        self.teTimerTime = QTimeEdit()
        self.teTimerTime.setButtonSymbols(QTimeEdit.NoButtons)
        self.teTimerTime.setAlignment(Qt.AlignCenter)
        self.teTimerTime.timeChanged.connect(lambda x: self.describeTimer())

        lbWnd = QLabel("Window:")
        lbWnd.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        self.cbxTimerWnd = QComboBox()
        self.cbxTimerWnd.addItems([str(x).zfill(2) for x in range(0, 16)])
        self.cbxTimerWnd.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        hl_tmr_days = HLayout(0)
        self.TimerWeekday = QButtonGroup()
        self.TimerWeekday.setExclusive(False)
        for i, wd in enumerate(
            ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]):
            cb = QCheckBox(wd)
            cb.clicked.connect(lambda x: self.describeTimer())
            hl_tmr_days.addWidget(cb)
            self.TimerWeekday.addButton(cb, i)

        gbTimerDesc = GroupBoxV("Timer description", 5)
        gbTimerDesc.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        self.lbTimerDesc = QLabel()
        self.lbTimerDesc.setAlignment(Qt.AlignCenter)
        self.lbTimerDesc.setWordWrap(True)
        gbTimerDesc.layout().addWidget(self.lbTimerDesc)
        hl_tmr_btns = HLayout(0)
        btnCopyTrigger = QPushButton("Copy trigger")
        btnTimerSave = QPushButton("Save")
        hl_tmr_btns.addWidgets([btnCopyTrigger, btnTimerSave])
        hl_tmr_btns.insertStretch(1)

        btnTimerSave.clicked.connect(self.saveTimer)
        btnCopyTrigger.clicked.connect(self.copyTrigger)

        hl_tmr_time.addWidgets(
            [self.cbxTimerPM, self.teTimerTime, lbWnd, self.cbxTimerWnd])

        self.gbTimers.layout().addWidget(self.cbTimer)
        self.gbTimers.layout().addLayout(hl_tmr_arm_rpt)
        self.gbTimers.layout().addLayout(hl_tmr_out_act)
        self.gbTimers.layout().addWidget(gbTimerMode)
        self.gbTimers.layout().addLayout(hl_tmr_time)
        self.gbTimers.layout().addLayout(hl_tmr_days)
        self.gbTimers.layout().addWidget(gbTimerDesc)
        self.gbTimers.layout().addLayout(hl_tmr_btns)

        vl_r.addWidget(self.gbTimers)

        hl.addLayout(vl_r)
        hl.setStretch(0, 2)
        hl.setStretch(1, 1)

        rules.layout().addLayout(hl)
        rules.layout().addLayout(hl_rt_vm)
        rules.layout().setStretch(0, 3)
        rules.layout().setStretch(1, 0)

        return rules
Example #11
0
class DevicesConfigWidget(QWidget):
    def __init__(self, parent, topic, *args, **kwargs):
        super(DevicesConfigWidget, self).__init__(*args, **kwargs)

        self.settings = QSettings("{}/TDM/tdm.cfg".format(QDir.homePath()),
                                  QSettings.IniFormat)
        self.topic = topic
        self.full_topic = self.settings.value(
            'Devices/{}/full_topic'.format(topic))
        self.friendly_name = self.settings.value(
            'Devices/{}/friendly_name'.format(topic))

        self.cmnd_topic = self.full_topic.replace("%topic%",
                                                  self.topic).replace(
                                                      "%prefix%", 'cmnd')
        self.tele_topic = self.full_topic.replace(
            "%topic%", self.topic).replace("%prefix%", 'tele') + "+"
        self.stat_topic = self.full_topic.replace(
            "%topic%", self.topic).replace("%prefix%", 'stat') + "+"

        self.setWindowTitle(self.friendly_name)

        self.mqtt = MqttClient()

        self.module = None
        self.modules = []

        self.gpios = []
        self.supported_gpios = []
        self.gpio_cb = []
        self.timers = False

        self.setLayout(VLayout(margin=[0, 6, 0, 0], spacing=3))

        self.lbModule = QLabel("Connecting...")
        fnt = self.lbModule.font()
        fnt.setPointSize(14)
        fnt.setBold(True)
        self.lbModule.setFont(fnt)
        self.lbModule.setAlignment(Qt.AlignCenter)
        self.lbModule.setMaximumHeight(25)
        self.layout().addWidget(self.lbModule)

        self.build_tabs()

        self.create_timers()

    def create_timers(self):
        self.auto_timer = QTimer()
        self.auto_timer.setInterval(1000)
        self.auto_timer.timeout.connect(self.auto)
        self.auto_timer.start()
        self.mqtt_timer = QTimer()
        self.mqtt_timer.setSingleShot(True)
        self.mqtt_timer.timeout.connect(self.setupMqtt)
        self.mqtt_timer.start(1000)

    def build_tabs(self):
        self.tabs = QTabWidget()

        tabInformation = self.tabInformation()
        self.tabs.addTab(tabInformation, "Information")

        tabModule = self.tabModule()
        self.tabs.addTab(tabModule, "Module and Firmware")

        self.rule_grps = []
        tabRules = self.tabRules()
        self.tabs.addTab(tabRules, "Rules and Timers")
        self.pbRTSet.clicked.connect(self.saveRuleTimers)
        self.pbVMSet.clicked.connect(self.saveVarMem)

        self.rg.pbSave.clicked.connect(self.saveRule)

        self.tabs.currentChanged.connect(self.tabChanged)
        self.tabs.setEnabled(False)
        self.layout().addWidget(self.tabs)

    def auto(self):
        if self.mqtt.state == self.mqtt.Connected:
            if self.pbRTPoll.isChecked():
                self.loadRuleTimers()

            if self.pbVMPoll.isChecked():
                self.loadVarMem()

    def tabChanged(self, tab):
        if tab == 2:
            self.loadRule(self.rg.cbRule.currentIndex())
            self.loadTimer(self.cbTimer.currentIndex())
            self.loadRuleTimers()
            self.loadVarMem()

    def setupMqtt(self):
        self.mqtt.hostname = self.settings.value('hostname', 'localhost')
        self.mqtt.port = self.settings.value('port', 1883, int)

        if self.settings.value('username'):
            self.mqtt.setAuth(self.settings.value('username'),
                              self.settings.value('password'))

        self.mqtt.connected.connect(self.mqtt_subscribe)
        self.mqtt.messageSignal.connect(self.mqtt_message)
        self.mqtt.connectToHost()

    def mqtt_subscribe(self):
        self.mqtt.subscribe(self.tele_topic)
        self.mqtt.subscribe(self.stat_topic)

        self.initial_query()

        self.tabs.setEnabled(True)

    def mqtt_message(self, topic, msg):
        match = match_topic(self.full_topic, topic)
        if match:
            match = match.groupdict()
            reply = match['reply']

            try:
                msg = loads(msg)

                if reply == "RESULT":
                    first = list(msg)[0]

                    if first.startswith('Rule'):
                        self.parseRule(msg)

                    elif first == "T1":
                        self.parseRuleTimer(msg)

                    elif first.startswith('Var'):
                        self.parseVarMem(msg, 0)

                    elif first.startswith('Mem'):
                        self.parseVarMem(msg, 1)

                    elif first == "Module":
                        self.module = msg[first]
                        self.updateCBModules()

                    elif first.startswith('Modules'):
                        self.parseModules(msg)

                    elif first.startswith(
                            'GPIO') and not first.startswith('GPIOs'):
                        self.parse_available_gpio(msg)

                    elif first.startswith('GPIOs'):
                        self.parse_supported_peripherals(msg)

                    elif not first.startswith("Timers") and first.startswith(
                            "Timer"):
                        self.parseTimer(msg[first])

                    elif first == "Timers":
                        self.gbTimers.setChecked(msg[first] == "ON")

                    elif first.startswith("Timers"):
                        pass

                    else:
                        print(msg)

                elif reply in ("STATE", "STATUS11"):
                    if reply == "STATUS11":
                        msg = msg['StatusSTS']

                    self.wifi_model.item(0, 1).setText("{} ({})".format(
                        msg['Wifi'].get('SSId', "n/a"),
                        msg['Wifi'].get('RSSI', "n/a")))
                    self.power = {
                        k: msg[k]
                        for k in msg.keys() if k.startswith("POWER")
                    }
                    self.cbxTimerOut.clear()
                    self.cbxTimerOut.addItems(self.power)

                elif reply == "STATUS":
                    msg = msg['Status']
                    self.lbModule.setText(modules.get(msg.get("Module")))

                    fname = msg['FriendlyName']
                    if isinstance(fname, str):
                        fname = [fname]
                    for i, fn in enumerate(fname):
                        self.program_model.item(6 + i, 1).setText(
                            msg['FriendlyName'][i])

                    self.mqtt_model.item(4, 1).setText("{}".format(
                        msg.get('Topic', "n/a")))

                elif reply == "STATUS1":
                    msg = msg['StatusPRM']
                    self.program_model.item(3, 1).setText("{} @{}".format(
                        msg.get('SaveCount', "n/a"),
                        msg.get('SaveAddress', "n/a")))
                    self.program_model.item(4, 1).setText("{}".format(
                        msg.get('BootCount', "n/a")))
                    self.program_model.item(5, 1).setText("{}".format(
                        msg.get('RestartReason', "n/a")))
                    self.mqtt_model.item(5, 1).setText("{}".format(
                        msg.get('GroupTopic', "n/a")))

                elif reply == "STATUS2":
                    msg = msg['StatusFWR']
                    self.program_model.item(0, 1).setText("{}".format(
                        msg.get('Version', "n/a")))
                    self.program_model.item(1, 1).setText("{}".format(
                        msg.get('BuildDateTime', "n/a")))
                    self.program_model.item(2, 1).setText("{} / {}".format(
                        msg.get('Core', "n/a"), msg.get('SDK', "n/a")))

                elif reply == "STATUS3":
                    msg = msg['StatusLOG']

                elif reply == "STATUS4":
                    msg = msg['StatusMEM']
                    self.esp_model.item(0, 1).setText("n/a")
                    self.esp_model.item(1, 1).setText("{}".format(
                        msg.get('FlashChipId', "n/a")))
                    self.esp_model.item(2, 1).setText("{}".format(
                        msg.get('FlashSize', "n/a")))
                    self.esp_model.item(3, 1).setText("{}".format(
                        msg.get('ProgramFlashSize', "n/a")))
                    self.esp_model.item(4, 1).setText("n/a")
                    self.esp_model.item(5, 1).setText("{}".format(
                        msg.get('Free', "n/a")))
                    self.esp_model.item(6, 1).setText("{}".format(
                        msg.get('Heap', "n/a")))

                elif reply == "STATUS5":
                    msg = msg['StatusNET']
                    self.wifi_model.item(1, 1).setText("{}".format(
                        msg.get('Hostname', "n/a")))
                    self.wifi_model.item(2, 1).setText("{}".format(
                        msg.get('IPAddress', "n/a")))
                    self.wifi_model.item(3, 1).setText("{}".format(
                        msg.get('Gateway', "n/a")))
                    self.wifi_model.item(4, 1).setText("{}".format(
                        msg.get('Subnetmask', "n/a")))
                    self.wifi_model.item(5, 1).setText("{}".format(
                        msg.get('DNSServer', "n/a")))
                    self.wifi_model.item(6, 1).setText("{}".format(
                        msg.get('Mac', "n/a")))

                elif reply == "STATUS6":
                    msg = msg['StatusMQT']
                    self.mqtt_model.item(0, 1).setText("{}".format(
                        msg.get('MqttHost', "n/a")))
                    self.mqtt_model.item(1, 1).setText("{}".format(
                        msg.get('MqttPort', "n/a")))
                    self.mqtt_model.item(2, 1).setText("{}".format(
                        msg.get('MqttUser', "n/a")))
                    self.mqtt_model.item(3, 1).setText("{}".format(
                        msg.get('MqttClient', "n/a")))
                    self.mqtt_model.item(6, 1).setText("{}".format(
                        self.full_topic))
                    self.mqtt_model.item(7, 1).setText("cmnd/{}_fb".format(
                        msg.get('MqttClient', "n/a")))

                elif reply == "STATUS7":
                    msg = msg['StatusTIM']
                    self._sunrise = msg.get('Sunrise', "")
                    self._sunset = msg.get('Sunset', "")
                    self.TimerMode.button(1).setText(
                        self.TimerMode.button(1).text().format(
                            msg.get('Sunrise', "")))
                    self.TimerMode.button(2).setText(
                        self.TimerMode.button(2).text().format(
                            msg.get('Sunset', "")))

            except JSONDecodeError:
                pass

    def initial_query(self):
        self.mqtt.publish(self.cmnd_topic + "status", 0)
        self.mqtt.publish(self.cmnd_topic + "timers")
        self.mqtt.publish(self.cmnd_topic + "modules")
        self.mqtt.publish(self.cmnd_topic + "module")
        self.mqtt.publish(self.cmnd_topic + "gpios")
        self.mqtt.publish(self.cmnd_topic + "gpio")

    def parseModules(self, msg):
        k = list(msg)[0]
        v = msg[k]

        if k == "Modules1":
            self.modules = v

        else:
            self.modules += v

    def updateCBModules(self):
        self.cbModule.clear()
        self.cbModule.addItems(self.modules)
        self.cbModule.setCurrentText(self.module)

    def saveModule(self):
        module = self.cbModule.currentText().split(" ")[0]
        self.mqtt.publish(self.cmnd_topic + "module", payload=module)
        self.parent().close()

    def parse_available_gpio(self, msg):
        self.gpios = list(msg)
        for i, g in enumerate(self.gpios):
            cb = QComboBox()
            cb.addItems(self.supported_gpios)
            cb.setCurrentText(msg[g])
            self.gpio_cb.append(cb)
            self.gbGPIO.layout().insertRow(0 + i, g, cb)

    def parse_supported_peripherals(self, msg):
        k = list(msg)[0]
        v = msg[k]

        if k == "GPIOs1":
            self.supported_gpios = v

        else:
            self.supported_gpios += v

    def saveGPIOs(self):
        payload = ""
        for i, g in enumerate(list(self.gpios)):
            gpio = self.gpio_cb[i].currentText().split(" ")[0]
            payload += "{} {}; ".format(g, gpio)
        self.mqtt.publish(self.cmnd_topic + "backlog", payload=payload)
        self.parent().close()

    def loadRule(self, idx):
        self.mqtt.publish(self.cmnd_topic + "Rule{}".format(idx + 1))

    def parseRule(self, msg):
        rule, once, stop, _, rules = list(msg)
        self.rg.cbEnabled.setChecked(msg[rule] == "ON")
        self.rg.cbOnce.setChecked(msg[once] == "ON")
        self.rg.cbStopOnError.setChecked(msg[stop] == "ON")
        self.rg.text.setPlainText(msg['Rules'].replace(
            " on ", "\non ").replace(" do ",
                                     " do\n\t").replace(" endon", "\nendon "))

    def saveRule(self):
        text = self.rg.text.toPlainText().replace("\n", " ").replace(
            "\t", " ").replace("  ", " ")
        backlog = {
            'rule_nr': "Rule{}".format(self.rg.cbRule.currentIndex() + 1),
            'text': text if len(text) > 0 else '""',
            'enabled': "1" if self.rg.cbEnabled.isChecked() else "0",
            'once': "5" if self.rg.cbOnce.isChecked() else "4",
            'stop': "9" if self.rg.cbStopOnError.isChecked() else "8"
        }
        self.mqtt.publish(
            self.cmnd_topic + "backlog",
            payload=
            '{rule_nr} {text}; {rule_nr} {once}; {rule_nr} {stop}; {rule_nr} {enabled}; '
            .format(**backlog))

    def loadRuleTimers(self):
        self.mqtt.publish(self.cmnd_topic + "ruletimer")

    def parseRuleTimer(self, msg):
        for c in range(8):
            itm = self.twRT.cellWidget(0, c)
            itm.setValue(int(msg["T{}".format(c + 1)]))

    def saveRuleTimers(self):
        for t in range(8):
            self.mqtt.publish(self.cmnd_topic + "ruletimer{}".format(t + 1),
                              payload=self.twRT.cellWidget(0, t).value())

    def loadVarMem(self):
        for x in range(5):
            self.mqtt.publish(self.cmnd_topic + "var{}".format(x + 1))
            self.mqtt.publish(self.cmnd_topic + "mem{}".format(x + 1))

    def parseVarMem(self, msg, row):
        k = list(msg)[0]
        nr = k[-1]
        v = msg[k]
        itm = self.twVM.cellWidget(row, int(nr) - 1)
        itm.setText(v)

    def saveVarMem(self):
        for r, cmd in enumerate(['Var', 'Mem']):
            for c in range(5):
                self.mqtt.publish(self.cmnd_topic + "{}{}".format(cmd, c + 1),
                                  payload="{}".format(
                                      self.twVM.cellWidget(r, c).text()))

    def toggleTimers(self, state):
        self.mqtt.publish(self.cmnd_topic + "timers",
                          payload="ON" if state else "OFF")

    def loadTimer(self, idx):
        self.mqtt.publish(self.cmnd_topic + "Timers")
        self.mqtt.publish(self.cmnd_topic + "Timer{}".format(idx + 1))

    def parseTimer(self, payload):
        self.blockSignals(True)
        self.cbTimerArm.setChecked(payload['Arm'])
        self.cbTimerRpt.setChecked(payload['Repeat'])
        self.cbxTimerAction.setCurrentIndex(payload['Action'])

        output = payload.get('Output')
        if output:
            self.cbxTimerOut.setEnabled(True)
            self.cbxTimerOut.setCurrentIndex(output - 1)
        else:
            self.cbxTimerOut.setEnabled(False)

        mode = payload.get('Mode')
        if not mode:
            mode = 0
            self.TimerMode.button(1).setEnabled(False)
            self.TimerMode.button(2).setEnabled(False)
        self.TimerMode.button(mode).setChecked(True)

        h, m = map(int, payload["Time"].split(":"))
        if h < 0:
            self.cbxTimerPM.setCurrentText("-")
            h *= -1
        self.teTimerTime.setTime(QTime(h, m))
        self.cbxTimerWnd.setCurrentText(str(payload['Window']).zfill(2))
        for wd, v in enumerate(payload['Days']):
            self.TimerWeekday.button(wd).setChecked(int(v))

        self.describeTimer()
        self.blockSignals(False)

    def saveTimer(self):
        payload = {
            "Arm":
            int(self.cbTimerArm.isChecked()),
            "Mode":
            self.TimerMode.checkedId(),
            "Time":
            self.teTimerTime.time().toString("hh:mm"),
            "Window":
            self.cbxTimerWnd.currentIndex(),
            "Days":
            "".join([
                str(int(cb.isChecked())) for cb in self.TimerWeekday.buttons()
            ]),
            "Repeat":
            int(self.cbTimerRpt.isChecked()),
            "Output":
            self.cbxTimerOut.currentIndex(),
            "Action":
            self.cbxTimerAction.currentIndex()
        }
        self.mqtt.publish(self.cmnd_topic +
                          "timer{}".format(self.cbTimer.currentIndex() + 1),
                          payload=dumps(payload))

    def copyTrigger(self):
        mode = self.cbxTimerAction.currentText()
        if mode == "Rule":
            trigger = "clock#Timer={}".format(self.cbTimer.currentIndex() + 1)
        else:
            trigger = "{}#state={}".format(self.cbxTimerOut.currentText(),
                                           self.cbxTimerAction.currentIndex())
        QApplication.clipboard().setText("on {} do\n\t\nendon".format(trigger))

    def describeTimer(self):
        if self.cbTimerArm.isChecked():
            desc = {'days': '', 'repeat': ''}
            desc['timer'] = self.cbTimer.currentText().upper()
            repeat = self.cbTimerRpt.isChecked()
            out = self.cbxTimerOut.currentText()
            act = self.cbxTimerAction.currentText()
            mode = self.TimerMode.checkedId()
            pm = self.cbxTimerPM.currentText()
            time = self.teTimerTime.time()
            wnd = int(self.cbxTimerWnd.currentText()) * 60

            if mode == 0:
                if wnd == 0:
                    desc['time'] = "at {}".format(time.toString("hh:mm"))
                else:
                    desc['time'] = "somewhere between {} and {}".format(
                        time.addSecs(wnd * -1).toString("hh:mm"),
                        time.addSecs(wnd).toString("hh:mm"))
            else:
                prefix = "before" if pm == "-" else "after"
                mode_desc = "sunrise" if mode == 1 else "sunset"
                window = "somewhere in a {} minute window centered around ".format(
                    wnd // 30)
                desc['time'] = "{}h{}m {} {}".format(time.hour(),
                                                     time.minute(), prefix,
                                                     mode_desc)

                if wnd > 0:
                    desc['time'] = window + desc['time']

            if repeat:
                day_names = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]
                days = [cb.isChecked() for cb in self.TimerWeekday.buttons()]
                if days.count(True) == 7:
                    desc['days'] = "everyday"
                else:
                    days_list = [day_names[d] for d in range(7) if days[d]]
                    desc['days'] = "on every {}".format(", ".join(days_list))
            else:
                desc['repeat'] = "only ONCE"

            if act == "Rule":
                desc['action'] = "trigger clock#Timer={}".format(
                    self.cbTimer.currentIndex() + 1)
                text = "{timer} will {action} {time} {days} {repeat}".format(
                    **desc)

            elif self.cbxTimerOut.count() > 0:

                if act == "Toggle":
                    desc['action'] = "TOGGLE {}".format(out.upper())
                else:
                    desc['action'] = "set {} to {}".format(
                        out.upper(), act.upper())

                text = "{timer} will {action} {time} {days} {repeat}".format(
                    **desc)
            else:
                text = "{timer} will do nothing because there are no relays configured.".format(
                    **desc)

            self.lbTimerDesc.setText(text)

        else:
            self.lbTimerDesc.setText(
                "{} is not armed, it will do nothing.".format(
                    self.cbTimer.currentText().upper()))

    def tabInformation(self):
        info = QWidget()
        vl = VLayout()

        self.program_model = QStandardItemModel()
        for d in [
                "Program version", "Build date & time", "Core/SDK version",
                "Flash write count", "Boot count", "Restart reason",
                "Friendly Name 1", "Friendly Name 2", "Friendly Name 3",
                "Friendly Name 4"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.program_model.appendRow([k, v])

        gbPrgm = GroupBoxH("Program")
        gbPrgm.setFlat(True)
        tvPrgm = QTreeView()
        tvPrgm.setHeaderHidden(True)
        tvPrgm.setRootIsDecorated(False)
        tvPrgm.setModel(self.program_model)
        tvPrgm.resizeColumnToContents(0)
        gbPrgm.addWidget(tvPrgm)

        self.esp_model = QStandardItemModel()
        for d in [
                "ESP Chip Id", "Flash Chip Id", "Flash Size",
                "Program Flash Size", "Program Size", "Free Program Space",
                "Free Memory"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.esp_model.appendRow([k, v])

        gbESP = GroupBoxH("ESP")
        gbESP.setFlat(True)
        tvESP = QTreeView()
        tvESP.setHeaderHidden(True)
        tvESP.setRootIsDecorated(False)
        tvESP.setModel(self.esp_model)
        tvESP.resizeColumnToContents(0)
        gbESP.addWidget(tvESP)

        # self.emul_model = QStandardItemModel()
        # for d in ["Emulation", "mDNS Discovery"]:
        #     k = QStandardItem(d)
        #     k.setEditable(False)
        #     v = QStandardItem()
        #     v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
        #     v.setEditable(False)
        #     self.emul_model.appendRow([k, v])
        #
        # gbEmul = GroupBoxH("Emulation")
        # gbEmul.setFlat(True)
        # tvEmul = QTreeView()
        # tvEmul.setHeaderHidden(True)
        # tvEmul.setRootIsDecorated(False)
        # tvEmul.setModel(self.emul_model)
        # tvEmul.resizeColumnToContents(0)
        # gbEmul.addWidget(tvEmul)

        self.wifi_model = QStandardItemModel()
        for d in [
                "AP1 SSId (RSSI)", "Hostname", "IP Address", "Gateway",
                "Subnet Mask", "DNS Server", "MAC Address"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.wifi_model.appendRow([k, v])

        gbWifi = GroupBoxH("Wifi")
        gbWifi.setFlat(True)
        tvWifi = QTreeView()
        tvWifi.setHeaderHidden(True)
        tvWifi.setRootIsDecorated(False)
        tvWifi.setModel(self.wifi_model)
        tvWifi.resizeColumnToContents(0)
        gbWifi.addWidget(tvWifi)

        self.mqtt_model = QStandardItemModel()
        for d in [
                "MQTT Host", "MQTT Port", "MQTT User", "MQTT Client",
                "MQTT Topic", "MQTT Group Topic", "MQTT Full Topic",
                "MQTT Fallback Topic"
        ]:
            k = QStandardItem(d)
            k.setEditable(False)
            v = QStandardItem()
            v.setTextAlignment(Qt.AlignVCenter | Qt.AlignRight)
            v.setEditable(False)
            self.mqtt_model.appendRow([k, v])

        gbMQTT = GroupBoxH("MQTT")
        gbMQTT.setFlat(True)
        tvMQTT = QTreeView()
        tvMQTT.setHeaderHidden(True)
        tvMQTT.setRootIsDecorated(False)
        tvMQTT.setModel(self.mqtt_model)
        tvMQTT.resizeColumnToContents(0)
        gbMQTT.addWidget(tvMQTT)

        hl = HLayout(0)
        vl_lc = VLayout(0, 3)
        vl_rc = VLayout(0, 3)

        vl_lc.addWidgets([gbPrgm, gbESP])
        vl_rc.addWidgets([gbWifi, gbMQTT])

        vl_rc.setStretch(0, 2)
        vl_rc.setStretch(1, 2)
        vl_rc.setStretch(2, 1)

        hl.addLayout(vl_lc)
        hl.addLayout(vl_rc)
        vl.addLayout(hl)
        info.setLayout(vl)
        return info

    def tabModule(self):
        module = QWidget()
        module.setLayout(HLayout())

        self.gbModule = GroupBoxH("Module")
        self.cbModule = QComboBox()
        self.pbModuleSet = QPushButton("Save and close (device will restart)")
        self.gbModule.addWidgets([self.cbModule, self.pbModuleSet])
        self.pbModuleSet.clicked.connect(self.saveModule)

        self.gbGPIO = QGroupBox("GPIO")
        fl_gpio = QFormLayout()
        pbGPIOSet = QPushButton("Save and close (device will restart)")
        fl_gpio.addWidget(pbGPIOSet)
        pbGPIOSet.clicked.connect(self.saveGPIOs)

        self.gbGPIO.setLayout(fl_gpio)

        mg_vl = VLayout([0, 0, 3, 0])
        mg_vl.addWidgets([self.gbModule, self.gbGPIO])
        mg_vl.setStretch(0, 1)
        mg_vl.setStretch(1, 3)

        self.gbFirmware = GroupBoxV("Firmware", margin=[3, 0, 0, 0])
        lb = QLabel("Feature under development.")
        lb.setAlignment(Qt.AlignCenter)
        lb.setEnabled(False)
        self.gbFirmware.addWidget(lb)

        module.layout().addLayout(mg_vl)
        module.layout().addWidget(self.gbFirmware)
        return module

    def tabRules(self):
        rules = QWidget()
        rules.setLayout(VLayout())
        hl = HLayout(0)
        vl_l = VLayout(0)
        self.rg = RuleGroupBox(rules, "Rule editor")
        self.rg.setFlat(True)
        self.rg.cbRule.currentIndexChanged.connect(self.loadRule)
        vl_l.addWidget(self.rg)

        gRT = GroupBoxH("Rule timers")
        vl_RT_func = VLayout(margin=[0, 0, 3, 0])
        self.pbRTPoll = QPushButton("Poll")
        self.pbRTPoll.setCheckable(True)
        self.pbRTSet = QPushButton("Set")

        vl_RT_func.addWidgets([self.pbRTPoll, self.pbRTSet])
        vl_RT_func.addStretch(1)
        gRT.layout().addLayout(vl_RT_func)

        self.twRT = QTableWidget(1, 8)
        self.twRT.setHorizontalHeaderLabels(
            ["T{}".format(i) for i in range(1, 9)])
        for c in range(8):
            self.twRT.horizontalHeader().setSectionResizeMode(
                c, QHeaderView.Stretch)
            self.twRT.setCellWidget(0, c, SpinBox(minimum=0, maximum=32766))
        self.twRT.verticalHeader().hide()

        self.twRT.verticalHeader().setDefaultSectionSize(
            self.twRT.horizontalHeader().height() * 2 + 1)
        self.twRT.setMaximumHeight(self.twRT.horizontalHeader().height() +
                                   self.twRT.rowHeight(0))
        gRT.layout().addWidget(self.twRT)

        gVM = GroupBoxH("VAR/MEM")
        vl_VM_func = VLayout(margin=[3, 0, 0, 0])
        self.pbVMPoll = QPushButton("Poll")
        self.pbVMPoll.setCheckable(True)
        self.pbVMSet = QPushButton("Set")

        vl_VM_func.addWidgets([self.pbVMPoll, self.pbVMSet])
        vl_VM_func.addStretch(1)
        gVM.layout().addLayout(vl_VM_func)

        self.twVM = QTableWidget(2, 5)
        self.twVM.setHorizontalHeaderLabels(
            ["{}".format(i) for i in range(1, 9)])
        self.twVM.setVerticalHeaderLabels(["VAR", "MEM"])
        self.twVM.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        for c in range(5):
            self.twVM.horizontalHeader().setSectionResizeMode(
                c, QHeaderView.Stretch)

        for r in range(2):
            for c in range(5):
                self.twVM.setCellWidget(r, c, QLineEdit())

        self.twVM.verticalHeader().setDefaultSectionSize(
            self.twVM.horizontalHeader().height())
        self.twVM.setMaximumHeight(self.twVM.horizontalHeader().height() +
                                   self.twVM.rowHeight(0) * 2)
        gVM.layout().addWidget(self.twVM)

        hl_rt_vm = HLayout(0)
        hl_rt_vm.addWidgets([gRT, gVM])

        hl.addLayout(vl_l)

        vl_r = VLayout(0)
        self.gbTimers = GroupBoxV("Timers", spacing=5)
        self.gbTimers.setCheckable(True)
        self.gbTimers.setChecked(False)
        self.gbTimers.toggled.connect(self.toggleTimers)

        self.cbTimer = QComboBox()
        self.cbTimer.addItems(["Timer{}".format(nr + 1) for nr in range(16)])
        self.cbTimer.currentIndexChanged.connect(self.loadTimer)

        hl_tmr_arm_rpt = HLayout(0)
        self.cbTimerArm = QCheckBox("Arm")
        self.cbTimerArm.clicked.connect(lambda x: self.describeTimer())
        self.cbTimerRpt = QCheckBox("Repeat")
        self.cbTimerRpt.clicked.connect(lambda x: self.describeTimer())
        hl_tmr_arm_rpt.addWidgets([self.cbTimerArm, self.cbTimerRpt])

        hl_tmr_out_act = HLayout(0)
        self.cbxTimerOut = QComboBox()
        self.cbxTimerOut.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        self.cbxTimerAction = QComboBox()
        self.cbxTimerAction.addItems(["Off", "On", "Toggle", "Rule"])
        self.cbxTimerAction.currentIndexChanged.connect(
            lambda x: self.describeTimer())
        hl_tmr_out_act.addWidgets([self.cbxTimerOut, self.cbxTimerAction])

        self.TimerMode = QButtonGroup()
        rbTime = QRadioButton("Time")
        rbSunrise = QRadioButton("Sunrise ({})")
        rbSunset = QRadioButton("Sunset ({})")
        self.TimerMode.addButton(rbTime, 0)
        self.TimerMode.addButton(rbSunrise, 1)
        self.TimerMode.addButton(rbSunset, 2)
        self.TimerMode.buttonClicked.connect(lambda x: self.describeTimer())
        gbTimerMode = GroupBoxH("Mode")
        gbTimerMode.addWidgets(self.TimerMode.buttons())

        hl_tmr_time = HLayout(0)
        self.cbxTimerPM = QComboBox()
        self.cbxTimerPM.addItems(["+", "-"])
        self.cbxTimerPM.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        self.TimerMode.buttonClicked[int].connect(
            lambda x: self.cbxTimerPM.setEnabled(x != 0))
        self.teTimerTime = QTimeEdit()
        self.teTimerTime.setButtonSymbols(QTimeEdit.NoButtons)
        self.teTimerTime.setAlignment(Qt.AlignCenter)
        self.teTimerTime.timeChanged.connect(lambda x: self.describeTimer())

        lbWnd = QLabel("Window:")
        lbWnd.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
        self.cbxTimerWnd = QComboBox()
        self.cbxTimerWnd.addItems([str(x).zfill(2) for x in range(0, 16)])
        self.cbxTimerWnd.currentIndexChanged.connect(
            lambda x: self.describeTimer())

        hl_tmr_days = HLayout(0)
        self.TimerWeekday = QButtonGroup()
        self.TimerWeekday.setExclusive(False)
        for i, wd in enumerate(
            ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]):
            cb = QCheckBox(wd)
            cb.clicked.connect(lambda x: self.describeTimer())
            hl_tmr_days.addWidget(cb)
            self.TimerWeekday.addButton(cb, i)

        gbTimerDesc = GroupBoxV("Timer description", 5)
        gbTimerDesc.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
        self.lbTimerDesc = QLabel()
        self.lbTimerDesc.setAlignment(Qt.AlignCenter)
        self.lbTimerDesc.setWordWrap(True)
        gbTimerDesc.layout().addWidget(self.lbTimerDesc)
        hl_tmr_btns = HLayout(0)
        btnCopyTrigger = QPushButton("Copy trigger")
        btnTimerSave = QPushButton("Save")
        hl_tmr_btns.addWidgets([btnCopyTrigger, btnTimerSave])
        hl_tmr_btns.insertStretch(1)

        btnTimerSave.clicked.connect(self.saveTimer)
        btnCopyTrigger.clicked.connect(self.copyTrigger)

        hl_tmr_time.addWidgets(
            [self.cbxTimerPM, self.teTimerTime, lbWnd, self.cbxTimerWnd])

        self.gbTimers.layout().addWidget(self.cbTimer)
        self.gbTimers.layout().addLayout(hl_tmr_arm_rpt)
        self.gbTimers.layout().addLayout(hl_tmr_out_act)
        self.gbTimers.layout().addWidget(gbTimerMode)
        self.gbTimers.layout().addLayout(hl_tmr_time)
        self.gbTimers.layout().addLayout(hl_tmr_days)
        self.gbTimers.layout().addWidget(gbTimerDesc)
        self.gbTimers.layout().addLayout(hl_tmr_btns)

        vl_r.addWidget(self.gbTimers)

        hl.addLayout(vl_r)
        hl.setStretch(0, 2)
        hl.setStretch(1, 1)

        rules.layout().addLayout(hl)
        rules.layout().addLayout(hl_rt_vm)
        rules.layout().setStretch(0, 3)
        rules.layout().setStretch(1, 0)

        return rules

    def closeEvent(self, event):
        self.mqtt.disconnectFromHost()
        event.accept()
Example #12
0
class DevicesConfigWidget(QWidget):
    def __init__(self, parent, topic, *args, **kwargs):
        super(DevicesConfigWidget, self).__init__(*args, **kwargs)

        self.settings = QSettings()
        self.topic = topic
        self.full_topic = self.settings.value(
            'Devices/{}/full_topic'.format(topic))
        self.friendly_name = self.settings.value(
            'Devices/{}/friendly_name'.format(topic))

        self.cmnd_topic = self.full_topic.replace("%topic%",
                                                  self.topic).replace(
                                                      "%prefix%", 'cmnd')
        self.tele_topic = self.full_topic.replace(
            "%topic%", self.topic).replace("%prefix%", 'tele') + "+"
        self.stat_topic = self.full_topic.replace(
            "%topic%", self.topic).replace("%prefix%", 'stat') + "+"

        self.setWindowTitle(self.friendly_name)

        self.mqtt = MqttClient()

        self.module = None
        self.modules = []

        self.gpios = []
        self.supported_gpios = []
        self.current_gpios = {}

        self.setLayout(VLayout(margin=[0, 6, 0, 0], spacing=3))
        self.build_detail_row()
        self.build_tabs()

        self.create_timers()

    def create_timers(self):
        self.auto_timer = QTimer()
        self.auto_timer.setInterval(1000)
        self.auto_timer.timeout.connect(self.auto)
        self.auto_timer.start()
        self.mqtt_timer = QTimer()
        self.mqtt_timer.setSingleShot(True)
        self.mqtt_timer.timeout.connect(self.setupMqtt)
        self.mqtt_timer.start(1000)

    def build_tabs(self):
        self.tabs = QTabWidget()

        self.rule_grps = []
        tabModule = self.tabModule()
        self.tabs.addTab(tabModule, "Module | Firmware")
        tabRules = self.tabRules()
        self.tabs.addTab(tabRules, "Rules")
        self.pbRTSet.clicked.connect(self.saveRuleTimers)
        self.pbVMSet.clicked.connect(self.saveVarMem)
        for r in range(3):
            self.rule_grps[r].pbSave.clicked.connect(
                lambda x, r=r: self.saveRule(r))
        self.tabs.currentChanged.connect(self.tabChanged)
        self.tabs.setEnabled(False)
        self.layout().addWidget(self.tabs)

    def build_detail_row(self):
        frDetails = QFrame()
        frDetails.setFrameStyle(QFrame.StyledPanel | QFrame.Plain)
        hl_details = HLayout()
        leTopic = DetailLE(self.topic)
        hl_details.addWidgets([QLabel("Topic"), leTopic])
        leFTopic = DetailLE(self.full_topic)
        hl_details.addWidgets([QLabel("FullTopic"), leFTopic])
        # self.leMAC = DetailLE("")
        # hl_details.addWidgets([QLabel("MAC"), self.leMAC])
        # self.leIP = DetailLE("")
        # hl_details.addWidgets([QLabel("IP"), self.leIP])
        self.leGTopic = DetailLE("")
        hl_details.addWidgets([QLabel("GroupTopic"), self.leGTopic])
        frDetails.setLayout(hl_details)
        self.layout().addWidget(frDetails)

    def auto(self):
        if self.mqtt.state == self.mqtt.Connected:
            if self.pbRTPoll.isChecked():
                self.loadRuleTimers()

            if self.pbVMPoll.isChecked():
                self.loadVarMem()

    def tabChanged(self, tab):
        if tab == 1:
            self.loadRule()
            self.loadRuleTimers()
            self.loadVarMem()

    def torture(self):
        # self.mqtt.publish(self.cmnd_topic + "status", payload="0")
        print('x')

    def setupMqtt(self):
        self.mqtt.hostname = self.settings.value('hostname', 'localhost')
        self.mqtt.port = self.settings.value('port', 1883, int)

        if self.settings.value('username'):
            self.mqtt.setAuth(self.settings.value('username'),
                              self.settings.value('password'))

        self.mqtt.connectToHost()
        self.mqtt.connected.connect(self.mqtt_subscribe)
        self.mqtt.messageSignal.connect(self.mqtt_message)

    def mqtt_subscribe(self):
        self.mqtt.subscribe(self.tele_topic)
        self.mqtt.subscribe(self.stat_topic)

        self.initial_query()

        self.tabs.setEnabled(True)

    def mqtt_message(self, topic, msg):
        match = match_topic(self.full_topic, topic)
        if match:
            match = match.groupdict()
            reply = match['reply']

            if reply == "RESULT":
                msg = loads(msg)
                first = list(msg)[0]

                if first.startswith('Rule'):
                    self.parseRule(msg)

                elif first == "T1":
                    self.parseRuleTimer(msg)

                elif first.startswith('Var'):
                    self.parseVarMem(msg, 0)

                elif first.startswith('Mem'):
                    self.parseVarMem(msg, 1)

                elif first == "Module":
                    self.module = msg[first]

                elif first.startswith('Modules'):
                    self.parseModules(msg)

                elif first.startswith(
                        'GPIO') and not first.startswith('GPIOs'):
                    self.parseGPIO(msg)

                elif first.startswith('GPIOs'):
                    self.parseGPIOs(msg)

                else:
                    print(msg)

            elif reply == "STATUS1":
                msg = loads(msg)['StatusPRM']
                self.leGTopic.setText(msg.get("GroupTopic"))

    def initial_query(self):
        self.mqtt.publish(self.cmnd_topic + "module")
        self.mqtt.publish(self.cmnd_topic + "modules")
        self.mqtt.publish(self.cmnd_topic + "gpio")
        self.mqtt.publish(self.cmnd_topic + "gpios")
        self.mqtt.publish(self.cmnd_topic + "status", 1)

    def parseModules(self, msg):
        k = list(msg)[0]
        nr = k[-1]
        v = msg[k]

        if k == "Modules1":
            self.modules = v

        elif k == "Modules2":
            self.modules += v

        elif k == "Modules3":
            self.modules += v
            self.updateCBModules()

    def updateCBModules(self):
        self.cbModule.addItems(self.modules)
        self.cbModule.setCurrentText(self.module)

    def updateCBGpios(self):
        for i, cb in enumerate(self.gpios):
            cb.addItems(self.supported_gpios)
            cb.setCurrentText(self.current_gpios[list(self.current_gpios)[i]])

    def saveModule(self):
        module = self.cbModule.currentText().split(" ")[0]
        self.mqtt.publish(self.cmnd_topic + "module", payload=module)
        self.parent().close()

    def parseGPIO(self, msg):
        for g in list(msg):
            self.current_gpios[g] = (msg[g])
            cb = QComboBox()
            self.gpios.append(cb)
            self.gbGPIO.layout().addRow(QLabel(g), cb)

        pbGPIOSet = QPushButton("Save and close (device will restart)")
        pbGPIOSet.clicked.connect(self.saveGPIOs)
        self.gbGPIO.layout().addWidget(pbGPIOSet)

    def parseGPIOs(self, msg):
        k = list(msg)[0]
        nr = k[-1]
        v = msg[k]

        if k == "GPIOs1":
            self.supported_gpios = v

        elif k == "GPIOs2":
            self.supported_gpios += v

        elif k == "GPIOs3":
            self.supported_gpios += v
            self.updateCBGpios()

    def saveGPIOs(self):
        payload = ""
        for i, g in enumerate(list(self.current_gpios)):
            gpio = self.gpios[i].currentText().split(" ")[0]
            payload += "{} {}; ".format(g, gpio)

        self.mqtt.publish(self.cmnd_topic + "backlog", payload=payload)
        self.parent().close()

    def loadRule(self, rule=None):
        if rule:
            self.mqtt.publish(self.cmnd_topic + "Rule{}".format(rule))
        else:
            for r in range(1, 4):
                self.mqtt.publish(self.cmnd_topic + "Rule{}".format(r))

    def parseRule(self, msg):
        rule, once, stop, _, rules = list(msg)
        rg = self.rule_grps[int(rule[-1]) - 1]
        rg.cbEnabled.setChecked(msg[rule] == "ON")
        rg.text.setPlainText(msg['Rules'].replace(" on ", "\non ").replace(
            " do ", " do\n\t").replace(" endon", "\nendon "))

    def saveRule(self, rule):
        rg = self.rule_grps[rule]
        text = rg.text.toPlainText().replace("\n", " ").replace("\t",
                                                                " ").replace(
                                                                    "  ", " ")
        self.mqtt.publish(self.cmnd_topic + "Rule{}".format(rule + 1),
                          payload=text)
        backlog = {
            'rule_nr': "Rule{}".format(rule + 1),
            'enabled': "1" if rg.cbEnabled.isChecked() else "0",
            'once': "5" if rg.cbOnce.isChecked() else "4",
            'stop': "9" if rg.cbStopOnError.isChecked() else "8"
        }

        self.mqtt.publish(
            self.cmnd_topic + "backlog",
            payload="{rule_nr} {once}; {rule_nr} {stop}; {rule_nr} {enabled}; "
            .format(**backlog))

    def loadRuleTimers(self):
        self.mqtt.publish(self.cmnd_topic + "ruletimer")

    def parseRuleTimer(self, msg):
        for c in range(8):
            itm = self.twRT.cellWidget(0, c)
            itm.setValue(int(msg["T{}".format(c + 1)]))

    def saveRuleTimers(self):
        for t in range(8):
            self.mqtt.publish(self.cmnd_topic + "ruletimer{}".format(t + 1),
                              payload=self.twRT.cellWidget(0, t).value())

    def loadVarMem(self):
        for x in range(5):
            self.mqtt.publish(self.cmnd_topic + "var{}".format(x + 1))
            self.mqtt.publish(self.cmnd_topic + "mem{}".format(x + 1))

    def parseVarMem(self, msg, row):
        k = list(msg)[0]
        nr = k[-1]
        v = msg[k]
        itm = self.twVM.cellWidget(row, int(nr) - 1)
        itm.setText(v)

    def saveVarMem(self):
        for r, cmd in enumerate(['Var', 'Mem']):
            for c in range(5):
                self.mqtt.publish(self.cmnd_topic + "{}{}".format(cmd, c + 1),
                                  payload=self.twVM.cellWidget(r, c).text())

    def tabModule(self):
        module = QWidget()
        module.setLayout(HLayout())

        self.gbModule = QGroupBox("Module")
        fl_module = QFormLayout()

        self.cbModule = QComboBox()
        fl_module.addRow("Module type", self.cbModule)

        self.pbModuleSet = QPushButton("Save and close (device will restart)")
        self.pbModuleSet.clicked.connect(self.saveModule)
        fl_module.addWidget(self.pbModuleSet)

        self.gbModule.setLayout(fl_module)

        self.gbGPIO = QGroupBox("GPIO")
        fl_gpio = QFormLayout()

        self.gbGPIO.setLayout(fl_gpio)

        mg_vl = VLayout([0, 0, 3, 0])
        mg_vl.addWidgets([self.gbModule, self.gbGPIO])
        mg_vl.setStretch(0, 1)
        mg_vl.setStretch(1, 3)

        self.gbFirmware = GroupBoxV("Firmware", margin=[3, 0, 0, 0])
        lb = QLabel("Feature under development.")
        lb.setAlignment(Qt.AlignCenter)
        lb.setEnabled(False)
        self.gbFirmware.addWidget(lb)

        module.layout().addLayout(mg_vl)
        module.layout().addWidget(self.gbFirmware)
        return module

    def tabRules(self):
        rules = QWidget()
        rules.setLayout(VLayout())

        for r in range(3):
            rg = RuleGroupBox(rules, "Rule buffer {}".format(r + 1))
            rg.pbLoad.clicked.connect(lambda x, r=r + 1: self.loadRule(r))
            self.rule_grps.append(rg)
            rules.layout().addWidget(rg)
            rules.layout().setStretch(r, 1)

        gRT = GroupBoxH("Rule timers")
        vl_RT_func = VLayout(margin=[0, 0, 3, 0])
        self.pbRTPoll = QPushButton("Poll")
        self.pbRTPoll.setCheckable(True)
        self.pbRTSet = QPushButton("Set")

        vl_RT_func.addWidgets([self.pbRTPoll, self.pbRTSet])
        vl_RT_func.addStretch(1)
        gRT.layout().addLayout(vl_RT_func)

        self.twRT = QTableWidget(1, 8)
        self.twRT.setHorizontalHeaderLabels(
            ["T{}".format(i) for i in range(1, 9)])
        for c in range(8):
            self.twRT.horizontalHeader().setSectionResizeMode(
                c, QHeaderView.Stretch)
            self.twRT.setCellWidget(0, c, SpinBox(minimum=0, maximum=32766))
        self.twRT.verticalHeader().hide()

        self.twRT.verticalHeader().setDefaultSectionSize(
            self.twRT.horizontalHeader().height() * 2 + 1)
        self.twRT.setMaximumHeight(self.twRT.horizontalHeader().height() +
                                   self.twRT.rowHeight(0))
        gRT.layout().addWidget(self.twRT)

        gVM = GroupBoxH("VAR/MEM")
        vl_VM_func = VLayout(margin=[3, 0, 0, 0])
        self.pbVMPoll = QPushButton("Poll")
        self.pbVMPoll.setCheckable(True)
        self.pbVMSet = QPushButton("Set")

        vl_VM_func.addWidgets([self.pbVMPoll, self.pbVMSet])
        vl_VM_func.addStretch(1)
        gVM.layout().addLayout(vl_VM_func)

        self.twVM = QTableWidget(2, 5)
        self.twVM.setHorizontalHeaderLabels(
            ["{}".format(i) for i in range(1, 9)])
        self.twVM.setVerticalHeaderLabels(["VAR", "MEM"])
        self.twVM.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        for c in range(5):
            self.twVM.horizontalHeader().setSectionResizeMode(
                c, QHeaderView.Stretch)

        for r in range(2):
            for c in range(5):
                self.twVM.setCellWidget(r, c, QLineEdit())

        self.twVM.verticalHeader().setDefaultSectionSize(
            self.twVM.horizontalHeader().height())
        self.twVM.setMaximumHeight(self.twVM.horizontalHeader().height() +
                                   self.twVM.rowHeight(0) * 2)
        gVM.layout().addWidget(self.twVM)

        hl_rt_vm = HLayout()
        hl_rt_vm.addWidgets([gRT, gVM])
        rules.layout().addLayout(hl_rt_vm)
        rules.layout().setStretch(3, 0)

        return rules

    def closeEvent(self, event):
        self.mqtt.disconnectFromHost()
        event.accept()