#!/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()