├── window_sample.png ├── serialtest.py ├── untitled0.py ├── README.md └── oguplot.py /window_sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OzyOzk/Oguplot/HEAD/window_sample.png -------------------------------------------------------------------------------- /serialtest.py: -------------------------------------------------------------------------------- 1 | import serial.tools.list_ports 2 | controller = serial.Serial 3 | 4 | ports = serial.tools.list_ports.comports() 5 | 6 | for p in ports: 7 | print(p.serial_number) 8 | if p.serial_number == 9553034373435110E020: 9 | p.device 10 | 11 | ser = serial.Serial(p.device, 9600) 12 | 13 | while(True): 14 | line = ser.readline() 15 | #print(line) 16 | first = line.decode().split(",")[0] 17 | secon = line.decode().split(",")[1] 18 | print(first, secon) -------------------------------------------------------------------------------- /untitled0.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Fri Nov 9 19:17:05 2018 4 | 5 | @author: Ouzi 6 | """ 7 | 8 | from PyQt5 import QtGui # (the example applies equally well to PySide) 9 | import pyqtgraph as pg 10 | 11 | ## Always start by initializing Qt (only once per application) 12 | app = QtGui.QApplication([]) 13 | 14 | ## Define a top-level widget to hold everything 15 | w = QtGui.QWidget() 16 | 17 | ## Create some widgets to be placed inside 18 | btn = QtGui.QPushButton('press me') 19 | text = QtGui.QLineEdit('enter text') 20 | listw = QtGui.QListWidget() 21 | plot = pg.PlotWidget() 22 | 23 | ## Create a grid layout to manage the widgets size and position 24 | layout = QtGui.QGridLayout() 25 | w.setLayout(layout) 26 | 27 | ## Add widgets to the layout in their proper positions 28 | layout.addWidget(btn, 0, 0) # button goes in upper-left 29 | layout.addWidget(text, 1, 0) # text edit goes in middle-left 30 | layout.addWidget(listw, 2, 0) # list widget goes in bottom-left 31 | layout.addWidget(plot, 0, 1, 3, 1) # plot goes on right side, spanning 3 rows 32 | 33 | ## Display the widget as a new window 34 | w.show() 35 | 36 | ## Start the Qt event loop 37 | app.exec_() -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | __Oguplot Repository__ 2 | ===================================== 3 | 4 | ![sample_image](https://github.com/OzyOzk/Oguplot/blob/master/window_sample.png) 5 | 6 | Repository to contain source code and versions of the python 7 | live plot project. Will contain; 8 | 9 | * Code 10 | * Issues 11 | * Workflow 12 | * Features to be added 13 | ### Demo 14 | 15 | [Demo of the old script in action](https://www.youtube.com/watch?v=c8xMLtfUHTE) 16 | 17 | ### Current Issues: 18 | 19 | ~~Pyserial warning. The following error warning keeps coming up when I run on Windows 7 (does not happen on Ubuntu 14.04)~~ 20 | ```python 21 | SerialException: could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) 22 | ``` 23 | ~~Restarting the kernel is a solution. Removing all variables also works. (From IPython console in Spyder)~~ 24 | 25 | **Issue resolved**. The program now checks if the port is available. 26 | 27 | Also resolved an issue where every second time the script is run from the ipython console, the kernel crashes. **This is specifically 28 | a spyder issue**. This is due to the previous instance of QT not being ternimated when the script is shut, thus when the script is 29 | executed again, the kernel crashes as Qt does not like more than one instance of itself running at once. Previously, a new instance 30 | would be created at the start; 31 | 32 | ```python 33 | app = QtGui.QApplication([]) 34 | ``` 35 | 36 | Now the instance is created only if one does not already exist. Otherwise old instance is used 37 | ```python 38 | if not QtGui.QApplication.instance(): 39 | app = QtGui.QApplication([]) 40 | else: 41 | app = QtGui.QApplication.instance() 42 | ``` 43 | 44 | ### Added GUI 45 | 46 | The script no longer plots as soon as it is run. Now the serial number of the device printing csv values needs to be entered. Hitting 47 | poll button will then query all connected serial devices for their serial numbers. If a matching serial number is found, the graph will start plotting. Also added a close button to close the current port. Hitting close will close the serial port and the plot will stop. 48 | 49 | ### Micro-controller setup 50 | 51 | For testing I'm using an Arduino Uno Rev3 with an MPU6050. The code is printed to the serial monitor in comma separated format in x,y pairs on a new line. Any two values will work as long as they are separated by a comma. See below code for example; 52 | 53 | ```c++ 54 | Serial.print(AcX);Serial.print(",");Serial.println(AcY); 55 | ``` 56 | Where AcX and AcY are 16 bit signed integers (int16_t). See below for sample output in Serial monitor; 57 | 58 | ``` 59 | 140,-140 60 | 48,-256 61 | 104,-188 62 | 176,-372 63 | 172,-208 64 | -32,-204 65 | 124,-212 66 | 336,-388 67 | 0,-328 68 | ``` 69 | 70 | -------------------------------------------------------------------------------- /oguplot.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from PyQt5 import QtGui, QtCore 3 | import serial.tools.list_ports 4 | import pyqtgraph as pg 5 | import numpy as np 6 | import sys 7 | import math 8 | """ 9 | Created on Thu Jan 18 14:38:46 2018 10 | 11 | @author: OzyOzk 12 | """ 13 | 14 | """ 15 | If you are on a windows machine, after succesfully running the code once, if you 16 | terminated and run again, you will get the following error 17 | 18 | SerialException: could not open port 'COM3': PermissionError(13, 'Access is denied.', None, 5) 19 | 20 | You need to either reset your Kernel or delete all environment variables. On Spyder, 21 | this can be done in the Ipython console. Reset is inside the options menu on the top 22 | right of the console (Cog symbol). Remove all variables is to the left of the Cog by 23 | the stop button. 24 | 25 | Once I find a proper solution I will update the code. 26 | """ 27 | 28 | 29 | ser = None 30 | dt = 5 # Time delta in milliseconds 31 | element_count = 0 32 | curves = list() 33 | curve_xdata = list() 34 | 35 | size = 500 36 | buffersize = 2*500 37 | #serial_number = "9553034373435110E020" 38 | 39 | 40 | def find_device(sn): 41 | ports = serial.tools.list_ports.comports() 42 | for p in ports: 43 | if p.serial_number == sn: 44 | comport = p.device 45 | print("Found device on", p.device) 46 | return comport 47 | return "Not found" 48 | 49 | 50 | def connect_to_device(comport): 51 | return serial.Serial(comport, 9600, timeout=1) 52 | 53 | 54 | def poll_button(lineedit): 55 | return(lineedit.text()) 56 | 57 | 58 | def find_length(ser): 59 | elements = 0 60 | for x in range(5): 61 | line = ser.readline() 62 | csv = line.decode().split(',') 63 | elements += len(csv) 64 | return elements/5 65 | 66 | 67 | def qlewrapper(): 68 | global element_count 69 | serial_number = poll_button(t1) 70 | comport = find_device(serial_number) 71 | if comport == "Not found": 72 | return 73 | global ser 74 | ser = connect_to_device(comport) 75 | print(comport) 76 | element_count = int(math.ceil(find_length(ser))) 77 | print("There are", element_count, "values in the CSV") 78 | 79 | 80 | def make_curves(x, px): 81 | global element_count, curves, curve_xdata, buffersize 82 | for x in range(element_count): 83 | curves[x] = px.plot() 84 | curve_xdata[x] = np.zeros(buffersize+1, int) 85 | 86 | 87 | def shift_elements(buffer, csv): 88 | global size, buffersize, element_count 89 | i = buffer[buffersize] 90 | buffer[i] = buffer[i+size] = csv[0] 91 | buffer[buffersize] = i = (i+1) % size 92 | 93 | 94 | def close_port(): 95 | global ser 96 | if (ser != None): 97 | ser.close() 98 | 99 | 100 | def close_app(): 101 | close_port() 102 | sys.exit() 103 | 104 | # for Spyder. When you close your window, the QtApplicaiton instance is 105 | # still there after being created once. Therefore check if a Qt instance 106 | # already exists, if it does, then use it, otherwise, create new instance 107 | 108 | 109 | if not QtGui.QApplication.instance(): 110 | app = QtGui.QApplication([]) 111 | else: 112 | app = QtGui.QApplication.instance() 113 | 114 | win = QtGui.QWidget() 115 | win.setWindowTitle("OguPlot") 116 | win.resize(1000, 600) 117 | layout = QtGui.QGridLayout() 118 | win.setLayout(layout) 119 | 120 | b1 = QtGui.QPushButton("Poll") 121 | b1.clicked.connect(qlewrapper) 122 | 123 | b2 = QtGui.QPushButton("Close port") 124 | b2.clicked.connect(close_port) 125 | 126 | t1 = QtGui.QLineEdit("Enter Device Serial") 127 | 128 | p1 = pg.PlotWidget() 129 | p1.setRange(yRange=[-18000, 18000]) 130 | p1.addLegend() 131 | p1.showGrid(x=True, y=True, alpha=0.8) 132 | p1.setLabel('left', 'Amplitude (16bit Signed)') 133 | 134 | curve1 = p1.plot(pen='y', name="Data 1") 135 | curve2 = p1.plot(pen='g', name="Data 2") 136 | curve3 = p1.plot() 137 | 138 | layout.addWidget(p1, 0, 0, 1, 3) 139 | layout.addWidget(b1, 1, 0) 140 | layout.addWidget(t1, 1, 1) 141 | layout.addWidget(b2, 1, 2) 142 | 143 | 144 | buffer1 = np.zeros(buffersize+1, int) 145 | buffer2 = np.zeros(buffersize+1, int) 146 | 147 | x = 0 148 | 149 | 150 | def update(): 151 | global curve1, curve2, x, ser, size, buffersize 152 | if(ser != None and ser.is_open): 153 | line = ser.readline() 154 | csv = line.decode().split(',') 155 | x += 1 156 | if len(csv) == 2: 157 | 158 | i = buffer1[buffersize] 159 | buffer1[i] = buffer1[i+size] = csv[0] 160 | buffer1[buffersize] = i = (i+1) % size 161 | 162 | j = buffer2[buffersize] 163 | buffer2[j] = buffer2[j+size] = csv[1] 164 | buffer2[buffersize] = j = (j+1) % size 165 | 166 | curve1.setData(buffer1[i:i+size]) 167 | curve1.setPos(x, 0) 168 | curve2.setData(buffer2[j:j+size]) 169 | curve2.setPos(x, 0) 170 | app.processEvents() 171 | 172 | 173 | timer = QtCore.QTimer() 174 | timer.timeout.connect(update) 175 | timer.start(dt) 176 | timer.setInterval(dt) 177 | # if(ser != None): 178 | # timer.stop() 179 | win.show() 180 | app.exec_() 181 | --------------------------------------------------------------------------------