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))
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 = [''] * 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)