Compare View

switch
from
...
to
 
robots/little_john/telemetry/code/monitor/version1/main.py
... ... @@ -21,6 +21,8 @@ import logging
21 21  
22 22 from serialselect import selectserial
23 23 from colours import *
  24 +from series import Series
  25 +from plot import Plot
24 26  
25 27 logging.basicConfig(format='%(levelname)s:\t%(message)s', level=logging.DEBUG)
26 28 logging.info("Logging system active")
... ... @@ -38,171 +40,7 @@ else:
38 40 os='Other'
39 41 logging.info('OS = ' + os)
40 42  
41   -class Series:
42   - def __init__(self, points=300, title="Series title", xname="x-axis name", yname="y-axis name"):
43   - """Set up an object to store a 2D data series"""
44   - # Proposal:
45   - # In order to neatly handle multiple lines on the same graph
46   - # a series is not so much a set of 2D data points, but a number
47   - # of series sharing an x axis. For example
48   - # (time, temperature)
49   - # (time, xaccel, yaccel, zaccel)
50   - # The latter seta of points much be of like nature and share an axis
51   - # (time, temperature, xaccel)
52   - # would be a meaningless thing to plot on a single graph anyway
53   -
54   - self.title = title
55   - self.xname = xname
56   - self.yname = yname
57   - self.xlimits = (0, 100)
58   - self.ylimits = (-100, 100)
59   - self.data = []
60   - self.points = points
61   -
62   - def addpoint(self, point):
63   - """Add a point to the dataset, and remove the oldest, if necessary"""
64   - self.data.append(point)
65   - if len(self.data) > self.points:
66   - del self.data[0]
67   - self.autoscale(0)
68   -
69   - def autoscale(self, axis): # axis=0 is x, 1 is y
70   - minval = self.data[0][axis]
71   - maxval = self.data[0][axis]
72   - for value in self.data:
73   - if value[axis] < minval:
74   - minval = value[axis]
75   - if value[axis] > maxval:
76   - maxval = value[axis]
77   - if axis == 0:
78   - self.xlimits = (minval, maxval)
79   - else:
80   - self.ylimits = (minval, maxval)
81   -
82   -class Plot(pyglet.window.Window):
83   - def __init__(self, series):
84   - """Setup a the details of a plot, and create a corresponding window"""
85   - pyglet.window.Window.__init__(self, resizable=True)
86   - self.set_icon(pyglet.image.load('32x32.png'))
87   - self.set_minimum_size(320,320)
88   - self.series = series
89   - self.font = 'Arkhip'
90   - self.margins = (0.09, 0.08) # Fractions of window size
91   - self.lines = (10, 8)
92   - #self.resizable = True
93   - self.set_caption(self.series.title)
94   -
95   - def on_resize(self, width, height):
96   - """Handle a resize event from the pyglet event loop"""
97   - try:
98   - self.bounds = ((int(self.width * self.margins[0]), int(self.width * (1 - self.margins[0]))),
99   - (int(self.height * self.margins[1]), int(self.height * (1 - self.margins[1]))))
100   - except Exception as e:
101   - logging.critical(str(e))
102   - self.close()
103   - logging.critical('Instance closed')
104   - sys.exit()
105   - self.tag_size = min(self.height*self.margins[1]*0.3,self.width*self.margins[0]*0.3)
106   - # This sometimes seems to throw an error ('AttributeError: 'Plot' object has no attribute 'margins') when started for a second time from the same instance. Interesting. Causes the plot windows to freeze
107   - pyglet.window.Window.on_resize(self, width, height)
108   -
109   - def on_draw(self):
110   - """Draw all the components of the graph"""
111   - self.drawBackground()
112   - self.drawHeading()
113   - self.drawAxis(0)
114   - self.drawAxis(1)
115   - self.drawLine(self.series)
116   -
117   - def drawBackground(self):
118   - """Draw the graph background, currently a plain colour"""
119   - pyglet.image.SolidColorImagePattern(WHITE).create_image(self.width, self.height).blit(0, 0)
120   -
121   - def drawHeading(self):
122   - """Draw a title for the graph (duplicated in the window titlebar, if present"""
123   - heading = pyglet.text.Label(self.series.title, color=BLACK,
124   - font_name=self.font, font_size=self.height*self.margins[0]*0.5,
125   - x=self.width/2, y=self.height-(self.margins[1]),
126   - anchor_x='center', anchor_y='top')
127   - heading.draw()
128   -
129   - def drawLine(self, series):
130   - xscale = float(self.series.xlimits[1]-self.series.xlimits[0])/(self.bounds[0][1]-self.bounds[0][0])
131   - yscale = float(self.series.ylimits[1]-self.series.ylimits[0])/(self.bounds[1][1]-self.bounds[1][0])
132   - logging.debug("xscale = " + str(xscale) + ", yscale = " + str(yscale))
133   - lmar = int(self.width * self.margins[0])
134   - rmar = int(self.width * self.margins[1])
135   - tmar = int(self.height * self.margins[0])
136   - bmar = int(self.height * self.margins[1])
137   -
138   - pyglet.gl.glLineWidth(2)
139   -
140   - for n in range(len(series.data) - 1):
141   - x1, y1 = series.data[n][0]-series.xlimits[0], series.data[n][1]-series.ylimits[0]
142   - x2, y2 = series.data[n+1][0]-series.xlimits[0], series.data[n+1][1]-series.ylimits[0]
143   - x1 = int((x1/xscale)+lmar)
144   - y1 = int((y1/yscale)+bmar)
145   - x2 = int((x2/xscale)+lmar)
146   - y2 = int((y2/yscale)+bmar)
147   - pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
148   - ('v2i', (x1, y1, x2, y2)),
149   - ('c3B', (255, 0, 0, 255, 0, 0)))
150   - pyglet.gl.glLineWidth(1)
151   -
152   -
153   -
154   - def drawAxis(self, axis): # axis=0 is x, 1 is y
155   - """Draw the gridlines and labels for one axis, specified in the last argument"""
156   - limita = self.bounds[1-axis][1]
157   - limitb = self.bounds[1-axis][0]
158   - start = self.bounds[axis][0]
159   - stop = self.bounds[axis][1]
160   - increment = float(stop-start)/self.lines[axis]
161   - for pos in numpy.arange(start, stop+1, increment):
162   - # Using fp arithmetic to avoid intermittent fencepost errors
163   - pos = int(pos)
164   - if axis==0: # x axis, vertical lines
165   - scale = float(self.series.xlimits[1]-self.series.xlimits[0])/(stop-start)
166   - tagvalue = ((pos-start) * scale) + self.series.xlimits[0]
167   - tagtext = str(int(tagvalue))
168   - pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (pos, limita, pos, limitb)),
169   - ('c3B', (0, 0, 0, 0, 0, 0)))
170   - tag = pyglet.text.Label(tagtext, color=BLACK,
171   - font_name=self.font, font_size=self.tag_size,
172   - x=pos, y=self.height*self.margins[1],
173   - anchor_x='left', anchor_y='top')
174   - axistitle = pyglet.text.Label(self.series.xname, color=BLACK,
175   - font_name=self.font, font_size=self.tag_size,
176   - x=self.width/2, y=0,
177   - anchor_x='center', anchor_y='bottom')
178   - axistitle.draw()
179   - if axis==1: # y axis, horizontal lines
180   - scale = float(self.series.ylimits[1]-self.series.ylimits[0])/(stop-start)
181   - tagvalue = ((pos-start) * scale) + self.series.ylimits[0]
182   - tagtext = str(int(tagvalue))
183   - pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (limita, pos, limitb, pos)),
184   - ('c3B', (0, 0, 0, 0, 0, 0)))
185   - tag = pyglet.text.Label(tagtext, color=BLACK,
186   - font_name=self.font, font_size=self.tag_size,
187   - x=self.width*self.margins[0]*0.9, y=pos,
188   - anchor_x='right', anchor_y='center')
189   - axistitle = pyglet.text.Label(self.series.yname, color=BLACK,
190   - font_name=self.font, font_size=self.tag_size,
191   - x=0, y=self.height/2,
192   - anchor_x='center', anchor_y='top')
193   - pyglet.gl.glPushMatrix() # Set up a new context to avoid confusing the main one
194   - # Tranformation to rotate label, and ensure it ends up in the right place
195   - pyglet.gl.glTranslatef(self.height//2, self.height//2, 0.0)
196   - pyglet.gl.glRotatef(90.0, 0.0, 0.0, 1.0)
197   - # Draw the axis title using the rotated coordinate system
198   - axistitle.draw()
199   - # Return everything to its previous state
200   - pyglet.gl.glPopMatrix()
201   -
202   - tag.draw()
203   -
204   -testseries = Series()
205   -logging.info("Series created")
  43 +testseries = Series(points=150, title="Sine wave demo", xname="Time (s)", yname="100sin(t)")
206 44 plots = []
207 45 plots.append(Plot(testseries))
208 46  
... ... @@ -223,7 +61,7 @@ def pollSerial(elapsed):
223 61 def fakePollSerial(elapsed):
224 62 """This function immitates the behaviour of pollSerial, for testing purposes"""
225 63 timefromstart = (time.time()-starttime)
226   - values = [timefromstart, 100*math.sin(25*math.radians(timefromstart))]
  64 + values = [timefromstart, 100*math.sin(timefromstart)]
227 65 #logging.info("Generated test data: " + str(values))
228 66 testseries.addpoint(values)
229 67  
... ...
robots/little_john/telemetry/code/monitor/version1/plot.py 0 → 100644
... ... @@ -0,0 +1,129 @@
  1 +# Class representing a single 2D plot window and its properties
  2 +# Written as a telemetry tool by:
  3 +# The UoN Robot Wars Project, 2018
  4 +
  5 +import pyglet
  6 +import numpy
  7 +import logging
  8 +from colours import *
  9 +
  10 +class Plot(pyglet.window.Window):
  11 + def __init__(self, series):
  12 + """Setup a the details of a plot, and create a corresponding window"""
  13 + pyglet.window.Window.__init__(self, resizable=True)
  14 + self.set_icon(pyglet.image.load('32x32.png'))
  15 + self.set_minimum_size(320,320)
  16 + self.series = series
  17 + self.font = 'Arkhip'
  18 + self.margins = (0.09, 0.08) # Fractions of window size
  19 + self.lines = (10, 8)
  20 + #self.resizable = True
  21 + self.set_caption(self.series.title)
  22 +
  23 + def on_resize(self, width, height):
  24 + """Handle a resize event from the pyglet event loop"""
  25 + try:
  26 + self.bounds = ((int(self.width * self.margins[0]), int(self.width * (1 - self.margins[0]))),
  27 + (int(self.height * self.margins[1]), int(self.height * (1 - self.margins[1]))))
  28 + except Exception as e:
  29 + logging.critical(str(e))
  30 + self.close()
  31 + logging.critical('Instance closed')
  32 + sys.exit()
  33 + self.tag_size = min(self.height*self.margins[1]*0.3,self.width*self.margins[0]*0.3)
  34 + # This sometimes seems to throw an error ('AttributeError: 'Plot' object has no attribute 'margins') when started for a second time from the same instance. Interesting. Causes the plot windows to freeze
  35 + pyglet.window.Window.on_resize(self, width, height)
  36 +
  37 + def on_draw(self):
  38 + """Draw all the components of the graph"""
  39 + self.drawBackground()
  40 + self.drawHeading()
  41 + self.drawAxis(0)
  42 + self.drawAxis(1)
  43 + self.drawLine(self.series)
  44 +
  45 + def drawBackground(self):
  46 + """Draw the graph background, currently a plain colour"""
  47 + pyglet.image.SolidColorImagePattern(WHITE).create_image(self.width, self.height).blit(0, 0)
  48 +
  49 + def drawHeading(self):
  50 + """Draw a title for the graph (duplicated in the window titlebar, if present"""
  51 + heading = pyglet.text.Label(self.series.title, color=BLACK,
  52 + font_name=self.font, font_size=self.height*self.margins[0]*0.5,
  53 + x=self.width/2, y=self.height-(self.margins[1]),
  54 + anchor_x='center', anchor_y='top')
  55 + heading.draw()
  56 +
  57 + def drawLine(self, series):
  58 + xscale = float(self.series.xlimits[1]-self.series.xlimits[0])/(self.bounds[0][1]-self.bounds[0][0])
  59 + yscale = float(self.series.ylimits[1]-self.series.ylimits[0])/(self.bounds[1][1]-self.bounds[1][0])
  60 + lmar = int(self.width * self.margins[0])
  61 + rmar = int(self.width * self.margins[1])
  62 + tmar = int(self.height * self.margins[0])
  63 + bmar = int(self.height * self.margins[1])
  64 +
  65 + pyglet.gl.glLineWidth(2)
  66 +
  67 + for n in range(len(series.data) - 1):
  68 + x1, y1 = series.data[n][0]-series.xlimits[0], series.data[n][1]-series.ylimits[0]
  69 + x2, y2 = series.data[n+1][0]-series.xlimits[0], series.data[n+1][1]-series.ylimits[0]
  70 + x1 = int((x1/xscale)+lmar)
  71 + y1 = int((y1/yscale)+bmar)
  72 + x2 = int((x2/xscale)+lmar)
  73 + y2 = int((y2/yscale)+bmar)
  74 + pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
  75 + ('v2i', (x1, y1, x2, y2)),
  76 + ('c3B', (255, 0, 0, 255, 0, 0)))
  77 + pyglet.gl.glLineWidth(1)
  78 +
  79 +
  80 +
  81 + def drawAxis(self, axis): # axis=0 is x, 1 is y
  82 + """Draw the gridlines and labels for one axis, specified in the last argument"""
  83 + limita = self.bounds[1-axis][1]
  84 + limitb = self.bounds[1-axis][0]
  85 + start = self.bounds[axis][0]
  86 + stop = self.bounds[axis][1]
  87 + increment = float(stop-start)/self.lines[axis]
  88 + for pos in numpy.arange(start, stop+1, increment):
  89 + # Using fp arithmetic to avoid intermittent fencepost errors
  90 + pos = int(pos)
  91 + if axis==0: # x axis, vertical lines
  92 + scale = float(self.series.xlimits[1]-self.series.xlimits[0])/(stop-start)
  93 + tagvalue = ((pos-start) * scale) + self.series.xlimits[0]
  94 + tagtext = str(int(tagvalue))
  95 + pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (pos, limita, pos, limitb)),
  96 + ('c3B', (0, 0, 0, 0, 0, 0)))
  97 + tag = pyglet.text.Label(tagtext, color=BLACK,
  98 + font_name=self.font, font_size=self.tag_size,
  99 + x=pos, y=self.height*self.margins[1],
  100 + anchor_x='left', anchor_y='top')
  101 + axistitle = pyglet.text.Label(self.series.xname, color=BLACK,
  102 + font_name=self.font, font_size=self.tag_size,
  103 + x=self.width/2, y=0,
  104 + anchor_x='center', anchor_y='bottom')
  105 + axistitle.draw()
  106 + if axis==1: # y axis, horizontal lines
  107 + scale = float(self.series.ylimits[1]-self.series.ylimits[0])/(stop-start)
  108 + tagvalue = ((pos-start) * scale) + self.series.ylimits[0]
  109 + tagtext = str(int(tagvalue))
  110 + pyglet.graphics.draw(2, pyglet.gl.GL_LINES, ('v2i', (limita, pos, limitb, pos)),
  111 + ('c3B', (0, 0, 0, 0, 0, 0)))
  112 + tag = pyglet.text.Label(tagtext, color=BLACK,
  113 + font_name=self.font, font_size=self.tag_size,
  114 + x=self.width*self.margins[0]*0.9, y=pos,
  115 + anchor_x='right', anchor_y='center')
  116 + axistitle = pyglet.text.Label(self.series.yname, color=BLACK,
  117 + font_name=self.font, font_size=self.tag_size,
  118 + x=0, y=self.height/2,
  119 + anchor_x='center', anchor_y='top')
  120 + pyglet.gl.glPushMatrix() # Set up a new context to avoid confusing the main one
  121 + # Tranformation to rotate label, and ensure it ends up in the right place
  122 + pyglet.gl.glTranslatef(self.height//2, self.height//2, 0.0)
  123 + pyglet.gl.glRotatef(90.0, 0.0, 0.0, 1.0)
  124 + # Draw the axis title using the rotated coordinate system
  125 + axistitle.draw()
  126 + # Return everything to its previous state
  127 + pyglet.gl.glPopMatrix()
  128 +
  129 + tag.draw()
... ...
robots/little_john/telemetry/code/monitor/version1/series.py 0 → 100644
... ... @@ -0,0 +1,48 @@
  1 +# Class to store data for a live graph
  2 +# Written as a telemetry tool by:
  3 +# The UoN Robot Wars Project, 2018
  4 +
  5 +import logging
  6 +
  7 +class Series:
  8 + def __init__(self, points=100, title="Series title", xname="x-axis name", yname="y-axis name"):
  9 + """Set up an object to store a 2D data series"""
  10 + # Proposal:
  11 + # In order to neatly handle multiple lines on the same graph
  12 + # a series is not so much a set of 2D data points, but a number
  13 + # of series sharing an x axis. For example
  14 + # (time, temperature)
  15 + # (time, xaccel, yaccel, zaccel)
  16 + # The latter seta of points much be of like nature and share an axis
  17 + # (time, temperature, xaccel)
  18 + # would be a meaningless thing to plot on a single graph anyway
  19 +
  20 + self.title = title
  21 + self.xname = xname
  22 + self.yname = yname
  23 + self.xlimits = (0, 100)
  24 + self.ylimits = (-100, 100)
  25 + self.data = []
  26 + self.points = points
  27 +
  28 + logging.info("Created series: " + title)
  29 +
  30 + def addpoint(self, point):
  31 + """Add a point to the dataset, and remove the oldest, if necessary"""
  32 + self.data.append(point)
  33 + if len(self.data) > self.points:
  34 + del self.data[0]
  35 + self.autoscale(0)
  36 +
  37 + def autoscale(self, axis): # axis=0 is x, 1 is y
  38 + minval = self.data[0][axis]
  39 + maxval = self.data[0][axis]
  40 + for value in self.data:
  41 + if value[axis] < minval:
  42 + minval = value[axis]
  43 + if value[axis] > maxval:
  44 + maxval = value[axis]
  45 + if axis == 0:
  46 + self.xlimits = (minval, maxval)
  47 + else:
  48 + self.ylimits = (minval, maxval)
... ...