class BaseDev(QWidget): base_address = 0 has_target = False has_status = False offset = 1 def __init__(self, model, name, index, addr, has_status=False, target=None, value_offset=1): QWidget.__init__(self) self.index = index self.name = name self.model = model self.offset = value_offset self.has_status = has_status self.has_target = target is not None self.base_address = addr self._namelabel = QLabel(name) self._namelabel.setMinimumWidth(120) self._namelabel.setMaximumWidth(120) # self._groupbox = QGroupBox(name) self._groupbox = QFrame() # self._groupbox.setFlat(False) # self._groupbox.setCheckable(False) self._hlayout = QHBoxLayout() self._hlayout.addWidget(self._namelabel) self._hlayout.addWidget(self._groupbox) self._hlayout.setSpacing(0) # inside of the groupbox there is a vbox with 1 or 2 hboxes self._inner_vbox = QVBoxLayout() self._groupbox.setLayout(self._inner_vbox) # upper inner hbox self._inner_hbox1 = QHBoxLayout() self._inner_vbox.addLayout(self._inner_hbox1) # fill upper hbox self.valueWidget = QLineEdit('0b123456789abcdef0') self.valueWidget.setMaximumWidth(120) self._inner_hbox1.addWidget(self.valueWidget) if self.has_target: self.targetWidget = QLineEdit() self.targetWidget.setPlaceholderText(target) self.targetWidget.setMaximumWidth(120) self.targetWidget.returnPressed.connect(lambda *a: model.targeter( index, (self.targetWidget.text(), self.targetWidget.setText(''))[0])) self._inner_hbox1.addWidget(self.targetWidget) self.goButton = QPushButton('Go') self.goButton.clicked.connect(lambda *a: model.targeter( index, (self.targetWidget.text(), self.targetWidget.setText(''))[0])) self._inner_hbox1.addWidget(self.goButton) self.stopButton = QPushButton('Stop') self.stopButton.clicked.connect(lambda *a: model.stopper(index)) self._inner_hbox1.addWidget(self.stopButton) # now (conditionally) the second hbox if has_status: self._inner_hbox2 = QHBoxLayout() self._inner_vbox.addLayout(self._inner_hbox2) self.statvalueWidget = QLineEdit('statval') self.statvalueWidget.setMaximumWidth(120) self._inner_hbox2.addWidget(self.statvalueWidget) self.statusWidget = QLineEdit('Statusstring if available') self.statusWidget.setMaximumWidth(10000) self._inner_hbox2.addWidget(self.statusWidget) self.resetButton = QPushButton('Reset') self.resetButton.clicked.connect(lambda *a: model.resetter(index)) self._inner_hbox1.addWidget(self.resetButton) # self._inner_hbox2.addStretch(0.1) # allow space for resizing self._inner_hbox1.addStretch(1) self._inner_vbox.setSpacing(0) self._inner_vbox.setContentsMargins(0, 0, 0, 0) self._hlayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self._hlayout) self.show()
class BaseDev(QWidget): has_target = False has_status = False def __init__(self, model, name, addr): super(BaseDev, self).__init__() self.name = name self.model = model self.addr = addr self._namelabel = QLabel(name) self._namelabel.setMinimumWidth(120) self._namelabel.setMaximumWidth(120) # self._groupbox = QGroupBox(name) self._groupbox = QFrame() # self._groupbox.setFlat(False) # self._groupbox.setCheckable(False) self._hlayout = QHBoxLayout() self._hlayout.addWidget(self._namelabel) self._hlayout.addWidget(self._groupbox) self._hlayout.setSpacing(0) # inside of the groupbox there is a vbox with 1 or 2 hboxes self._inner_vbox = QVBoxLayout() self._groupbox.setLayout(self._inner_vbox) # upper inner hbox self._inner_hbox1 = QHBoxLayout() self._inner_vbox.addLayout(self._inner_hbox1) # fill upper hbox self.valueWidget = QLineEdit('0b123456789abcdef0') self.valueWidget.setMaximumWidth(120) self._inner_hbox1.addWidget(self.valueWidget) if self.has_target: self.targetWidget = QLineEdit() self.targetWidget.setPlaceholderText('') self.targetWidget.setMaximumWidth(120) self.targetWidget.returnPressed.connect(self._go_clicked) self._inner_hbox1.addWidget(self.targetWidget) self.goButton = QPushButton('Go') self.goButton.clicked.connect(self._go_clicked) self._inner_hbox1.addWidget(self.goButton) self.stopButton = QPushButton('Stop') self.stopButton.clicked.connect(self._stop_clicked) self._inner_hbox1.addWidget(self.stopButton) # now (conditionally) the second hbox if self.has_status: self._inner_hbox2 = QHBoxLayout() self._inner_vbox.addLayout(self._inner_hbox2) self.statvalueWidget = QLineEdit('statval') self.statvalueWidget.setMaximumWidth(120) self._inner_hbox2.addWidget(self.statvalueWidget) self.statusWidget = QLineEdit('Statusstring if available') self.statusWidget.setMaximumWidth(10000) self._inner_hbox2.addWidget(self.statusWidget) self.resetButton = QPushButton('Reset') self.resetButton.clicked.connect(self._reset_clicked) self._inner_hbox1.addWidget(self.resetButton) # self._inner_hbox2.addStretch(0.1) # allow space for resizing self._inner_hbox1.addStretch(1) self._inner_vbox.setSpacing(0) self._inner_vbox.setContentsMargins(0, 0, 0, 0) self._hlayout.setContentsMargins(0, 0, 0, 0) self.setLayout(self._hlayout) self.show() def _go_clicked(self): self.model.targeter(self.index, self.targetWidget.text()) self.targetWidget.setText('') def _stop_clicked(self): self.model.stopper(self.index) def _reset_clicked(self): self.resetter(self.index) def _update(self): pass def _str2bin(self, value): return int(value) def _bin2str(self, value): return str(value) def _status(self, value): return "no status decoder implemented"
class MainWindow(QMainWindow): i = 0 def __init__(self, parent=None): QMainWindow.__init__(self, parent) # scroll area Widget contents - layout self.scrollLayout = QFormLayout() self.scrollLayout.setContentsMargins(0, 0, 0, 0) # scroll area Widget contents self.scrollWidget = QWidget() self.scrollWidget.setLayout(self.scrollLayout) # scroll area self.scrollArea = QScrollArea() self.scrollArea.setWidgetResizable(True) self.scrollArea.setWidget(self.scrollWidget) # main layout self.mainLayout = QVBoxLayout() self.mainLayout.setSpacing(0) # add all main to the main vLayout self.mainLayout.addWidget(self.scrollArea) # central Widget self.centralWidget = QWidget() self.centralWidget.setLayout(self.mainLayout) # set central Widget self.setCentralWidget(self.centralWidget) try: self._bus = ModbusTcpClient('drum.panda.frm2') self._bus.connect() self._sync() print("Modbus synced!") print(self.ReadWord(0x20), self.ReadWord(0x21)) except Exception as err: print("Modbus failed: %r, using demo mode!" % err) self._bus = None self._sync() widgets = [] widgets.append( BaseDev(self, 'mtt motor inputs', 0, has_status=True, addr=34)) widgets.append( BaseDev(self, 'spare inputs', 1, has_status=True, addr=44)) widgets.append( BaseDev(self, 'spare outputs', 2, has_status=True, target='0x%04x' % self.ReadWord(60), addr=59)) widgets.append( BaseDev(self, 'enable_word', 3, target='0x%04x' % self.ReadWord(73), addr=73)) widgets.append(BaseDev(self, 'cycle_counter', 4, addr=74)) widgets.append( BaseDev(self, 'handle_cw', 5, has_status=True, target='%d' % self.ReadWord(51), addr=50)) widgets.append( BaseDev(self, 'handle_ccw', 6, has_status=True, target='%d' % self.ReadWord(54), addr=53)) widgets.append(BaseDev(self, 'enc1', 7, has_status=True, addr=46)) widgets.append(BaseDev(self, 'enc2', 8, has_status=True, addr=48)) widgets.append( BaseDev(self, 'arm_switch', 9, has_status=True, target='%d' % self.ReadWord(63), addr=62)) widgets.append(BaseDev(self, 'encoder1', 10, has_status=False, addr=65)) widgets.append( BaseDev(self, 'arm', 11, has_status=True, target='%f' % self.ReadFloat(70), value_offset=2, addr=68)) widgets.append( BaseDev(self, 'magnet', 12, has_status=True, target='%d' % self.ReadWord(57), addr=56)) widgets.append( BaseDev(self, 'air', 13, has_status=True, target='%d' % self.ReadWord(60), addr=59)) for w in widgets: self.addWidget(w) widgets.sort(key=lambda w: w.index) self.widgets = widgets self.startTimer(225) # in ms ! def resetter(self, index): w = self.widgets[index] if w.has_status: addr = w.base_address + w.offset if w.has_target: addr += w.offset print(addr) self.reset(addr) else: print("resetter: device %d has no status" % index) def stopper(self, index): w = self.widgets[index] if w.has_target: addr = w.base_address if w.name == 'enable_word': print("stopper: DISABLING %d" % (addr)) self.WriteWord(addr, 0) else: addr += w.offset if w.has_target: addr += w.offset print("stopper: stopping on addr %d" % (addr)) self.stop(addr) else: print("stopper: cannot stop - no target %d" % index) def targeter(self, index, valuestr): w = self.widgets[index] if w.has_target: v = str(valuestr).strip() if not v: return # ignore empty values (no value entered into the box?) addr = w.base_address if w.offset == 2: v = float(v) addr += w.offset print("targeter: setting addr %d to %f" % (addr, v)) self.WriteFloat(addr, v) else: if v.startswith('0x') or v.startswith('0X'): v = int(v[2:], 16) elif v.startswith(('x', 'X', '$')): v = int(v[1:], 16) else: v = int(v) if w.name != 'enable_word': addr += w.offset print("targeter: setting addr %d to %r" % (addr, valuestr)) self.WriteWord(addr, v) else: print("targeter: device fas no target %d:%r" % (index, valuestr)) def ReadWord(self, addr): return self._registers[int(addr)] def WriteWord(self, addr, value): self._bus.write_register(int(addr | 0x4000), int(value)) self._sync() def ReadDWord(self, addr): return unpack( '<I', pack('<HH', self._registers[int(addr)], self._registers[int(addr) + 1])) def WriteDWord(self, addr, value): low, high = unpack('<HH', pack('<I', int(value))) self._bus.write_registers(int(addr | 0x4000), [low, high]) self._sync() def ReadFloat(self, addr): return unpack( '<f', pack('<HH', self._registers[int(addr) + 1], self._registers[int(addr)])) def WriteFloat(self, addr, value): low, high = unpack('<HH', pack('<f', float(value))) self._bus.write_registers(int(addr | 0x4000), [high, low]) self._sync() def _sync(self): if self._bus: self._registers = self._bus.read_holding_registers(0x4000, 75).registers[:] # print(self._registers) else: self._registers = [self.i] * 75 self.i += 1 def reset(self, addr): self.WriteWord(addr, 0x0fff & self.ReadWord(addr)) def stop(self, addr): self.WriteWord(addr, 0x1000 | (0x0fff & self.ReadWord(addr))) def timerEvent(self, event): self._sync() w = self.widgets # 1: %MB68: cycle counter val = self.ReadWord(34) stat = self.ReadWord(35) w[0].valueWidget.setText(bin(65536 | val)[3:]) w[0].statvalueWidget.setText('0x%04x' % stat) w[0].statusWidget.setText('mtt motor inputs') # 10: %MB96: spare inputs val = self.ReadWord(44) stat = self.ReadWord(45) w[1].valueWidget.setText(bin(65536 | val)[3:]) w[1].statvalueWidget.setText('0x%04x' % stat) w[1].statusWidget.setText('spare inputs') # 17: %MB136: spare outputs val = self.ReadWord(59) target = self.ReadWord(60) stat = self.ReadWord(61) stati = Stati(stat) w[2].valueWidget.setText('%d' % val) w[2].statvalueWidget.setText('0x%04x' % stat) w[2].statusWidget.setText(', '.join(stati)) w[2].targetWidget.setPlaceholderText('%d' % target) # 19: %MB146: enable code word val = self.ReadWord(73) w[3].valueWidget.setText('0x%04x' % val) w[3].targetWidget.setPlaceholderText('0x%04x' % val) # 20: %MB148: cycle counter val = self.ReadWord(74) w[4].valueWidget.setText('0x%04x' % val) # 13: %MB112: liftclamp val = self.ReadWord(50) target = self.ReadWord(51) stat = self.ReadWord(52) stati = Stati(stat) if (stat & 0x9000) == 0x9000: stati.append('ERR:Movement timed out') if stat & 0x0800: stati.append('ERR:liftclamp switches in Error') if stat & 0x0004: stati.append('No Air pressure') if stat & 0x0002: stati.append('ERR:Actuator Wire shorted!') if stat & 0x0001: stati.append('ERR:Actuator Wire open!') w[5].valueWidget.setText('%d' % val) w[5].statvalueWidget.setText('0x%04x' % stat) w[5].statusWidget.setText(', '.join(stati)) w[5].targetWidget.setPlaceholderText('%d' % target) # 13: %MB112: liftclamp val = self.ReadWord(53) target = self.ReadWord(54) stat = self.ReadWord(55) stati = Stati(stat) if (stat & 0x9000) == 0x9000: stati.append('ERR:Movement timed out') if stat & 0x0800: stati.append('ERR:liftclamp switches in Error') if stat & 0x0004: stati.append('No Air pressure') if stat & 0x0002: stati.append('ERR:Actuator Wire shorted!') if stat & 0x0001: stati.append('ERR:Actuator Wire open!') w[6].valueWidget.setText('%d' % val) w[6].statvalueWidget.setText('0x%04x' % stat) w[6].statusWidget.setText(', '.join(stati)) w[6].targetWidget.setPlaceholderText('%d' % target) # 7: %MB192: enc1 val = self.ReadWord(46) stat = self.ReadWord(47) stati = Stati(stat) if stat & 0x0002: stati.append('ERR:Underflow!') if stat & 0x0001: stati.append('ERR:Overflow!') w[7].valueWidget.setText(str(val)) w[7].statvalueWidget.setText('0x%04x' % stat) w[7].statusWidget.setText(', '.join(stati)) # 7: %MB192: arm val = self.ReadWord(48) stat = self.ReadWord(49) stati = Stati(stat) if stat & 0x0002: stati.append('ERR:Underflow!') if stat & 0x0001: stati.append('ERR:Overflow!') w[8].valueWidget.setText(str(val)) w[8].statvalueWidget.setText('0x%04x' % stat) w[8].statusWidget.setText(', '.join(stati)) # 17: %MB136: spare outputs val = self.ReadWord(62) target = self.ReadWord(63) stat = self.ReadWord(64) stati = Stati(stat) w[9].valueWidget.setText('%d' % val) w[9].statvalueWidget.setText('0x%04x' % stat) w[9].statusWidget.setText(', '.join(stati)) w[9].targetWidget.setPlaceholderText('%d' % target) # encoder val = self.ReadFloat(65) w[10].valueWidget.setText('%f' % val) # motro val = self.ReadFloat(68) target = self.ReadFloat(70) stat = self.ReadWord(72) stati = Stati(stat) w[11].valueWidget.setText('%f' % val) w[11].statvalueWidget.setText('0x%04x' % stat) w[11].statusWidget.setText(', '.join(stati)) w[11].targetWidget.setPlaceholderText('%f' % target) # 13: %MB112: liftclamp val = self.ReadWord(56) target = self.ReadWord(57) stat = self.ReadWord(58) stati = Stati(stat) if (stat & 0x9000) == 0x9000: stati.append('ERR:Movement timed out') if stat & 0x0800: stati.append('ERR:liftclamp switches in Error') if stat & 0x0004: stati.append('No Air pressure') if stat & 0x0002: stati.append('ERR:Actuator Wire shorted!') if stat & 0x0001: stati.append('ERR:Actuator Wire open!') w[12].valueWidget.setText('%d' % val) w[12].statvalueWidget.setText('0x%04x' % stat) w[12].statusWidget.setText(', '.join(stati)) w[12].targetWidget.setPlaceholderText('%d' % target) # 13: %MB112: air val = self.ReadWord(59) target = self.ReadWord(60) stat = self.ReadWord(61) stati = Stati(stat) if (stat & 0x9000) == 0x9000: stati.append('ERR:Movement timed out') if stat & 0x0800: stati.append('ERR:liftclamp switches in Error') if stat & 0x0004: stati.append('No Air pressure') if stat & 0x0002: stati.append('ERR:Actuator Wire shorted!') if stat & 0x0001: stati.append('ERR:Actuator Wire open!') w[13].valueWidget.setText('%d' % val) w[13].statvalueWidget.setText('0x%04x' % stat) w[13].statusWidget.setText(', '.join(stati)) w[13].targetWidget.setPlaceholderText('%d' % target) def addWidget(self, which): which.setContentsMargins(10, 0, 0, 0) self.scrollLayout.addRow(which) l = QFrame() l.setLineWidth(1) # l.setMidLineWidth(4) l.setFrameShape(QFrame.HLine) l.setContentsMargins(10, 0, 10, 0) self.scrollLayout.addRow(l)
class MainWindow(QMainWindow): i = 0 def __init__(self, parent=None): super(MainWindow, self).__init__(parent) # scroll area Widget contents - layout self.scrollLayout = QFormLayout() self.scrollLayout.setContentsMargins(0, 0, 0, 0) # scroll area Widget contents self.scrollWidget = QWidget() self.scrollWidget.setLayout(self.scrollLayout) # scroll area self.scrollArea = QScrollArea() self.scrollArea.setWidgetResizable(True) self.scrollArea.setWidget(self.scrollWidget) # main layout self.mainLayout = QVBoxLayout() self.mainLayout.setSpacing(0) # add all main to the main vLayout self.mainLayout.addWidget(self.scrollArea) # central Widget self.centralWidget = QWidget() self.centralWidget.setLayout(self.mainLayout) # set central Widget self.setCentralWidget(self.centralWidget) try: self._bus = ModbusTcpClient('wechsler.panda.frm2') self._bus.connect() self._sync() print("PLC conforms to spec %.4f" % self.ReadFloat(0)) except Exception: print("Modbus failed, using demo mode!") self._bus = None self._sync() widgets = [] widgets.append(WriteWord(self, 'last_liftpos', addr=58 / 2)) widgets.append(ReadWord(self, 'analog1', addr=92 / 2)) widgets.append(ReadWord(self, 'analog2', addr=96 / 2)) widgets.append(AnalogInput(self, 'liftpos_analog', addr=146 / 2)) widgets.append(DiscreteInput(self, 'lift_sw', addr=68 / 2)) widgets.append(LIFT(self, 'lift', 104 / 2)) widgets.append(WriteWord(self, 'last_magpos', addr=60 / 2)) widgets.append(DiscreteInput(self, 'magazin_sw', addr=72 / 2)) widgets.append(MAGAZIN(self, 'magazin', addr=110 / 2)) widgets.append(DiscreteInput(self, 'magazin_occ_sw', addr=84 / 2)) widgets.append(DiscreteInput(self, 'magazin_occ', addr=88 / 2)) widgets.append(DiscreteInput(self, 'liftclamp_sw', addr=76 / 2)) widgets.append(CLAMP(self, 'liftclamp', addr=116 / 2)) widgets.append(DiscreteInput(self, 'magazinclamp_sw', addr=80 / 2)) widgets.append(CLAMP(self, 'magazinclamp', addr=122 / 2)) widgets.append(CLAMP(self, 'tableclamp', addr=128 / 2)) widgets.append(CLAMP(self, 'inhibit_relay', addr=134 / 2)) widgets.append(WriteWord(self, 'enable_word', addr=150 / 2)) widgets.append(DiscreteInput(self, 'spare inputs', addr=100 / 2)) widgets.append(DiscreteOutput(self, 'spare outputs', addr=140 / 2)) widgets.append(ReadWord(self, 'cycle_counter', addr=152 / 2)) for w in widgets: self.addWidget(w) self.widgets = widgets self.startTimer(225) # in ms ! def ReadWord(self, addr): return self._registers[int(addr)] def WriteWord(self, addr, value): if self._bus: self._bus.write_register(int(addr | 0x4000), int(value)) self._sync() def ReadDWord(self, addr): return unpack( '<I', pack('<HH', self._registers[int(addr)], self._registers[int(addr) + 1])) def WriteDWord(self, addr, value): if self._bus: low, high = unpack('<HH', pack('<I', int(value))) self._bus.write_registers(int(addr | 0x4000), [low, high]) self._sync() def ReadFloat(self, addr): return unpack( '<f', pack('<HH', self._registers[int(addr) + 1], self._registers[int(addr)])) def WriteFloat(self, addr, value): if self._bus: low, high = unpack('<HH', pack('<f', float(value))) self._bus.write_registers(int(addr | 0x4000), [high, low]) self._sync() def _sync(self): if self._bus: self._registers = self._bus.read_holding_registers(0x4000, 77).registers[:] else: self._registers = [self.i + i for i in range(77)] self.i += 1 def timerEvent(self, event): # pylint: disable=R0915 self._sync() for w in self.widgets: w._update() return def addWidget(self, which): which.setContentsMargins(10, 0, 0, 0) self.scrollLayout.addRow(which) l = QFrame() l.setLineWidth(1) # l.setMidLineWidth(4) l.setFrameShape(QFrame.HLine) l.setContentsMargins(10, 0, 10, 0) self.scrollLayout.addRow(l)