Compare View
Commits (3)
Showing
3 changed files
Show diff stats
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) | ... | ... |