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 __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)
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))
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)
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 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 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
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()
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