CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
Ardupilot

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place. Commercial Alternative to JupyterHub.

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