├── example-pi.png ├── .gitignore ├── LICENSE ├── dp832gui ├── dp832.py └── dpgui.py └── README.md /example-pi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/colinoflynn/dp832-gui/HEAD/example-pi.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Colin O'Flynn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /dp832gui/dp832.py: -------------------------------------------------------------------------------- 1 | 2 | #Install pyvisa, use easy_install for example: 3 | #easy_install pyvisa 4 | import visa 5 | 6 | class DP832(object): 7 | def __init__(self): 8 | pass 9 | 10 | def conn(self, constr="USB0::0x1AB1::0x0E11::DPXXXXXXXXXXX::INSTR"): 11 | """Attempt to connect to instrument""" 12 | rm = visa.ResourceManager() 13 | self.inst = rm.open_resource(constr) 14 | 15 | def identify(self): 16 | """Return identify string which has serial number""" 17 | return self.inst.query("*IDN?") 18 | 19 | def readings(self, channel="CH1"): 20 | """Read voltage/current/power from CH1/CH2/CH3""" 21 | resp = self.inst.query("MEAS:ALL? %s"%channel) 22 | resp = resp.split(',') 23 | dr = {"v":float(resp[0]), "i":float(resp[1]), "p":float(resp[2])} 24 | return dr 25 | 26 | def dis(self): 27 | del self.inst 28 | 29 | def writing(self, command=""): 30 | self.inst.write(command) 31 | 32 | if __name__ == '__main__': 33 | test = DP832() 34 | 35 | #Insert your serial number here / confirm via Ultra Sigma GUI 36 | test.conn("USB0::0x1AB1::0x0E11::DPXXXXXXXXXXX::INSTR") 37 | 38 | print test.readings() 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | dp832-gui 2 | ========= 3 | 4 | Rigol DP832 GUI. This is a simple graphing GUI for the Rigol DP832 connected via VISA (tested over USB although other IO connections should work). 5 | 6 | To use this you'll need to install: 7 | 8 | * Ultra Sigma from Rigol [OPTIONAL: Can also just copy/paste the address from the DP832 display] 9 | * Python 2.7 with PySide, suggested to just install WinPython (see http://winpython.sourceforge.net/) 10 | * PyQtGraph, see http://www.pyqtgraph.org/ 11 | * pyvisa, use easy_install 12 | 13 | Once your system is running, just run dpgui.py via your installed Python. Supply the address string (open Ultra Sigma, make sure it finds your Power Supply, and copy-paste address string from that, OR just look in the 'utilities' menu). Will look something like USB0::0x1AB1::0x0E11::DP8XXXXXXXX::INSTR 14 | 15 | If the address copied from the DP832 display doesn't work, install Ultra Sigma to confirm it is detected there. If Ultra Sigma didn't see the power supply something else is up... 16 | 17 | Bugs 18 | ======= 19 | 20 | * Can only set number of windows before connecting 21 | * Doesn't validate instrument state before doing anything, so crashes are likely. Check python output for reasons. 22 | 23 | Notes 24 | ======== 25 | 26 | While connected remotely you CANNOT control the power supply from the front panel. You can turn outputs on/off seems to be about it. So setup your required voltages etc first then hit connect. If you want to change anything just disconnect, modify settings on the panel, and connect again. You don't need to restart the dp832gui application. 27 | -------------------------------------------------------------------------------- /dp832gui/dpgui.py: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Very Crappy DP832 GUI by Colin O'Flynn 4 | # Crashes under most circumstances, if you do things in the wrong order, etc 5 | # 6 | 7 | 8 | import sys 9 | 10 | from PySide.QtCore import * 11 | from PySide.QtGui import * 12 | 13 | try: 14 | import pyqtgraph as pg 15 | import pyqtgraph.parametertree.parameterTypes as pTypes 16 | from pyqtgraph.parametertree import Parameter, ParameterTree, ParameterItem, registerParameterType 17 | except ImportError: 18 | print "Install pyqtgraph from http://www.pyqtgraph.org" 19 | raise 20 | 21 | from dp832 import DP832 22 | 23 | class GraphWidget(QWidget): 24 | """ 25 | This GraphWidget holds a pyqtgraph PlotWidget, and adds a toolbar for the user to control it. 26 | """ 27 | 28 | def __init__(self): 29 | pg.setConfigOption('background', 'w') 30 | pg.setConfigOption('foreground', 'k') 31 | 32 | QWidget.__init__(self) 33 | layout = QVBoxLayout() 34 | 35 | self.pw = pg.PlotWidget(name="Power Trace View") 36 | self.pw.setLabel('top', 'Power Trace View') 37 | self.pw.setLabel('bottom', 'Samples') 38 | self.pw.setLabel('left', 'Data') 39 | vb = self.pw.getPlotItem().getViewBox() 40 | vb.setMouseMode(vb.RectMode) 41 | 42 | layout.addWidget(self.pw) 43 | 44 | self.setLayout(layout) 45 | 46 | self.setDefaults() 47 | 48 | def setDefaults(self): 49 | self.defaultYRange = None 50 | 51 | def VBStateChanged(self, obj): 52 | """Called when ViewBox state changes, used to sync X/Y AutoScale buttons""" 53 | arStatus = self.pw.getPlotItem().getViewBox().autoRangeEnabled() 54 | 55 | #X Axis 56 | if arStatus[0]: 57 | self.XLockedAction.setChecked(False) 58 | else: 59 | self.XLockedAction.setChecked(True) 60 | 61 | #Y Axis 62 | if arStatus[1]: 63 | self.YLockedAction.setChecked(False) 64 | else: 65 | self.YLockedAction.setChecked(True) 66 | 67 | def VBXRangeChanged(self, vb, range): 68 | """Called when X-Range changed""" 69 | self.xRangeChanged.emit(range[0], range[1]) 70 | 71 | def xRange(self): 72 | """Returns the X-Range""" 73 | return self.pw.getPlotItem().getViewBox().viewRange()[0] 74 | 75 | def YDefault(self, extraarg=None): 76 | """Copy default Y range axis to active view""" 77 | if self.defaultYRange is not None: 78 | self.setYRange(self.defaultYRange[0], self.defaultYRange[1]) 79 | 80 | def setDefaultYRange(self, lower, upper): 81 | """Set default Y-Axis range, for when user clicks default button""" 82 | self.defaultYRange = [lower, upper] 83 | 84 | def setXRange(self, lower, upper): 85 | """Set the X Axis to extend from lower to upper""" 86 | self.pw.getPlotItem().getViewBox().setXRange(lower, upper) 87 | 88 | def setYRange(self, lower, upper): 89 | """Set the Y Axis to extend from lower to upper""" 90 | self.pw.getPlotItem().getViewBox().setYRange(lower, upper) 91 | 92 | def xAutoScale(self, enabled): 93 | """Auto-fit X axis to data""" 94 | vb = self.pw.getPlotItem().getViewBox() 95 | bounds = vb.childrenBoundingRect(None) 96 | vb.setXRange(bounds.left(), bounds.right()) 97 | 98 | def yAutoScale(self, enabled): 99 | """Auto-fit Y axis to data""" 100 | vb = self.pw.getPlotItem().getViewBox() 101 | bounds = vb.childrenBoundingRect(None) 102 | vb.setYRange(bounds.top(), bounds.bottom()) 103 | 104 | def xLocked(self, enabled): 105 | """Lock X axis, such it doesn't change with new data""" 106 | self.pw.getPlotItem().getViewBox().enableAutoRange(pg.ViewBox.XAxis, ~enabled) 107 | 108 | def yLocked(self, enabled): 109 | """Lock Y axis, such it doesn't change with new data""" 110 | self.pw.getPlotItem().getViewBox().enableAutoRange(pg.ViewBox.YAxis, ~enabled) 111 | 112 | def passTrace(self, trace, startoffset=0, pen='b', clear=True): 113 | if clear: 114 | self.pw.clear() 115 | xaxis = range(startoffset, len(trace)+startoffset) 116 | self.pw.plot(xaxis, trace, pen=pen) 117 | 118 | class DP832GUI(QMainWindow): 119 | 120 | def __init__(self): 121 | super(DP832GUI, self).__init__() 122 | wid = QWidget() 123 | layout = QVBoxLayout() 124 | self.drawDone = False 125 | 126 | settings = QSettings() 127 | 128 | constr = settings.value('constring') 129 | if constr is None: constr = "USB0::0x1AB1::0x0E11::DPXXXXXXXXXXX::INSTR" 130 | 131 | self.constr = QLineEdit(constr) 132 | self.conpb = QPushButton("Connect") 133 | self.conpb.clicked.connect(self.tryConnect) 134 | 135 | self.dispb = QPushButton("Disconnect") 136 | self.dispb.clicked.connect(self.dis) 137 | 138 | self.cbNumDisplays = QSpinBox() 139 | self.cbNumDisplays.setMinimum(1) 140 | self.cbNumDisplays.setMaximum(3) 141 | 142 | layoutcon = QHBoxLayout() 143 | layoutcon.addWidget(QLabel("Connect String:")) 144 | layoutcon.addWidget(self.constr) 145 | layoutcon.addWidget(self.conpb) 146 | layoutcon.addWidget(self.dispb) 147 | layoutcon.addWidget(self.cbNumDisplays) 148 | layout.addLayout(layoutcon) 149 | 150 | self.graphlist = [] 151 | self.graphsettings = [] 152 | self.vdata = [] 153 | self.idata = [] 154 | self.pdata = [] 155 | 156 | 157 | wid.setLayout(layout) 158 | 159 | self.setCentralWidget(wid) 160 | self.setWindowTitle("DP832 GUI") 161 | 162 | def addGraphs(self, numgraphs): 163 | 164 | self.numchannels = numgraphs 165 | layout = self.centralWidget().layout() 166 | 167 | for i in range(0,self.numchannels): 168 | self.graphsettings.append({"channel":"CH%d"%(i+1), "points":1024}) 169 | self.graphlist.append(GraphWidget()) 170 | self.vdata.append([0]*self.graphsettings[-1]["points"]) 171 | self.idata.append([0]*self.graphsettings[-1]["points"]) 172 | self.pdata.append([0]*self.graphsettings[-1]["points"]) 173 | gb = QGroupBox() 174 | lgb = QVBoxLayout() 175 | lgb.addWidget(self.graphlist[-1]) 176 | 177 | sbPoints = QSpinBox() 178 | sbPoints.setMinimum(1) 179 | sbPoints.setMaximum(500000) 180 | sbPoints.setValue(self.graphsettings[-1]["points"]) 181 | sbPoints.valueChanged.connect(lambda x: self.setPoints(i, x)) 182 | 183 | self.cbChannel = QComboBox() 184 | self.cbChannel.addItem("CH1") 185 | self.cbChannel.addItem("CH2") 186 | self.cbChannel.addItem("CH3") 187 | self.cbChannel.setCurrentIndex(i) 188 | self.cbChannel.currentIndexChanged.connect(lambda x :self.setChannel(i, "CH%d"%(x+1))) 189 | 190 | lsettings = QHBoxLayout() 191 | lsettings.addWidget(QLabel("Points")) 192 | lsettings.addWidget(sbPoints) 193 | lsettings.addWidget(QLabel("Channel")) 194 | lsettings.addWidget(self.cbChannel) 195 | 196 | plotV = QPushButton("V") 197 | plotV.setCheckable(True) 198 | plotV.setChecked(True) 199 | plotI = QPushButton("I") 200 | plotI.setCheckable(True) 201 | plotP = QPushButton("P") 202 | plotP.setCheckable(True) 203 | 204 | self.onButton = QPushButton("ON") 205 | self.onButton.setCheckable(True) 206 | self.onButton.clicked.connect(self.tryOn) 207 | 208 | self.pauseButton = QPushButton("Pause") 209 | self.pauseButton.setCheckable(True) 210 | self.pauseButton.clicked.connect(self.tryPause) 211 | 212 | 213 | self.graphsettings[-1]["venabled"] = plotV 214 | self.graphsettings[-1]["ienabled"] = plotI 215 | self.graphsettings[-1]["penabled"] = plotP 216 | 217 | lsettings.addWidget(plotV) 218 | lsettings.addWidget(plotI) 219 | lsettings.addWidget(plotP) 220 | lsettings.addWidget(self.onButton) 221 | lsettings.addWidget(self.pauseButton) 222 | 223 | lsettings.addStretch() 224 | 225 | lgb.addLayout(lsettings) 226 | 227 | gb.setLayout(lgb) 228 | 229 | layout.addWidget(gb) 230 | 231 | def tryOn(self): 232 | if self.onButton.isChecked() == True: 233 | self.inst.writing(":OUTP "+self.cbChannel.currentText()+",ON") 234 | else: 235 | self.inst.writing(":OUTP "+self.cbChannel.currentText()+",OFF") 236 | 237 | def tryPause(self): 238 | if self.pauseButton.isChecked() == True: 239 | self.readtimer.stop() 240 | else: 241 | self.readtimer.start() 242 | 243 | def dis(self): 244 | self.readtimer.stop() 245 | self.inst.dis() 246 | 247 | def tryConnect(self): 248 | 249 | if self.drawDone == False: 250 | self.addGraphs(self.cbNumDisplays.value()) 251 | self.cbNumDisplays.setEnabled(False) 252 | self.resize(800,600) 253 | self.drawDone = True 254 | 255 | constr = self.constr.text() 256 | QSettings().setValue('constring', constr) 257 | 258 | self.inst = DP832() 259 | self.inst.conn(constr) 260 | 261 | self.readtimer = QTimer() 262 | self.readtimer.setInterval(250) 263 | self.readtimer.timeout.connect(self.updateReadings) 264 | self.readtimer.start() 265 | 266 | def setChannel(self, graphnum, channelstr): 267 | self.graphsettings[graphnum]["channel"] = channelstr 268 | 269 | def setPoints(self, graphnum, points): 270 | self.graphsettings[graphnum]["points"] = points 271 | 272 | def updateReadings(self): 273 | for i, gs in enumerate(self.graphsettings): 274 | readings = self.inst.readings(gs["channel"]) 275 | self.vdata[i].append(readings["v"]) 276 | self.idata[i].append(readings["i"]) 277 | self.pdata[i].append(readings["p"]) 278 | 279 | while len(self.vdata[i]) > gs["points"]: 280 | self.vdata[i].pop(0) 281 | 282 | while len(self.idata[i]) > gs["points"]: 283 | self.idata[i].pop(0) 284 | 285 | while len(self.pdata[i]) > gs["points"]: 286 | self.pdata[i].pop(0) 287 | 288 | self.redrawGraphs() 289 | 290 | def redrawGraphs(self): 291 | for i,g in enumerate(self.graphlist): 292 | 293 | clear = True 294 | 295 | if self.graphsettings[i]["venabled"].isChecked(): 296 | g.passTrace(self.vdata[i], pen='b') 297 | clear = False 298 | 299 | if self.graphsettings[i]["ienabled"].isChecked(): 300 | g.passTrace(self.idata[i], pen='r', clear=clear) 301 | 302 | if self.graphsettings[i]["penabled"].isChecked(): 303 | g.passTrace(self.pdata[i], pen='k', clear=clear) 304 | 305 | def makeApplication(): 306 | # Create the Qt Application 307 | app = QApplication(sys.argv) 308 | app.setOrganizationName("GhettoFab Productions") 309 | app.setApplicationName("DP832 GUI") 310 | return app 311 | 312 | if __name__ == '__main__': 313 | app = makeApplication() 314 | 315 | # Create and show the form 316 | window = DP832GUI() 317 | window.show() 318 | 319 | # Run the main Qt loop 320 | sys.exit(app.exec_()) 321 | --------------------------------------------------------------------------------