├── BasicRealtimePlotter ├── sketch.properties ├── plotter_config.json ├── MockupSerial.pde ├── BasicRealtimePlotter.pde └── GraphClass.pde ├── RealtimePlotterWithControlPanel ├── images │ ├── button_a.png │ ├── button_b.png │ └── button_c.png ├── plotter_config.json ├── MockupSerial.pde ├── robot_config.json ├── ControlFrame.pde ├── RealtimePlotterWithControlPanel.pde └── GraphClass.pde ├── LICENSE ├── RealtimePlotterArduinoCode └── RealtimePlotterArduinoCode.ino └── README.md /BasicRealtimePlotter/sketch.properties: -------------------------------------------------------------------------------- 1 | mode.id=processing.mode.java.JavaMode 2 | mode=Java 3 | -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/images/button_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebnil/RealtimePlotter/HEAD/RealtimePlotterWithControlPanel/images/button_a.png -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/images/button_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebnil/RealtimePlotter/HEAD/RealtimePlotterWithControlPanel/images/button_b.png -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/images/button_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sebnil/RealtimePlotter/HEAD/RealtimePlotterWithControlPanel/images/button_c.png -------------------------------------------------------------------------------- /BasicRealtimePlotter/plotter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bcMaxY": "100", 3 | "bcMinY": "0", 4 | "bcMultiplier1": "1", 5 | "bcVisible1": "1.0", 6 | "bcVisible2": "1.0", 7 | "bcMultiplier6": "1", 8 | "bcVisible3": "1.0", 9 | "bcVisible4": "1.0", 10 | "bcMultiplier4": "1", 11 | "bcVisible5": "1.0", 12 | "lgMultiplier3": "1", 13 | "bcVisible6": "1.0", 14 | "bcMultiplier5": "0.3", 15 | "lgMultiplier2": "1", 16 | "bcMultiplier2": "1", 17 | "lgMultiplier1": "0.03", 18 | "bcMultiplier3": "1", 19 | "lgMultiplier6": "1", 20 | "lgMultiplier5": "0.1", 21 | "lgMultiplier4": "1", 22 | "lgMinY": "-8", 23 | "lgMaxY": "10", 24 | "lgVisible1": "1.0", 25 | "lgVisible2": "1.0", 26 | "lgVisible5": "0.0", 27 | "lgVisible6": "0.0", 28 | "lgVisible3": "1.0", 29 | "lgVisible4": "1.0" 30 | } -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/plotter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "bcMaxY": "100", 3 | "bcMinY": "0", 4 | "bcMultiplier1": "1", 5 | "bcVisible1": "1.0", 6 | "bcVisible2": "0.0", 7 | "bcMultiplier6": "1", 8 | "bcVisible3": "1.0", 9 | "bcVisible4": "1.0", 10 | "bcMultiplier4": "1", 11 | "bcVisible5": "1.0", 12 | "lgMultiplier3": "1", 13 | "bcVisible6": "1.0", 14 | "bcMultiplier5": "0.3", 15 | "lgMultiplier2": "1", 16 | "bcMultiplier2": "1", 17 | "lgMultiplier1": "1", 18 | "bcMultiplier3": "1", 19 | "lgMultiplier6": "1", 20 | "lgMultiplier5": "0.1", 21 | "lgMultiplier4": "1", 22 | "lgMinY": "-8", 23 | "lgMaxY": "10", 24 | "lgVisible1": "0.0", 25 | "lgVisible2": "0.0", 26 | "lgVisible5": "1.0", 27 | "lgVisible6": "0.0", 28 | "lgVisible3": "0.0", 29 | "lgVisible4": "0.0" 30 | } -------------------------------------------------------------------------------- /BasicRealtimePlotter/MockupSerial.pde: -------------------------------------------------------------------------------- 1 | // If you want to debug the plotter without using a real serial port 2 | 3 | int mockupValue = 0; 4 | int mockupDirection = 10; 5 | String mockupSerialFunction() { 6 | mockupValue = (mockupValue + mockupDirection); 7 | if (mockupValue > 100) 8 | mockupDirection = -10; 9 | else if (mockupValue < -100) 10 | mockupDirection = 10; 11 | String r = ""; 12 | for (int i = 0; i<6; i++) { 13 | switch (i) { 14 | case 0: 15 | r += mockupValue+" "; 16 | break; 17 | case 1: 18 | r += 100*cos(mockupValue*(2*3.14)/1000)+" "; 19 | break; 20 | case 2: 21 | r += mockupValue/4+" "; 22 | break; 23 | case 3: 24 | r += mockupValue/8+" "; 25 | break; 26 | case 4: 27 | r += mockupValue/16+" "; 28 | break; 29 | case 5: 30 | r += mockupValue/32+" "; 31 | break; 32 | } 33 | if (i < 7) 34 | r += '\r'; 35 | } 36 | delay(10); 37 | return r; 38 | } 39 | -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/MockupSerial.pde: -------------------------------------------------------------------------------- 1 | // If you want to debug the plotter without using a real serial port 2 | 3 | int mockupValue = 0; 4 | int mockupDirection = 10; 5 | String mockupSerialFunction() { 6 | mockupValue = (mockupValue + mockupDirection); 7 | if (mockupValue > 100) 8 | mockupDirection = -10; 9 | else if (mockupValue < -100) 10 | mockupDirection = 10; 11 | String r = ""; 12 | for (int i = 0; i<6; i++) { 13 | switch (i) { 14 | case 0: 15 | r += mockupValue+" "; 16 | break; 17 | case 1: 18 | r += 100*cos(mockupValue*(2*3.14)/1000)+" "; 19 | break; 20 | case 2: 21 | r += mockupValue/4+" "; 22 | break; 23 | case 3: 24 | r += mockupValue/8+" "; 25 | break; 26 | case 4: 27 | r += mockupValue/16+" "; 28 | break; 29 | case 5: 30 | r += mockupValue/32+" "; 31 | break; 32 | } 33 | if (i < 7) 34 | r += '\r'; 35 | } 36 | delay(10); 37 | return r; 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Sebastian Nilsson 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. 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/robot_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "stop": "1.0", 3 | "speedPIDKd": "0.0", 4 | "speedRawDebug": "1.0", 5 | "angleSensorSampling": "5", 6 | "anglePIDSetpointDebug": "1.0", 7 | "speedMovingAvarageFilter2Debug": "1", 8 | "outputLowerLimit": "-1", 9 | "speedKalmanFilterDebug": "1.0", 10 | "speedPIDInputDebug": "1.0", 11 | "anglePIDOutputDebug": "1.0", 12 | "motorSpeedSensorSampling": "5", 13 | "speedPIDSampling": "15", 14 | "anglePIDInputDebug": "1.0", 15 | "anglePIDLowerLimit": "4", 16 | "anglePIDAggKd": "0", 17 | "toggle": "1.0", 18 | "anglePIDAggKi": "0", 19 | "calibrate": "1.0", 20 | "anglePIDAggKp": "40", 21 | "speedPIDOutputHigherLimit": "10", 22 | "outputHigherLimit": "1", 23 | "turnLeft": "1.0", 24 | "speedPIDOutputLowerLimit": "-10", 25 | "speedPIDOutputDebug": "1.0", 26 | "anglePIDConKd": "0.0", 27 | "debugSampleRate": "30", 28 | "moveForwards": "1.0", 29 | "anglePIDConKp": "15", 30 | "speedKalmanFilterR": "50", 31 | "moveBackwards": "1.0", 32 | "calibratedZeroAngle": "-11.35", 33 | "anglePIDSampling": "5", 34 | "anglePIDConKi": "0.05", 35 | "speedPIDKp": "0.5", 36 | "angleRawDebug": "1.0", 37 | "start": "1.0", 38 | "debugLevel": "1.0", 39 | "speedPIDKi": "0.1", 40 | "angleKalmanFilterR": "20", 41 | "turnRight": "1.0" 42 | } -------------------------------------------------------------------------------- /RealtimePlotterArduinoCode/RealtimePlotterArduinoCode.ino: -------------------------------------------------------------------------------- 1 | uint8_t buffer[20]; //Buffer needed to store data packet for transmission 2 | int16_t data1 = 1; 3 | int16_t data2 = 2; 4 | int16_t data3 = 3; 5 | int16_t data4 = 4; 6 | bool debug = false; 7 | 8 | uint8_t buffer2[20]; 9 | 10 | void setup() { 11 | // put your setup code here, to run once: 12 | Serial.begin(115200); 13 | } 14 | 15 | int16_t value = 0; 16 | int8_t direction = 10; 17 | void loop() { 18 | // put your main code here, to run repeatedly: 19 | //Serial.write(value); 20 | value = (value + direction); 21 | if (value > 100) 22 | direction = -10; 23 | else if (value < -50) 24 | direction = 10; 25 | //data3 = value; 26 | //plot(value, value/2, value/4, value/8); 27 | for (uint8_t i = 0; i<7; i++) { 28 | switch (i) { 29 | case 0: 30 | Serial.print(value); 31 | break; 32 | case 1: 33 | Serial.print(value/2); 34 | break; 35 | case 2: 36 | Serial.print(value/4); 37 | break; 38 | case 3: 39 | Serial.print(value/8); 40 | break; 41 | case 4: 42 | Serial.print(value/16); 43 | break; 44 | case 5: 45 | Serial.print(value/32); 46 | break; 47 | } 48 | if (i < 7) 49 | Serial.print(" "); 50 | } 51 | Serial.print('\r'); 52 | delay(5); 53 | } 54 | uint8_t variableA = {0x00}; 55 | void plot(int16_t data1, int16_t data2, int16_t data3, int16_t data4) { 56 | int16_t pktSize; 57 | 58 | buffer[0] = 0xCDAB; //SimPlot packet header. Indicates start of data packet 59 | //buffer[1] = 4*sizeof(int16_t); //Size of data in bytes. Does not include the header and size fields 60 | buffer[1] = 1; 61 | buffer[2] = 5; 62 | buffer[3] = 6; 63 | buffer[4] = 7; 64 | buffer[5] = 8; 65 | 66 | pktSize = 2 + 2 + (4*sizeof(int16_t)); //Header bytes + size field bytes + data 67 | 68 | if (!debug) { 69 | Serial.print(data1); 70 | Serial.print(" "); 71 | Serial.print(data2); 72 | Serial.print(" "); 73 | Serial.print(data3); 74 | Serial.print(" "); 75 | Serial.print(data4); 76 | Serial.print('\r'); 77 | } 78 | else { 79 | Serial.print("Size: "); 80 | Serial.println(pktSize, HEX); 81 | for (int i = 0; iRealtime plotter video 12 | 13 | - Real-time plotter of your data while it is still being processed by your application 14 | - Plots live data from serial port. Microprocessor choice does not matter as long as it can send serial data to your computer. 15 | - 6 channels of data (and this can be increased if necessary) 16 | - Live bar charts 17 | - Live line graphs 18 | - You just send the data you want to debug with a space as delimiter like this "value1 value2 value3 value4 value5 value6". Floats or integers does not matter. 19 | - Open source 20 | - Robust. It will not crash because of corrupt data stream or similar. 21 | - Multi platform Java. Tested on OSX and Windows 8 (and should work on Linux as well). 22 | 23 | More info and other projects at http://sebastiannilsson.com/en/projekt/realtime-plotter/realtime-data-plotter/ 24 | 25 | ### How to install and use 26 | Since I have an Arduino I will use it as example but any micro processor can be used. 27 | 28 | 1. Get [ProcessingIDE](http://processing.org/) to run the code. It is a neat and useful IDE for doing graphical stuff. 29 | 3. Connect the Arduino to the usb or serial port of your computer. 30 | 4. Upload the example code (RealtimePlotterArduinoCode) to the Arduino 31 | 5. Check serial monitor (at 115200) and check that it outputs data in the format "value1 value2 value3 value4 value5 value6\r". Always end with a line break. Another way to do formatting is with the printf function like so: printf("%u %u %u %u %u %u\r", data1, data2,...); 32 | 6. Close the serial monitor (since only one resource can use the serial port at the same time). 33 | 7. Open the Processing sketch (Either BasicRealtimePlotter/BasicRealtimePlotter.pde or RealtimePlotterWithControlPanel/RealtimePlotterWithControlPanel.pde) 34 | 8. In the Processing IDE, go to Sketch->Import Library. Search for ControlP5 from Andreas Schlegel and Install it. (I used version 2.2.6). You can also manually download [controlP5 gui library](http://www.sojamo.de/libraries/controlP5/) and unzip it into your Processing libraries folder 35 | 36 | 9. Edit the sketch file so that the serial port name to correspond to the actual port ("COM3", "COM5", "/dev/tty.usbmodem1411" or whatever you have). (If you want to test the code without a serial port just set boolean mockupSerial = true;) 37 | 10. Run the sketch 38 | 39 | 40 | ### Advanced use 41 | The realtime plotter can be expanded to also send commands to the microprocessor. The usual approach when programming microprocessors is to set some parameters in the beginning of the code, upload them to the processor, see the result, change the parameters again, upload, and so on until satisfactory performance is achieved. This iterative process takes a lot of time and a better approach is to send updated parameters to the microprocessor from your computer via serial data. For example I needed to tune some parameters on my robot and created a command panel that runs in parallell with the realtime plotter. For each change in parameters I immediately am able to see the result on the plotting screen. Example code of this is located in /RealtimePlotterWithControlPanel. 42 | 43 | ![RealtimePlotterProcessingWithControlPanel](http://sebastiannilsson.com/wp-content/uploads/2013/12/RealtimePlotterProcessingWithControlPanel-300x140.png) 44 | 45 | ### Notes 46 | I decided to send and receive the data as ascii characters instead of binaries. The greatest disadvantage is performance. The greatest advantage is ease of use. 47 | 48 | In some sense the realtime data plotter can also be used as a very slow and limited digital oscilloscope. I would not recommend using it for any high frequency applications though. 49 | 50 | ### Processing 2/3 compatability 51 | - For Processing 3 compatability, use the most recent code. 52 | - For Processing 2 compatability, use the code from the Processing-2 branch or [download the older release](https://github.com/sebnil/RealtimePlotter/releases/tag/1.0). 53 | 54 | ### Some comments about earlier approaches and the used libraries 55 | I have tried many different ways of doing this. My first approach was Matlab but I had problems with it locking the serial port. It was a hassle to get it working and getting everything configured takes to much time. My second approach was Python and graphing libraries but this was still not very satisfactory. The Processing language together with a graph library and ControlP5 made the whole thing much easier. 56 | 57 | 58 | ## Support me creating open source software: 59 | [![Flattr this git repo](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=sebnil&url=https://github.com/sebnil/RealtimePlotter) 60 | 61 | Buy Me a Coffee at ko-fi.com -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/ControlFrame.pde: -------------------------------------------------------------------------------- 1 | // Settings for the control panel are saved in this file 2 | JSONObject robotConfigJSON; 3 | 4 | // the ControlFrame class extends PApplet, so we 5 | // are creating a new processing applet inside a 6 | // new frame with a controlP5 object loaded 7 | public class ControlFrame extends PApplet { 8 | 9 | int w, h; 10 | 11 | int abc = 100; 12 | 13 | public void settings() { 14 | size(w, h); 15 | 16 | } 17 | 18 | public void setup() { 19 | surface.setLocation(100, 100); 20 | surface.setResizable(false); 21 | surface.setVisible(true); 22 | frameRate(25); 23 | cp5 = new ControlP5(this); 24 | 25 | robotConfigJSON = loadJSONObject(topSketchPath+"/robot_config.json"); 26 | //printArray(json.getJSONObject("sensors2").getJSONObject("1")); 27 | 28 | // speed PID 29 | int x; 30 | int y; 31 | cp5.addTextlabel("label").setText("Speed PID (outer loop)").setPosition(x=5, y=5).setFont(createFont("Georgia", 12)); 32 | cp5.addTextfield("speed-PID Kp").setPosition(x=x+5, y=y+20).setText(getConfigString("speedPIDKp")).setWidth(40).setAutoClear(false); 33 | cp5.addTextfield("speed-PID Ki").setPosition(x, y=y+40).setText(getConfigString("speedPIDKi")).setWidth(40).setAutoClear(false); 34 | cp5.addTextfield("speed-PID Kd").setPosition(x, y=y+40).setText(getConfigString("speedPIDKd")).setWidth(40).setAutoClear(false); 35 | cp5.addTextfield("speed-PID Output LowerLimit").setPosition(x, y=y+60).setText(getConfigString("speedPIDOutputLowerLimit")).setWidth(40).setAutoClear(false); 36 | cp5.addTextfield("speed-PID Output HigherLimit").setPosition(x, y=y+40).setText(getConfigString("speedPIDOutputHigherLimit")).setWidth(40).setAutoClear(false); 37 | cp5.addTextfield("speed-PID Sampling").setPosition(x, y=y+40).setText(getConfigString("speedPIDSampling")).setWidth(40).setAutoClear(false); 38 | cp5.addTextfield("motor Speed SensorSampling").setPosition(x, y=y+40).setText(getConfigString("motorSpeedSensorSampling")).setWidth(40).setAutoClear(false); 39 | cp5.addTextfield("speed Kalman FilterR").setPosition(x, y=y+40).setText(getConfigString("speedKalmanFilterR")).setWidth(40).setAutoClear(false); 40 | 41 | // angple PID 42 | x = x+150; 43 | y = 5; 44 | cp5.addTextlabel("Angle PID (outer loop)").setText("Angle PID (outer loop)").setPosition(x, y).setFont(createFont("Georgia", 12)); 45 | x = x + 5; 46 | y = y+20; 47 | cp5.addTextlabel("Conservative").setText("Conservative").setPosition(x, y).setFont(createFont("Georgia", 12)); 48 | x = x + 100; 49 | cp5.addTextlabel("Aggressive").setText("Aggressive").setPosition(x, y).setFont(createFont("Georgia", 12)); 50 | x = x - 95; 51 | y = y+20; 52 | cp5.addTextfield("angle-PID ConKp").setPosition(x, y).setText(getConfigString("anglePIDConKp")).setWidth(40).setAutoClear(false); 53 | x = x + 100; 54 | cp5.addTextfield("angle-PID AggKp").setPosition(x, y).setText(getConfigString("anglePIDAggKp")).setWidth(40).setAutoClear(false); 55 | x = x - 100; 56 | y = y+40; 57 | cp5.addTextfield("angle-PID ConKi").setPosition(x, y).setText(getConfigString("anglePIDConKi")).setWidth(40).setAutoClear(false); 58 | x = x + 100; 59 | cp5.addTextfield("anglePIDAggKi").setPosition(x, y).setText(getConfigString("anglePIDAggKi")).setWidth(40).setAutoClear(false); 60 | x = x - 100; 61 | y = y+40; 62 | cp5.addTextfield("angle-PID ConKd").setPosition(x, y).setText(getConfigString("anglePIDConKd")).setWidth(40).setAutoClear(false); 63 | x = x + 100; 64 | cp5.addTextfield("angle-PID AggKd").setPosition(x, y).setText(getConfigString("anglePIDAggKd")).setWidth(40).setAutoClear(false); 65 | 66 | // angle general 67 | cp5.addTextfield("angle-PID LowerLimit").setPosition(x=x-100, y=y+60).setText(getConfigString("anglePIDLowerLimit")).setWidth(40).setAutoClear(false); 68 | cp5.addTextfield("calibrated Zero Angle").setPosition(x, y=y+40).setText(getConfigString("calibratedZeroAngle")).setWidth(40).setAutoClear(false); 69 | cp5.addTextfield("angle-PID Sampling").setPosition(x, y=y+40).setText(getConfigString("anglePIDSampling")).setWidth(40).setAutoClear(false); 70 | cp5.addTextfield("angle Sensor Sampling").setPosition(x, y=y+40).setText(getConfigString("angleSensorSampling")).setWidth(40).setAutoClear(false); 71 | cp5.addTextfield("angle Kalman FilterR").setPosition(x, y=y+40).setText(getConfigString("angleKalmanFilterR")).setWidth(40).setAutoClear(false); 72 | 73 | // Debug 74 | cp5.addTextlabel("Debug").setText("Debug").setPosition(x=x+200, y=5).setFont(createFont("Georgia", 12)); 75 | cp5.addToggle("debug Level").setPosition(x, y=y+40).setValue(int(getConfigString("debugLevel"))).setMode(ControlP5.SWITCH); 76 | cp5.addTextfield("debug Sample Rate").setPosition(x, y=y+40).setText(getConfigString("debugSampleRate")).setWidth(40).setAutoClear(false); 77 | cp5.addToggle("speed-PID OutputDebug").setPosition(x, y=y+40).setValue(int(getConfigString("speedPIDOutputDebug"))).setMode(ControlP5.SWITCH); 78 | cp5.addToggle("speed-PID InputDebug").setPosition(x, y=y+40).setValue(int(getConfigString("speedPIDInputDebug"))).setMode(ControlP5.SWITCH); 79 | cp5.addToggle("speed Kalman FilterDebug").setPosition(x, y=y+40).setValue(int(getConfigString("speedKalmanFilterDebug"))).setMode(ControlP5.SWITCH); 80 | cp5.addToggle("angle-PID SetpointDebug").setPosition(x, y=y+40).setValue(int(getConfigString("anglePIDSetpointDebug"))).setMode(ControlP5.SWITCH); 81 | cp5.addToggle("angle-PID InputDebug").setPosition(x, y=y+40).setValue(int(getConfigString("anglePIDInputDebug"))).setMode(ControlP5.SWITCH); 82 | cp5.addToggle("angle-PID OutputDebug").setPosition(x, y=y+40).setValue(int(getConfigString("anglePIDOutputDebug"))).setMode(ControlP5.SWITCH); 83 | cp5.addToggle("speed RawDebug").setPosition(x, y=y+40).setValue(int(getConfigString("speedRawDebug"))).setMode(ControlP5.SWITCH); 84 | cp5.addToggle("angle RawDebug").setPosition(x, y=y+40).setValue(int(getConfigString("angleRawDebug"))).setMode(ControlP5.SWITCH); 85 | 86 | PImage[] imgs = { 87 | loadImage(topSketchPath+"/images/button_a.png"), loadImage(topSketchPath+"/images/button_b.png"), loadImage(topSketchPath+"/images/button_c.png") 88 | }; 89 | 90 | x = 200; 91 | cp5.addButton("moveForwards").setValue(1).setPosition(x, y=y+60).setImages(imgs).updateSize(); 92 | cp5.addButton("moveBackwards").setValue(1).setPosition(x, y=y+60).setImages(imgs).updateSize(); 93 | cp5.addButton("turnLeft").setValue(1).setPosition(x=x-60, y).setImages(imgs).updateSize(); 94 | cp5.addButton("turnRight").setValue(1).setPosition(x=x+120, y).setImages(imgs).updateSize(); 95 | 96 | cp5.addButton("start").setValue(1).setPosition(x=x-250, y=y-60); 97 | cp5.addButton("stop1").setValue(1).setPosition(x, y=y+40); // Do not run when setting "stop" 98 | cp5.addButton("calibrate").setValue(1).setPosition(x, y=y+40); 99 | } 100 | 101 | 102 | 103 | void controlEvent(ControlEvent theEvent) { 104 | print(theEvent); 105 | if (theEvent.isAssignableFrom(Textfield.class) || theEvent.isAssignableFrom(Toggle.class) || theEvent.isAssignableFrom(Button.class)) { 106 | String parameter = theEvent.getName(); 107 | String value = ""; 108 | if (theEvent.isAssignableFrom(Textfield.class)) 109 | value = theEvent.getStringValue(); 110 | else if (theEvent.isAssignableFrom(Toggle.class) || theEvent.isAssignableFrom(Button.class)) 111 | value = theEvent.getValue()+""; 112 | 113 | robotConfigJSON.setString(parameter, value); 114 | saveJSONObject(robotConfigJSON, topSketchPath+"/robot_config.json"); 115 | if (!mockupSerial) { 116 | serialPort.write("set "+parameter+" "+value+";"); 117 | serialPort.clear(); 118 | } 119 | print("set "+parameter+" "+value+";\n"); 120 | /*for (int i=0; i 0) { 121 | String myString = ""; 122 | if (!mockupSerial) { 123 | try { 124 | serialPort.readBytesUntil('\r', inBuffer); 125 | } 126 | catch (Exception e) { 127 | } 128 | myString = new String(inBuffer); 129 | } 130 | else { 131 | myString = mockupSerialFunction(); 132 | } 133 | 134 | //println(myString); 135 | 136 | // split the string at delimiter (space) 137 | String[] nums = split(myString, ' '); 138 | 139 | // count number of bars and line graphs to hide 140 | int numberOfInvisibleBars = 0; 141 | for (i=0; i<6; i++) { 142 | if (int(getPlotterConfigString("bcVisible"+(i+1))) == 0) { 143 | numberOfInvisibleBars++; 144 | } 145 | } 146 | int numberOfInvisibleLineGraphs = 0; 147 | for (i=0; i<6; i++) { 148 | if (int(getPlotterConfigString("lgVisible"+(i+1))) == 0) { 149 | numberOfInvisibleLineGraphs++; 150 | } 151 | } 152 | // build a new array to fit the data to show 153 | barChartValues = new float[6-numberOfInvisibleBars]; 154 | 155 | // build the arrays for bar charts and line graphs 156 | int barchartIndex = 0; 157 | for (i=0; i 0) { 128 | String myString = ""; 129 | if (!mockupSerial) { 130 | try { 131 | serialPort.readBytesUntil('\r', inBuffer); 132 | } 133 | catch (Exception e) { 134 | } 135 | myString = new String(inBuffer); 136 | } 137 | else { 138 | myString = mockupSerialFunction(); 139 | } 140 | 141 | //println(myString); 142 | 143 | // split the string at delimiter (space) 144 | String[] nums = split(myString, ' '); 145 | 146 | // count number of bars and line graphs to hide 147 | int numberOfInvisibleBars = 0; 148 | for (i=0; i<6; i++) { 149 | if (int(getPlotterConfigString("bcVisible"+(i+1))) == 0) { 150 | numberOfInvisibleBars++; 151 | } 152 | } 153 | int numberOfInvisibleLineGraphs = 0; 154 | for (i=0; i<6; i++) { 155 | if (int(getPlotterConfigString("lgVisible"+(i+1))) == 0) { 156 | numberOfInvisibleLineGraphs++; 157 | } 158 | } 159 | // build a new array to fit the data to show 160 | barChartValues = new float[6-numberOfInvisibleBars]; 161 | 162 | // build the arrays for bar charts and line graphs 163 | int barchartIndex = 0; 164 | for (i=0; i Make sure time array doesn't decrease (go back in time) 270 | ===========================================================================*/ 271 | if(ix[i+1]){ 273 | 274 | ErrorFlag=true; 275 | 276 | } 277 | } 278 | 279 | /* ================================================================================= 280 | First and last bits can't be part of the curve, no points before first bit, 281 | none after last bit. So a streight line is drawn instead 282 | ================================================================================= */ 283 | 284 | if(i==0 || i==x.length-2)line(xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width, 285 | yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height, 286 | xPos+(x[i+1]-x[0])/(x[x.length-1]-x[0])*Width, 287 | yPos+Height-(y[i+1]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height); 288 | 289 | /* ================================================================================= 290 | For the rest of the array a curve (spline curve) can be created making the graph 291 | smooth. 292 | ================================================================================= */ 293 | 294 | curveVertex( xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width, 295 | yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height); 296 | 297 | /* ================================================================================= 298 | If the Dot option is true, Place a dot at each data point. 299 | ================================================================================= */ 300 | 301 | if(Dot)ellipse( 302 | xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width, 303 | yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height, 304 | 2,2 305 | ); 306 | 307 | /* ================================================================================= 308 | Highlights points closest to Mouse X position 309 | =================================================================================*/ 310 | 311 | if( abs(mouseX-(xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width))<5 ){ 312 | 313 | 314 | float yLinePosition = yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height; 315 | float xLinePosition = xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width; 316 | strokeWeight(1);stroke(240); 317 | // line(xPos,yLinePosition,xPos+Width,yLinePosition); 318 | strokeWeight(2);stroke(GraphColor); 319 | 320 | ellipse(xLinePosition,yLinePosition,4,4); 321 | } 322 | 323 | 324 | 325 | } 326 | 327 | endShape(); 328 | yMax=tempyMax; yMin=tempyMin; 329 | float xAxisTitleWidth=textWidth(str(map(xlocation,xPos,xPos+Width,x[0],x[x.length-1]))); 330 | 331 | 332 | if((mouseX>xPos&mouseX<(xPos+Width))&(mouseY>yPos&mouseY<(yPos+Height))){ 333 | if(ShowMouseLines){ 334 | // if(mouseXxPos+Width)xlocation=xPos+Width; 336 | else xlocation=mouseX; 337 | stroke(200); strokeWeight(0.5);fill(255);color(50); 338 | // Rectangle and x position 339 | line(xlocation,yPos,xlocation,yPos+Height); 340 | rect(xlocation-xAxisTitleWidth/2-10,yPos+Height-16,xAxisTitleWidth+20,12); 341 | 342 | textAlign(CENTER); fill(160); 343 | text(map(xlocation,xPos,xPos+Width,x[0],x[x.length-1]),xlocation,yPos+Height-6); 344 | 345 | // if(mouseYyPos+Height)ylocation=yPos+Height; 347 | else ylocation=mouseY; 348 | 349 | // Rectangle and y position 350 | stroke(200); strokeWeight(0.5);fill(255);color(50); 351 | 352 | line(xPos,ylocation,xPos+Width,ylocation); 353 | int yAxisTitleWidth=int(textWidth(str(map(ylocation,yPos,yPos+Height,y[0],y[y.length-1]))) ); 354 | rect(xPos-15+3,ylocation-6, -60 ,12); 355 | 356 | textAlign(RIGHT); fill(GraphColor);//StrokeColor 357 | // text(map(ylocation,yPos+Height,yPos,yMin,yMax),xPos+Width+3,yPos+Height+4); 358 | text(map(ylocation,yPos+Height,yPos,yMin,yMax),xPos -15,ylocation+4); 359 | if(RightAxis){ 360 | 361 | stroke(200); strokeWeight(0.5);fill(255);color(50); 362 | 363 | rect(xPos+Width+15-3,ylocation-6, 60 ,12); 364 | textAlign(LEFT); fill(160); 365 | text(map(ylocation,yPos+Height,yPos,yMinRight,yMaxRight),xPos+Width+15,ylocation+4); 366 | } 367 | noStroke();noFill(); 368 | } 369 | } 370 | 371 | 372 | } 373 | 374 | 375 | void smoothLine(float[] x ,float[] y, float[] z, float[] a ) { 376 | GraphColor=color(188,53,53); 377 | smoothLine(x ,y); 378 | GraphColor=color(193-100,216-100,16); 379 | smoothLine(z ,a); 380 | 381 | } 382 | 383 | 384 | 385 | } 386 | 387 | 388 | -------------------------------------------------------------------------------- /RealtimePlotterWithControlPanel/GraphClass.pde: -------------------------------------------------------------------------------- 1 | 2 | /* ================================================================================= 3 | The Graph class contains functions and variables that have been created to draw 4 | graphs. Here is a quick list of functions within the graph class: 5 | 6 | Graph(int x, int y, int w, int h,color k) 7 | DrawAxis() 8 | Bar([]) 9 | smoothLine([][]) 10 | DotGraph([][]) 11 | LineGraph([][]) 12 | 13 | =================================================================================*/ 14 | 15 | 16 | class Graph 17 | { 18 | 19 | boolean Dot=true; // Draw dots at each data point if true 20 | boolean RightAxis; // Draw the next graph using the right axis if true 21 | boolean ErrorFlag=false; // If the time array isn't in ascending order, make true 22 | boolean ShowMouseLines=true; // Draw lines and give values of the mouse position 23 | 24 | int xDiv=5,yDiv=5; // Number of sub divisions 25 | int xPos,yPos; // location of the top left corner of the graph 26 | int Width,Height; // Width and height of the graph 27 | 28 | 29 | color GraphColor; 30 | color BackgroundColor=color(255); 31 | color StrokeColor=color(180); 32 | 33 | String Title="Title"; // Default titles 34 | String xLabel="x - Label"; 35 | String yLabel="y - Label"; 36 | 37 | float yMax=1024, yMin=0; // Default axis dimensions 38 | float xMax=10, xMin=0; 39 | float yMaxRight=1024,yMinRight=0; 40 | 41 | PFont Font; // Selected font used for text 42 | 43 | // int Peakcounter=0,nPeakcounter=0; 44 | 45 | Graph(int x, int y, int w, int h,color k) { // The main declaration function 46 | xPos = x; 47 | yPos = y; 48 | Width = w; 49 | Height = h; 50 | GraphColor = k; 51 | 52 | } 53 | 54 | 55 | void DrawAxis(){ 56 | 57 | /* ========================================================================================= 58 | Main axes Lines, Graph Labels, Graph Background 59 | ========================================================================================== */ 60 | 61 | fill(BackgroundColor); color(0);stroke(StrokeColor);strokeWeight(1); 62 | int t=60; 63 | 64 | rect(xPos-t*1.6,yPos-t,Width+t*2.5,Height+t*2); // outline 65 | textAlign(CENTER);textSize(18); 66 | float c=textWidth(Title); 67 | fill(BackgroundColor); color(0);stroke(0);strokeWeight(1); 68 | rect(xPos+Width/2-c/2,yPos-35,c,0); // Heading Rectangle 69 | 70 | fill(0); 71 | text(Title,xPos+Width/2,yPos-37); // Heading Title 72 | textAlign(CENTER);textSize(14); 73 | text(xLabel,xPos+Width/2,yPos+Height+t/1.5); // x-axis Label 74 | 75 | rotate(-PI/2); // rotate -90 degrees 76 | text(yLabel,-yPos-Height/2,xPos-t*1.6+20); // y-axis Label 77 | rotate(PI/2); // rotate back 78 | 79 | textSize(10); noFill(); stroke(0); smooth();strokeWeight(1); 80 | //Edges 81 | line(xPos-3,yPos+Height,xPos-3,yPos); // y-axis line 82 | line(xPos-3,yPos+Height,xPos+Width+5,yPos+Height); // x-axis line 83 | 84 | stroke(200); 85 | if(yMin<0){ 86 | line(xPos-7, // zero line 87 | yPos+Height-(abs(yMin)/(yMax-yMin))*Height, // 88 | xPos+Width, 89 | yPos+Height-(abs(yMin)/(yMax-yMin))*Height 90 | ); 91 | 92 | 93 | } 94 | 95 | if(RightAxis){ // Right-axis line 96 | stroke(0); 97 | line(xPos+Width+3,yPos+Height,xPos+Width+3,yPos); 98 | } 99 | 100 | /* ========================================================================================= 101 | Sub-devisions for both axes, left and right 102 | ========================================================================================== */ 103 | 104 | stroke(0); 105 | 106 | for(int x=0; x<=xDiv; x++){ 107 | 108 | /* ========================================================================================= 109 | x-axis 110 | ========================================================================================== */ 111 | 112 | line(float(x)/xDiv*Width+xPos-3,yPos+Height, // x-axis Sub devisions 113 | float(x)/xDiv*Width+xPos-3,yPos+Height+5); 114 | 115 | textSize(10); // x-axis Labels 116 | String xAxis=str(xMin+float(x)/xDiv*(xMax-xMin)); // the only way to get a specific number of decimals 117 | String[] xAxisMS=split(xAxis,'.'); // is to split the float into strings 118 | text(xAxisMS[0]+"."+xAxisMS[1].charAt(0), // ... 119 | float(x)/xDiv*Width+xPos-3,yPos+Height+15); // x-axis Labels 120 | } 121 | 122 | 123 | /* ========================================================================================= 124 | left y-axis 125 | ========================================================================================== */ 126 | 127 | for(int y=0; y<=yDiv; y++){ 128 | line(xPos-3,float(y)/yDiv*Height+yPos, // ... 129 | xPos-7,float(y)/yDiv*Height+yPos); // y-axis lines 130 | 131 | textAlign(RIGHT);fill(20); 132 | 133 | String yAxis=str(yMin+float(y)/yDiv*(yMax-yMin)); // Make y Label a string 134 | String[] yAxisMS=split(yAxis,'.'); // Split string 135 | 136 | text(yAxisMS[0]+"."+yAxisMS[1].charAt(0), // ... 137 | xPos-15,float(yDiv-y)/yDiv*Height+yPos+3); // y-axis Labels 138 | 139 | 140 | /* ========================================================================================= 141 | right y-axis 142 | ========================================================================================== */ 143 | 144 | if(RightAxis){ 145 | 146 | color(GraphColor); stroke(GraphColor);fill(20); 147 | 148 | line(xPos+Width+3,float(y)/yDiv*Height+yPos, // ... 149 | xPos+Width+7,float(y)/yDiv*Height+yPos); // Right Y axis sub devisions 150 | 151 | textAlign(LEFT); 152 | 153 | String yAxisRight=str(yMinRight+float(y)/ // ... 154 | yDiv*(yMaxRight-yMinRight)); // convert axis values into string 155 | String[] yAxisRightMS=split(yAxisRight,'.'); // 156 | 157 | text(yAxisRightMS[0]+"."+yAxisRightMS[1].charAt(0), // Right Y axis text 158 | xPos+Width+15,float(yDiv-y)/yDiv*Height+yPos+3); // it's x,y location 159 | 160 | noFill(); 161 | }stroke(0); 162 | 163 | 164 | } 165 | 166 | 167 | } 168 | 169 | 170 | /* ========================================================================================= 171 | Bar graph 172 | ========================================================================================== */ 173 | 174 | void Bar(float[] a ,int from, int to) { 175 | 176 | 177 | stroke(GraphColor); 178 | fill(GraphColor); 179 | 180 | if(from<0){ // If the From or To value is out of bounds 181 | for (int x=0; x Make sure time array doesn't decrease (go back in time) 270 | ===========================================================================*/ 271 | if(ix[i+1]){ 273 | 274 | ErrorFlag=true; 275 | 276 | } 277 | } 278 | 279 | /* ================================================================================= 280 | First and last bits can't be part of the curve, no points before first bit, 281 | none after last bit. So a streight line is drawn instead 282 | ================================================================================= */ 283 | 284 | if(i==0 || i==x.length-2)line(xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width, 285 | yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height, 286 | xPos+(x[i+1]-x[0])/(x[x.length-1]-x[0])*Width, 287 | yPos+Height-(y[i+1]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height); 288 | 289 | /* ================================================================================= 290 | For the rest of the array a curve (spline curve) can be created making the graph 291 | smooth. 292 | ================================================================================= */ 293 | 294 | curveVertex( xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width, 295 | yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height); 296 | 297 | /* ================================================================================= 298 | If the Dot option is true, Place a dot at each data point. 299 | ================================================================================= */ 300 | 301 | if(Dot)ellipse( 302 | xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width, 303 | yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height, 304 | 2,2 305 | ); 306 | 307 | /* ================================================================================= 308 | Highlights points closest to Mouse X position 309 | =================================================================================*/ 310 | 311 | if( abs(mouseX-(xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width))<5 ){ 312 | 313 | 314 | float yLinePosition = yPos+Height-(y[i]/(yMax-yMin)*Height)+(yMin)/(yMax-yMin)*Height; 315 | float xLinePosition = xPos+(x[i]-x[0])/(x[x.length-1]-x[0])*Width; 316 | strokeWeight(1);stroke(240); 317 | // line(xPos,yLinePosition,xPos+Width,yLinePosition); 318 | strokeWeight(2);stroke(GraphColor); 319 | 320 | ellipse(xLinePosition,yLinePosition,4,4); 321 | } 322 | 323 | 324 | 325 | } 326 | 327 | endShape(); 328 | yMax=tempyMax; yMin=tempyMin; 329 | float xAxisTitleWidth=textWidth(str(map(xlocation,xPos,xPos+Width,x[0],x[x.length-1]))); 330 | 331 | 332 | if((mouseX>xPos&mouseX<(xPos+Width))&(mouseY>yPos&mouseY<(yPos+Height))){ 333 | if(ShowMouseLines){ 334 | // if(mouseXxPos+Width)xlocation=xPos+Width; 336 | else xlocation=mouseX; 337 | stroke(200); strokeWeight(0.5);fill(255);color(50); 338 | // Rectangle and x position 339 | line(xlocation,yPos,xlocation,yPos+Height); 340 | rect(xlocation-xAxisTitleWidth/2-10,yPos+Height-16,xAxisTitleWidth+20,12); 341 | 342 | textAlign(CENTER); fill(160); 343 | text(map(xlocation,xPos,xPos+Width,x[0],x[x.length-1]),xlocation,yPos+Height-6); 344 | 345 | // if(mouseYyPos+Height)ylocation=yPos+Height; 347 | else ylocation=mouseY; 348 | 349 | // Rectangle and y position 350 | stroke(200); strokeWeight(0.5);fill(255);color(50); 351 | 352 | line(xPos,ylocation,xPos+Width,ylocation); 353 | int yAxisTitleWidth=int(textWidth(str(map(ylocation,yPos,yPos+Height,y[0],y[y.length-1]))) ); 354 | rect(xPos-15+3,ylocation-6, -60 ,12); 355 | 356 | textAlign(RIGHT); fill(GraphColor);//StrokeColor 357 | // text(map(ylocation,yPos+Height,yPos,yMin,yMax),xPos+Width+3,yPos+Height+4); 358 | text(map(ylocation,yPos+Height,yPos,yMin,yMax),xPos -15,ylocation+4); 359 | if(RightAxis){ 360 | 361 | stroke(200); strokeWeight(0.5);fill(255);color(50); 362 | 363 | rect(xPos+Width+15-3,ylocation-6, 60 ,12); 364 | textAlign(LEFT); fill(160); 365 | text(map(ylocation,yPos+Height,yPos,yMinRight,yMaxRight),xPos+Width+15,ylocation+4); 366 | } 367 | noStroke();noFill(); 368 | } 369 | } 370 | 371 | 372 | } 373 | 374 | 375 | void smoothLine(float[] x ,float[] y, float[] z, float[] a ) { 376 | GraphColor=color(188,53,53); 377 | smoothLine(x ,y); 378 | GraphColor=color(193-100,216-100,16); 379 | smoothLine(z ,a); 380 | 381 | } 382 | 383 | 384 | 385 | } 386 | 387 | 388 | --------------------------------------------------------------------------------