Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Ardupilot
GitHub Repository: Ardupilot/ardupilot
Path: blob/master/Tools/mavproxy_modules/lib/magcal_graph_ui.py
9573 views
1
# Copyright (C) 2016 Intel Corporation. All rights reserved.
2
#
3
# This file is free software: you can redistribute it and/or modify it
4
# under the terms of the GNU General Public License as published by the
5
# Free Software Foundation, either version 3 of the License, or
6
# (at your option) any later version.
7
#
8
# This file is distributed in the hope that it will be useful, but
9
# WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11
# See the GNU General Public License for more details.
12
#
13
# You should have received a copy of the GNU General Public License along
14
# with this program. If not, see <http://www.gnu.org/licenses/>.
15
16
# flake8: noqa
17
18
import matplotlib.pyplot as plt
19
from matplotlib.backends.backend_wxagg import FigureCanvas
20
from mpl_toolkits.mplot3d import Axes3D
21
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
22
from pymavlink.mavutil import mavlink
23
24
from MAVProxy.modules.lib import wx_processguard
25
from MAVProxy.modules.lib.wx_loader import wx
26
27
import geodesic_grid as grid
28
29
class MagcalPanel(wx.Panel):
30
_status_markup_strings = {
31
mavlink.MAG_CAL_NOT_STARTED: 'Not started',
32
mavlink.MAG_CAL_WAITING_TO_START: 'Waiting to start',
33
mavlink.MAG_CAL_RUNNING_STEP_ONE: 'Step one',
34
mavlink.MAG_CAL_RUNNING_STEP_TWO: 'Step two',
35
mavlink.MAG_CAL_SUCCESS: '<span color="blue">Success</span>',
36
mavlink.MAG_CAL_FAILED: '<span color="red">Failed</span>',
37
}
38
39
_empty_color = '#7ea6ce'
40
_filled_color = '#4680b9'
41
42
def __init__(self, *k, **kw):
43
super(MagcalPanel, self).__init__(*k, **kw)
44
45
facecolor = self.GetBackgroundColour().GetAsString(wx.C2S_HTML_SYNTAX)
46
fig = plt.figure(facecolor=facecolor, figsize=(1,1))
47
48
self._canvas = FigureCanvas(self, wx.ID_ANY, fig)
49
self._canvas.SetMinSize((300,300))
50
51
self._id_text = wx.StaticText(self, wx.ID_ANY)
52
self._status_text = wx.StaticText(self, wx.ID_ANY)
53
self._completion_pct_text = wx.StaticText(self, wx.ID_ANY)
54
55
sizer = wx.BoxSizer(wx.VERTICAL)
56
sizer.Add(self._id_text)
57
sizer.Add(self._status_text)
58
sizer.Add(self._completion_pct_text)
59
sizer.Add(self._canvas, proportion=1, flag=wx.EXPAND)
60
self.SetSizer(sizer)
61
62
ax = fig.add_subplot(111, axis_bgcolor=facecolor, projection='3d')
63
self.configure_plot(ax)
64
65
def configure_plot(self, ax):
66
extra = .5
67
lim = grid.radius + extra
68
ax.set_xlim3d(-lim, lim)
69
ax.set_ylim3d(-lim, lim)
70
ax.set_zlim3d(-lim, lim)
71
72
ax.set_xlabel('x')
73
ax.set_ylabel('y')
74
ax.set_zlabel('z')
75
76
ax.invert_zaxis()
77
ax.invert_xaxis()
78
79
ax.set_aspect('equal')
80
81
self._polygons_collection = Poly3DCollection(
82
grid.sections_triangles,
83
edgecolors='#386694',
84
)
85
ax.add_collection3d(self._polygons_collection)
86
87
def update_status_from_mavlink(self, m):
88
status_string = self._status_markup_strings.get(m.cal_status, '???')
89
self._status_text.SetLabelMarkup(
90
'<b>Status:</b> %s' % status_string,
91
)
92
93
def mavlink_magcal_report(self, m):
94
self.update_status_from_mavlink(m)
95
self._completion_pct_text.SetLabel('')
96
97
def mavlink_magcal_progress(self, m):
98
facecolors = []
99
for i, mask in enumerate(m.completion_mask):
100
for j in range(8):
101
section = i * 8 + j
102
if mask & 1 << j:
103
facecolor = self._filled_color
104
else:
105
facecolor = self._empty_color
106
facecolors.append(facecolor)
107
self._polygons_collection.set_facecolors(facecolors)
108
self._canvas.draw()
109
110
self._id_text.SetLabelMarkup(
111
'<b>Compass id:</b> %d' % m.compass_id
112
)
113
114
self._completion_pct_text.SetLabelMarkup(
115
'<b>Completion:</b> %d%%' % m.completion_pct
116
)
117
118
self.update_status_from_mavlink(m)
119
120
_legend_panel = None
121
@staticmethod
122
def legend_panel(*k, **kw):
123
if MagcalPanel._legend_panel:
124
return MagcalPanel._legend_panel
125
126
p = MagcalPanel._legend_panel = wx.Panel(*k, **kw)
127
sizer = wx.BoxSizer(wx.HORIZONTAL)
128
p.SetSizer(sizer)
129
130
marker = wx.Panel(p, wx.ID_ANY, size=(10, 10))
131
marker.SetBackgroundColour(MagcalPanel._empty_color)
132
sizer.Add(marker, flag=wx.ALIGN_CENTER)
133
text = wx.StaticText(p, wx.ID_ANY)
134
text.SetLabel('Sections not hit')
135
sizer.Add(text, border=4, flag=wx.ALIGN_CENTER | wx.LEFT)
136
137
marker = wx.Panel(p, wx.ID_ANY, size=(10, 10))
138
marker.SetBackgroundColour(MagcalPanel._filled_color)
139
sizer.Add(marker, border=10, flag=wx.ALIGN_CENTER | wx.LEFT)
140
text = wx.StaticText(p, wx.ID_ANY)
141
text.SetLabel('Sections hit')
142
sizer.Add(text, border=4, flag=wx.ALIGN_CENTER | wx.LEFT)
143
return p
144
145
class MagcalFrame(wx.Frame):
146
def __init__(self, conn):
147
super(MagcalFrame, self).__init__(
148
None,
149
wx.ID_ANY,
150
title='Magcal Graph',
151
)
152
153
self.SetMinSize((300, 300))
154
155
self._conn = conn
156
157
self._main_panel = wx.ScrolledWindow(self, wx.ID_ANY)
158
self._main_panel.SetScrollbars(1, 1, 1, 1)
159
160
self._magcal_panels = {}
161
162
self._sizer = wx.BoxSizer(wx.VERTICAL)
163
self._main_panel.SetSizer(self._sizer)
164
165
idle_text = wx.StaticText(self._main_panel, wx.ID_ANY)
166
idle_text.SetLabelMarkup('<i>No calibration messages received yet...</i>')
167
idle_text.SetForegroundColour('#444444')
168
169
self._sizer.AddStretchSpacer()
170
self._sizer.Add(
171
idle_text,
172
proportion=0,
173
flag=wx.ALIGN_CENTER | wx.ALL,
174
border=10,
175
)
176
self._sizer.AddStretchSpacer()
177
178
self._timer = wx.Timer(self)
179
self.Bind(wx.EVT_TIMER, self.timer_callback, self._timer)
180
self._timer.Start(200)
181
182
def add_compass(self, id):
183
if not self._magcal_panels:
184
self._sizer.Clear(deleteWindows=True)
185
self._magcal_panels_sizer = wx.BoxSizer(wx.HORIZONTAL)
186
187
self._sizer.Add(
188
self._magcal_panels_sizer,
189
proportion=1,
190
flag=wx.EXPAND,
191
)
192
193
legend = MagcalPanel.legend_panel(self._main_panel, wx.ID_ANY)
194
self._sizer.Add(
195
legend,
196
proportion=0,
197
flag=wx.ALIGN_CENTER,
198
)
199
200
self._magcal_panels[id] = MagcalPanel(self._main_panel, wx.ID_ANY)
201
self._magcal_panels_sizer.Add(
202
self._magcal_panels[id],
203
proportion=1,
204
border=10,
205
flag=wx.EXPAND | wx.ALL,
206
)
207
208
def timer_callback(self, evt):
209
close_requested = False
210
mavlink_msgs = {}
211
while self._conn.poll():
212
m = self._conn.recv()
213
if isinstance(m, str) and m == 'close':
214
close_requested = True
215
continue
216
if m.compass_id not in mavlink_msgs:
217
# Keep the last two messages so that we get the last progress
218
# if the last message is the calibration report.
219
mavlink_msgs[m.compass_id] = [None, m]
220
else:
221
l = mavlink_msgs[m.compass_id]
222
l[0] = l[1]
223
l[1] = m
224
225
if close_requested:
226
self._timer.Stop()
227
self.Destroy()
228
return
229
230
if not mavlink_msgs:
231
return
232
233
needs_fit = False
234
for k in mavlink_msgs:
235
if k not in self._magcal_panels:
236
self.add_compass(k)
237
needs_fit = True
238
if needs_fit:
239
self._sizer.Fit(self)
240
241
for k, l in mavlink_msgs.items():
242
for m in l:
243
if not m:
244
continue
245
panel = self._magcal_panels[k]
246
if m.get_type() == 'MAG_CAL_PROGRESS':
247
panel.mavlink_magcal_progress(m)
248
elif m.get_type() == 'MAG_CAL_REPORT':
249
panel.mavlink_magcal_report(m)
250
251