Path: blob/master/Tools/mavproxy_modules/lib/magcal_graph_ui.py
9573 views
# Copyright (C) 2016 Intel Corporation. All rights reserved.1#2# This file is free software: you can redistribute it and/or modify it3# under the terms of the GNU General Public License as published by the4# Free Software Foundation, either version 3 of the License, or5# (at your option) any later version.6#7# This file is distributed in the hope that it will be useful, but8# WITHOUT ANY WARRANTY; without even the implied warranty of9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.10# See the GNU General Public License for more details.11#12# You should have received a copy of the GNU General Public License along13# with this program. If not, see <http://www.gnu.org/licenses/>.1415# flake8: noqa1617import matplotlib.pyplot as plt18from matplotlib.backends.backend_wxagg import FigureCanvas19from mpl_toolkits.mplot3d import Axes3D20from mpl_toolkits.mplot3d.art3d import Poly3DCollection21from pymavlink.mavutil import mavlink2223from MAVProxy.modules.lib import wx_processguard24from MAVProxy.modules.lib.wx_loader import wx2526import geodesic_grid as grid2728class MagcalPanel(wx.Panel):29_status_markup_strings = {30mavlink.MAG_CAL_NOT_STARTED: 'Not started',31mavlink.MAG_CAL_WAITING_TO_START: 'Waiting to start',32mavlink.MAG_CAL_RUNNING_STEP_ONE: 'Step one',33mavlink.MAG_CAL_RUNNING_STEP_TWO: 'Step two',34mavlink.MAG_CAL_SUCCESS: '<span color="blue">Success</span>',35mavlink.MAG_CAL_FAILED: '<span color="red">Failed</span>',36}3738_empty_color = '#7ea6ce'39_filled_color = '#4680b9'4041def __init__(self, *k, **kw):42super(MagcalPanel, self).__init__(*k, **kw)4344facecolor = self.GetBackgroundColour().GetAsString(wx.C2S_HTML_SYNTAX)45fig = plt.figure(facecolor=facecolor, figsize=(1,1))4647self._canvas = FigureCanvas(self, wx.ID_ANY, fig)48self._canvas.SetMinSize((300,300))4950self._id_text = wx.StaticText(self, wx.ID_ANY)51self._status_text = wx.StaticText(self, wx.ID_ANY)52self._completion_pct_text = wx.StaticText(self, wx.ID_ANY)5354sizer = wx.BoxSizer(wx.VERTICAL)55sizer.Add(self._id_text)56sizer.Add(self._status_text)57sizer.Add(self._completion_pct_text)58sizer.Add(self._canvas, proportion=1, flag=wx.EXPAND)59self.SetSizer(sizer)6061ax = fig.add_subplot(111, axis_bgcolor=facecolor, projection='3d')62self.configure_plot(ax)6364def configure_plot(self, ax):65extra = .566lim = grid.radius + extra67ax.set_xlim3d(-lim, lim)68ax.set_ylim3d(-lim, lim)69ax.set_zlim3d(-lim, lim)7071ax.set_xlabel('x')72ax.set_ylabel('y')73ax.set_zlabel('z')7475ax.invert_zaxis()76ax.invert_xaxis()7778ax.set_aspect('equal')7980self._polygons_collection = Poly3DCollection(81grid.sections_triangles,82edgecolors='#386694',83)84ax.add_collection3d(self._polygons_collection)8586def update_status_from_mavlink(self, m):87status_string = self._status_markup_strings.get(m.cal_status, '???')88self._status_text.SetLabelMarkup(89'<b>Status:</b> %s' % status_string,90)9192def mavlink_magcal_report(self, m):93self.update_status_from_mavlink(m)94self._completion_pct_text.SetLabel('')9596def mavlink_magcal_progress(self, m):97facecolors = []98for i, mask in enumerate(m.completion_mask):99for j in range(8):100section = i * 8 + j101if mask & 1 << j:102facecolor = self._filled_color103else:104facecolor = self._empty_color105facecolors.append(facecolor)106self._polygons_collection.set_facecolors(facecolors)107self._canvas.draw()108109self._id_text.SetLabelMarkup(110'<b>Compass id:</b> %d' % m.compass_id111)112113self._completion_pct_text.SetLabelMarkup(114'<b>Completion:</b> %d%%' % m.completion_pct115)116117self.update_status_from_mavlink(m)118119_legend_panel = None120@staticmethod121def legend_panel(*k, **kw):122if MagcalPanel._legend_panel:123return MagcalPanel._legend_panel124125p = MagcalPanel._legend_panel = wx.Panel(*k, **kw)126sizer = wx.BoxSizer(wx.HORIZONTAL)127p.SetSizer(sizer)128129marker = wx.Panel(p, wx.ID_ANY, size=(10, 10))130marker.SetBackgroundColour(MagcalPanel._empty_color)131sizer.Add(marker, flag=wx.ALIGN_CENTER)132text = wx.StaticText(p, wx.ID_ANY)133text.SetLabel('Sections not hit')134sizer.Add(text, border=4, flag=wx.ALIGN_CENTER | wx.LEFT)135136marker = wx.Panel(p, wx.ID_ANY, size=(10, 10))137marker.SetBackgroundColour(MagcalPanel._filled_color)138sizer.Add(marker, border=10, flag=wx.ALIGN_CENTER | wx.LEFT)139text = wx.StaticText(p, wx.ID_ANY)140text.SetLabel('Sections hit')141sizer.Add(text, border=4, flag=wx.ALIGN_CENTER | wx.LEFT)142return p143144class MagcalFrame(wx.Frame):145def __init__(self, conn):146super(MagcalFrame, self).__init__(147None,148wx.ID_ANY,149title='Magcal Graph',150)151152self.SetMinSize((300, 300))153154self._conn = conn155156self._main_panel = wx.ScrolledWindow(self, wx.ID_ANY)157self._main_panel.SetScrollbars(1, 1, 1, 1)158159self._magcal_panels = {}160161self._sizer = wx.BoxSizer(wx.VERTICAL)162self._main_panel.SetSizer(self._sizer)163164idle_text = wx.StaticText(self._main_panel, wx.ID_ANY)165idle_text.SetLabelMarkup('<i>No calibration messages received yet...</i>')166idle_text.SetForegroundColour('#444444')167168self._sizer.AddStretchSpacer()169self._sizer.Add(170idle_text,171proportion=0,172flag=wx.ALIGN_CENTER | wx.ALL,173border=10,174)175self._sizer.AddStretchSpacer()176177self._timer = wx.Timer(self)178self.Bind(wx.EVT_TIMER, self.timer_callback, self._timer)179self._timer.Start(200)180181def add_compass(self, id):182if not self._magcal_panels:183self._sizer.Clear(deleteWindows=True)184self._magcal_panels_sizer = wx.BoxSizer(wx.HORIZONTAL)185186self._sizer.Add(187self._magcal_panels_sizer,188proportion=1,189flag=wx.EXPAND,190)191192legend = MagcalPanel.legend_panel(self._main_panel, wx.ID_ANY)193self._sizer.Add(194legend,195proportion=0,196flag=wx.ALIGN_CENTER,197)198199self._magcal_panels[id] = MagcalPanel(self._main_panel, wx.ID_ANY)200self._magcal_panels_sizer.Add(201self._magcal_panels[id],202proportion=1,203border=10,204flag=wx.EXPAND | wx.ALL,205)206207def timer_callback(self, evt):208close_requested = False209mavlink_msgs = {}210while self._conn.poll():211m = self._conn.recv()212if isinstance(m, str) and m == 'close':213close_requested = True214continue215if m.compass_id not in mavlink_msgs:216# Keep the last two messages so that we get the last progress217# if the last message is the calibration report.218mavlink_msgs[m.compass_id] = [None, m]219else:220l = mavlink_msgs[m.compass_id]221l[0] = l[1]222l[1] = m223224if close_requested:225self._timer.Stop()226self.Destroy()227return228229if not mavlink_msgs:230return231232needs_fit = False233for k in mavlink_msgs:234if k not in self._magcal_panels:235self.add_compass(k)236needs_fit = True237if needs_fit:238self._sizer.Fit(self)239240for k, l in mavlink_msgs.items():241for m in l:242if not m:243continue244panel = self._magcal_panels[k]245if m.get_type() == 'MAG_CAL_PROGRESS':246panel.mavlink_magcal_progress(m)247elif m.get_type() == 'MAG_CAL_REPORT':248panel.mavlink_magcal_report(m)249250251