graph_plotter_rewrite.py 6.29 KB
#!/usr/bin/env python

# Tool to draw live graphs of data coming in from serial port
# Written as a telemetry tool by:
# The UoN Robot Wars Project, 2018

# This code is incomplete, and is missing core features

import pyglet
#import math
#import time
import serial
import numpy

from colours import *

datafeed = serial.Serial(
port='/dev/ttyUSB0',
baudrate = 9600,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)

class Series:
    def __init__(self, points=100, title="Series title", xname="x-axis name", yname="y-axis name"):
        """Set up an object to store a 2D data series"""
        self.title = title
        self.xname = xname
        self.yname = yname
        self.xlimits = (100, 0)
        self.ylimits = (0, 255)
        self.data = []
        self.points = points
    def addpoint(self, point):
        """Add a point to the dataset, and remove the oldest, if necessary"""
        self.data.append(point)
        if len(self.data) > self.points:
            del self.data[0]

class Plot(pyglet.window.Window):
    def __init__(self, series):
        """Setup a the details of a plot, and create a corresponding window"""
        pyglet.window.Window.__init__(self, resizable=True)
        self.series = series
        self.font = 'Arkhip'
        self.margins = (0.1, 0.08) # Fractions of window size
        self.lines = (10, 8)
        #self.resizable = True
        self.set_caption(self.series.title)
        
    def on_resize(self, width, height):
        """Handle a resize event from the pyglet event loop"""
        self.bounds = ((int(self.width * self.margins[0]), int(self.width * (1 - self.margins[0]))),
                (int(self.height * self.margins[1]), int(self.height * (1 - self.margins[1]))))
        pyglet.window.Window.on_resize(self, width, height)
        
    def on_draw(self):
        """Draw all the components of the graph"""
        self.drawBackground()
        self.drawHeading()
        self.drawAxis(0)
        self.drawAxis(1)
        
    def drawBackground(self):
        """Draw the graph background, currently a plain colour"""
        pyglet.image.SolidColorImagePattern(WHITE).create_image(self.width, self.height).blit(0, 0)
        
    def drawHeading(self):
        """Draw a title for the graph (duplicated in the window titlebar, if present"""
        heading = pyglet.text.Label(self.series.title, color=BLACK,
                            font_name=self.font, font_size=self.height*self.margins[0]*0.5,
                            x=self.width/2, y=self.height-(self.margins[1]),
                            anchor_x='center', anchor_y='top')
        heading.draw()
     
    def drawAxis(self, axis): # axis=0 is x, 1 is y
        """Draw the gridlines and labels for one axis, specified in the last argument"""
        limita = self.bounds[1-axis][1]
        limitb = self.bounds[1-axis][0]
        start = self.bounds[axis][0]
        stop = self.bounds[axis][1]
        increment = float(stop-start)/self.lines[axis]
        for pos in numpy.arange(start, stop+1, increment):
            # Using fp arithmetic to avoid intermittent fencepost errors
            pos = int(pos)
            if axis==0:   # x axis, vertical lines
                scale = float(self.series.xlimits[1]-self.series.xlimits[0])/(stop-start)
                tagvalue = ((pos-start) * scale) + self.series.xlimits[0]
                tagtext = str(round(tagvalue, 1))
                pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (pos, limita, pos, limitb)),
                                 ('c3B', (0, 0, 0, 0, 0, 0)))
                tag = pyglet.text.Label(tagtext, color=BLACK,
                                        font_name=self.font, font_size=self.height*self.margins[1-axis]*0.28,
                                        x=pos, y=self.height*self.margins[1-axis],
                                        anchor_x='left', anchor_y='top')
                axistitle = pyglet.text.Label(self.series.xname, color=BLACK,
                                                font_name=self.font, font_size=self.height*self.margins[axis]*0.3,
                                                x=self.width/2, y=0,
                                                anchor_x='center', anchor_y='bottom')
                axistitle.draw()
            if axis==1:  # y axis, horizontal lines
                scale = float(self.series.ylimits[1]-self.series.ylimits[0])/(stop-start)
                tagvalue = ((pos-start) * scale) + self.series.ylimits[0]
                tagtext = str(round(tagvalue, 1))
                pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (limita, pos, limitb, pos)),
                                 ('c3B', (0, 0, 0, 0, 0, 0)))
                tag = pyglet.text.Label(tagtext, color=BLACK,
                                        font_name=self.font, font_size=self.width*self.margins[axis]*0.22,
                                        x=self.width*self.margins[1-axis]*0.9, y=pos,
                                        anchor_x='right', anchor_y='center')
                axistitle = pyglet.text.Label(self.series.yname, color=BLACK,
                                                font_name=self.font, font_size=self.height*self.margins[axis]*0.3,
                                                x=0, y=self.height/2,
                                                anchor_x='center', anchor_y='top')
                pyglet.gl.glPushMatrix()   # Set up a new context to avoid confusing the main one
                # Tranformation to rotate label, and ensure it ends up in the right place
                pyglet.gl.glTranslatef(self.height//2, self.height//2, 0.0)
                pyglet.gl.glRotatef(90.0, 0.0, 0.0, 1.0)
                # Draw the axis title using the rotated coordinate system
                axistitle.draw()
                # Return everything to its previous state
                pyglet.gl.glPopMatrix()

            tag.draw()
        
testseries = Series()

plots = []         
plots.append(Plot(testseries))

def pollSerial(elapsed):
    """Check serial port for incoming data"""
    # Note: 'elapsed' is time since last call of this function
    values = datafeed.readline().strip().split(", ")
    testseries.addpoint(values)

# Pyglet looks after the main event loop, but this ensures that data keeps being read in
pyglet.clock.schedule_interval(pollSerial, 0.1)

pyglet.app.run()