diff --git a/conf/tools/real_time_plotter__python_.xml b/conf/tools/real_time_plotter__python_.xml
index 4924fa0751..6e411b9628 100644
--- a/conf/tools/real_time_plotter__python_.xml
+++ b/conf/tools/real_time_plotter__python_.xml
@@ -1,2 +1,2 @@
-
+
diff --git a/sw/ground_segment/python/real_time_plot/messagepicker.py b/sw/ground_segment/python/real_time_plot/messagepicker.py
deleted file mode 100755
index 4fe9143d29..0000000000
--- a/sw/ground_segment/python/real_time_plot/messagepicker.py
+++ /dev/null
@@ -1,138 +0,0 @@
-#!/usr/bin/env python
-
-from __future__ import absolute_import, print_function
-
-import wx
-import sys
-import time
-from os import path, getenv
-
-# if PAPARAZZI_HOME not set, then assume the tree containing this
-# file is a reasonable substitute
-PPRZ_HOME = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../../')))
-sys.path.append(PPRZ_HOME + "/var/lib/python")
-
-from pprzlink.ivy import IvyMessagesInterface
-from pprzlink.message import PprzMessage
-
-
-class Message(PprzMessage):
- def __init__(self, class_name, name):
- super(Message, self).__init__(class_name, name)
- self.field_controls = []
- self.index = None
- self.last_seen = time.clock()
-
-
-class Aircraft(object):
- def __init__(self, ac_id):
- self.ac_id = ac_id
- self.messages = {}
- self.messages_book = None
-
-
-class MessagePicker(wx.Frame):
- def __init__(self, parent, callback, ivy_interface=None):
- wx.Frame.__init__(self, parent, name="MessagePicker", title=u'Message Picker', size=wx.Size(320,640))
-
- self.aircrafts = {}
- self.callback = callback
-
- self.tree = wx.TreeCtrl(self)
- self.root = self.tree.AddRoot("Telemetry")
- self.tree.Bind(wx.EVT_LEFT_DCLICK, self.OnDoubleClick)
- self.tree.Bind(wx.EVT_CHAR, self.OnKeyChar)
- self.Bind(wx.EVT_CLOSE, self.OnClose)
- if ivy_interface is None:
- self.message_interface = IvyMessagesInterface("MessagePicker")
- else:
- self.message_interface = ivy_interface
- self.message_interface.subscribe(self.msg_recv)
-
- def OnClose(self, event):
- # if we have a parent (like the plotpanel) only hide instead of shutdown
- if self.GetParent() is not None:
- self.Hide()
- else:
- self.message_interface.shutdown()
- self.Destroy()
-
- def msg_recv(self, ac_id, msg):
- if msg.msg_class != "telemetry":
- return
-
- self.tree.Expand(self.root)
- if ac_id not in self.aircrafts:
- ac_node = self.tree.AppendItem(self.root, str(ac_id))
- self.aircrafts[ac_id] = Aircraft(ac_id)
- self.aircrafts[ac_id].messages_book = ac_node
-
- aircraft = self.aircrafts[ac_id]
- ac_node = aircraft.messages_book
-
- if msg.name not in aircraft.messages:
- msg_node = self.tree.AppendItem(ac_node, str(msg.name))
- self.tree.SortChildren(ac_node)
- aircraft.messages[msg.name] = Message("telemetry", msg.name)
- for field in aircraft.messages[msg.name].fieldnames:
- item = self.tree.AppendItem(msg_node, field)
-
- def OnKeyChar(self, event):
- if event.GetKeyCode() != 13:
- return False
- node = self.tree.GetSelection()
- field_name = self.tree.GetItemText(node)
-
- parent = self.tree.GetItemParent(node)
- message_name = self.tree.GetItemText(parent)
-
- grandparent = self.tree.GetItemParent(parent)
- ac_id = self.tree.GetItemText(grandparent)
-
- if node == self.root or parent == self.root or grandparent == self.root:
- # if not leaf, double click = expand
- if self.tree.IsExpanded(node):
- self.tree.Collapse(node)
- else:
- self.tree.Expand(node)
- return
-
- self.callback(int(ac_id), message_name, field_name)
-
- def OnDoubleClick(self, event):
- node = self.tree.GetSelection()
- field_name = self.tree.GetItemText(node)
-
- parent = self.tree.GetItemParent(node)
- message_name = self.tree.GetItemText(parent)
-
- grandparent = self.tree.GetItemParent(parent)
- ac_id = self.tree.GetItemText(grandparent)
-
- if node == self.root or parent == self.root or grandparent == self.root:
- # if not leaf, double click = expand
- if self.tree.IsExpanded(node):
- self.tree.Collapse(node)
- else:
- self.tree.Expand(node)
- return
-
- self.callback(int(ac_id), message_name, field_name)
-
-class TestApp(wx.App):
- def OnInit(self):
- self.main = MessagePicker(None, callback)
- self.main.Show()
- self.SetTopWindow(self.main)
-
- return True
-
-def test():
- application = TestApp(0)
- application.MainLoop()
-
-def callback(ac_id, message, field):
- print(ac_id, message, field)
-
-if __name__ == '__main__':
- test()
diff --git a/sw/ground_segment/python/real_time_plot/plotframe.py b/sw/ground_segment/python/real_time_plot/plotframe.py
deleted file mode 100644
index f438db580b..0000000000
--- a/sw/ground_segment/python/real_time_plot/plotframe.py
+++ /dev/null
@@ -1,255 +0,0 @@
-#Boa:Frame:PlotFrame
-
-from __future__ import division
-
-import wx
-import plotpanel
-
-
-_INITIAL_TIME_VALUE_ = 0.2 # initial refresh rate in seconds
-
-def create(parent):
- return PlotFrame(parent)
-
-[wxID_PLOTFRAME, wxID_PLOTFRAMECHECKAUTOSCALE, wxID_PLOTFRAMEEDITMAX, wxID_PLOTFRAMEEDITMIN, wxID_PLOTFRAMEEDITTIME, wxID_PLOTFRAMEPANEL1, wxID_PLOTFRAMESLIDERTIME, wxID_PLOTFRAMESTATICTEXT1, wxID_PLOTFRAMESTATICTEXT2, wxID_PLOTFRAMESTATICTEXT3] = [wx.NewId() for _init_ctrls in range(10)]
-
-[wxID_PLOTFRAMEMENU1ITEM_ADD, wxID_PLOTFRAMEMENU1ITEM_PAUSE, wxID_PLOTFRAMEMENU1ITEM_RESET] = [wx.NewId() for _init_coll_menuPlot_Items in range(3)]
-
-class PlotFrame(wx.Frame):
- def _init_coll_boxSizer1_Items(self, parent):
- # generated method, don't edit
-
- parent.AddSizer(self.boxSizer2, 0, border=0, flag=0)
- parent.AddWindow(self.panel1, 1, border=0, flag=wx.EXPAND)
-
- def _init_coll_boxSizer2_Items(self, parent):
- # generated method, don't edit
-
- parent.AddWindow(self.checkAutoScale, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
- parent.AddWindow(self.staticText1, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
- parent.AddWindow(self.editMin, 0, border=0, flag=0)
- parent.AddWindow(self.staticText2, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
- parent.AddWindow(self.editMax, 0, border=0, flag=0)
- parent.AddWindow(self.staticText3, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
- parent.AddWindow(self.sliderTime, 0, border=0, flag=wx.ALIGN_CENTER_VERTICAL)
- parent.AddWindow(self.editTime, 0, border=0, flag=0)
-
- def _init_coll_menuBar1_Menus(self, parent):
- # generated method, don't edit
-
- parent.Append(menu=self.menuPlot, title=u'Plot')
- parent.Append(menu=self.menuCurves, title=u'Curves')
-
- def _init_coll_menuPlot_Items(self, parent):
- # generated method, don't edit
-
- parent.Append(help=u'Add plots', id=wxID_PLOTFRAMEMENU1ITEM_ADD, kind=wx.ITEM_NORMAL, text=u'&Add\tCtrl+A')
- parent.Append(help=u'Reset plot scale', id=wxID_PLOTFRAMEMENU1ITEM_RESET, kind=wx.ITEM_NORMAL, text=u'&Reset\tCtrl+L')
- parent.Append(help=u'Pause the plot', id=wxID_PLOTFRAMEMENU1ITEM_PAUSE, kind=wx.ITEM_CHECK, text=u'&Pause\tCtrl+P')
- self.Bind(wx.EVT_MENU, self.OnMenu1Item_addMenu, id=wxID_PLOTFRAMEMENU1ITEM_ADD)
- self.Bind(wx.EVT_MENU, self.OnMenu1Item_resetMenu, id=wxID_PLOTFRAMEMENU1ITEM_RESET)
- self.Bind(wx.EVT_MENU, self.OnMenu1Item_pauseMenu, id=wxID_PLOTFRAMEMENU1ITEM_PAUSE)
-
- def _init_sizers(self):
- # generated method, don't edit
- self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL)
-
- self.boxSizer2 = wx.BoxSizer(orient=wx.HORIZONTAL)
-
- self._init_coll_boxSizer1_Items(self.boxSizer1)
- self._init_coll_boxSizer2_Items(self.boxSizer2)
-
- self.SetSizer(self.boxSizer1)
-
- def _init_utils(self):
- # generated method, don't edit
- self.menuPlot = wx.Menu(title='')
-
- self.menuCurves = wx.Menu(title='')
-
- self.menuBar1 = wx.MenuBar()
-
- self._init_coll_menuPlot_Items(self.menuPlot)
- self._init_coll_menuBar1_Menus(self.menuBar1)
-
- def _init_ctrls(self, prnt):
- # generated method, don't edit
- wx.Frame.__init__(self, id=wxID_PLOTFRAME, name=u'PlotFrame', parent=prnt, pos=wx.Point(476, 365), size=wx.Size(800, 225), style=wx.DEFAULT_FRAME_STYLE, title=u'Real Time Plot')
- self._init_utils()
- self.SetMenuBar(self.menuBar1)
- self.SetClientSize(wx.Size(800, 225))
-
- self.checkAutoScale = wx.CheckBox(id=wxID_PLOTFRAMECHECKAUTOSCALE, label=u'Auto scale', name=u'checkAutoScale', parent=self, pos=wx.Point(0, 2), size=wx.Size(93, 22), style=0)
- self.checkAutoScale.SetValue(True)
- self.checkAutoScale.Bind(wx.EVT_CHECKBOX, self.OnCheckAutoScaleCheckbox, id=wxID_PLOTFRAMECHECKAUTOSCALE)
-
- self.staticText1 = wx.StaticText(id=wxID_PLOTFRAMESTATICTEXT1, label=u'min', name='staticText1', parent=self, pos=wx.Point(93, 5), size=wx.Size(68, 17), style=wx.ALIGN_RIGHT)
-
- self.editMin = wx.TextCtrl(id=wxID_PLOTFRAMEEDITMIN, name=u'editMin', parent=self, pos=wx.Point(161, 0), size=wx.Size(80, 27), style=0, value=u'')
- self.editMin.Enable(False)
- self.editMin.Bind(wx.EVT_TEXT, self.OnEditMinText, id=wxID_PLOTFRAMEEDITMIN)
-
- self.staticText2 = wx.StaticText(id=wxID_PLOTFRAMESTATICTEXT2, label=u'max', name='staticText2', parent=self, pos=wx.Point(241, 5), size=wx.Size(68, 17), style=wx.ALIGN_RIGHT)
-
- self.editMax = wx.TextCtrl(id=wxID_PLOTFRAMEEDITMAX, name=u'editMax', parent=self, pos=wx.Point(309, 0), size=wx.Size(80, 27), style=0, value=u'')
- self.editMax.Enable(False)
- self.editMax.Bind(wx.EVT_TEXT, self.OnEditMaxText, id=wxID_PLOTFRAMEEDITMAX)
-
- self.staticText3 = wx.StaticText(id=wxID_PLOTFRAMESTATICTEXT3, label=u'interval', name='staticText3', parent=self, pos=wx.Point(389, 5), size=wx.Size(68, 17), style=wx.ALIGN_RIGHT)
-
- self.sliderTime = wx.Slider(id=wxID_PLOTFRAMESLIDERTIME, maxValue=1000, minValue=1, name=u'sliderTime', parent=self, pos=wx.Point(457, 4), size=wx.Size(200, 19), style=wx.SL_HORIZONTAL, value=_INITIAL_TIME_VALUE_ * 1000)
- self.sliderTime.SetLabel(u'')
- self.sliderTime.Bind(wx.EVT_COMMAND_SCROLL, self.OnSliderTimeCommandScroll, id=wxID_PLOTFRAMESLIDERTIME)
-
- self.editTime = wx.TextCtrl(id=wxID_PLOTFRAMEEDITTIME, name=u'editTime', parent=self, pos=wx.Point(657, 0), size=wx.Size(80, 27), style=wx.TE_PROCESS_ENTER, value="%0.2f" % _INITIAL_TIME_VALUE_)
- self.editTime.Bind(wx.EVT_TEXT_ENTER, self.OnEditTimeTextEnter, id=wxID_PLOTFRAMEEDITTIME)
-
- self.panel1 = wx.Panel(id=wxID_PLOTFRAMEPANEL1, name='panel1', parent=self, pos=wx.Point(0, 27), size=wx.Size(800, 200), style=wx.TAB_TRAVERSAL)
-
- self._init_sizers()
-
- def __init__(self, parent):
- self._init_ctrls(parent)
-
- self.canvas = plotpanel.create(self.panel1, self)
- self.dynamic_menus = {}
-
- self.Bind( wx.EVT_CLOSE, self.OnClose)
- self.Bind( wx.EVT_ERASE_BACKGROUND, self.OnErase)
- self.panel1.Bind( wx.EVT_RIGHT_DOWN, self.OnRightDown)
- self.panel1.Bind( wx.EVT_SIZE, self.OnSize)
-
- def OnRightDown(self, event):
- self.PopupMenu(self.menuPlot, event.GetPosition())
-
- def AddPlot(self, ac_id, message, field, color = None, x_axis = False):
- self.canvas.BindCurve(ac_id, message, field, color, x_axis)
-
- def SetMinMax(self, min_, max_):
- self.editMin.SetValue(str(min_))
- self.editMax.SetValue(str(max_))
-
- def OnClose(self, event):
- # need to forward close to canvas so that ivy is shut down, otherwise ivy hangs the shutdown
- self.canvas.OnClose()
- self.Destroy()
-
- def OnErase(self, event):
- pass
-
- def OnSize(self, event):
- self.canvas.OnSize( event.GetSize())
-
- def OnSliderTimeCommandScroll(self, event):
- value = event.GetPosition()
- self.canvas.SetPlotInterval(value)
- self.editTime.SetValue( '%.3f' % (value/1000.0))
-
- def OnEditTimeTextEnter(self, event):
- try:
- value = int(float(event.GetString()) * 1000.0)
- except:
- value = 0
- if value < 1 or value > 1000:
- value = '%.3f' % (self.sliderTime.GetValue() / 1000.0)
- self.editTime.SetValue( value)
- return
- self.canvas.SetPlotInterval(value)
- self.sliderTime.SetValue(value)
-
- def OnCheckAutoScaleCheckbox(self, event):
- value = self.checkAutoScale.GetValue()
- self.editMin.Enable( not value)
- self.editMax.Enable( not value)
- self.canvas.SetAutoScale(value)
-
- def OnMenu1Item_addMenu(self, event):
- self.canvas.ShowMessagePicker(self)
-
- def OnMenu1Item_resetMenu(self, event):
- self.canvas.ResetScale()
-
- def OnMenu1Item_pauseMenu(self, event):
- self.canvas.Pause(event.IsChecked())
-
- def AddCurve(self, menu_id, title, use_as_x = False):
- curveMenu = wx.Menu(title='')
-
- curveMenu.Append(help=u'Delete plot', id=menu_id*10, kind=wx.ITEM_NORMAL, text=u'&Delete')
- curveMenu.Append(help=u'Offset plot', id=menu_id*10+1, kind=wx.ITEM_NORMAL, text=u'&Offset')
- curveMenu.Append(help=u'Scale plot', id=menu_id*10+2, kind=wx.ITEM_NORMAL, text=u'&Scale')
- curveMenu.Append(help=u'Plot data as messages are received rather than async', id=menu_id*10+3, kind=wx.ITEM_CHECK, text=u'&Real time plot')
- curveMenu.Append(help=u'Use this curve as the X-axis rather than a time based scale', id=menu_id*10+4, kind=wx.ITEM_CHECK, text=u'&Use as X-axis')
-
- curveMenu.Check(id=menu_id*10+4, check=bool(use_as_x))
-
- self.Bind(wx.EVT_MENU, self.OnMenuDeleteCurve, id=menu_id*10)
- self.Bind(wx.EVT_MENU, self.OnMenuOffsetCurve, id=menu_id*10+1)
- self.Bind(wx.EVT_MENU, self.OnMenuScaleCurve, id=menu_id*10+2)
- self.Bind(wx.EVT_MENU, self.OnMenuRealTime, id=menu_id*10+3)
- self.Bind(wx.EVT_MENU, self.OnMenuUseAsXAxis, id=menu_id*10+4)
-
- self.dynamic_menus[menu_id] = self.menuCurves.AppendSubMenu(submenu=curveMenu, text=title)
-
- def OnMenuDeleteCurve(self, event):
- menu_id = event.GetId() // 10
- item = self.dynamic_menus[menu_id]
- self.canvas.RemovePlot(menu_id)
- self.menuCurves.DestroyItem(item)
- del self.dynamic_menus[menu_id]
-
- def OnMenuOffsetCurve(self, event):
- menu_id = (event.GetId()-1) // 10
-
- default_value = str(self.canvas.FindPlot(menu_id).offset)
- value = wx.GetTextFromUser("Enter a value to offset the plot", "Offset", default_value)
- try:
- value = float(value)
- self.canvas.OffsetPlot(menu_id, value)
- except:
- pass
-
- def OnMenuScaleCurve(self, event):
- menu_id = (event.GetId()-2) // 10
-
- default_value = str(self.canvas.FindPlot(menu_id).scale)
- value = wx.GetTextFromUser("Enter a factor to scale the plot", "Scale", default_value)
- try:
- value = float(value)
- self.canvas.ScalePlot(menu_id, value)
- except:
- pass
-
- def OnMenuRealTime(self,event):
- menu_id = (event.GetId()-3) // 10
- self.canvas.SetRealTime(menu_id, event.IsChecked())
-
- def OnMenuUseAsXAxis(self,event):
- event_id = event.GetId()
- menu_id = (event_id-4) // 10
- value = event.IsChecked()
-
- if value:
- # go through and clear the checks from any other curves
- for i in self.dynamic_menus:
- for item in self.dynamic_menus[i].GetSubMenu().GetMenuItems():
- if item.GetText() == u'_Use as X-axis' and event_id != item.GetId():
- item.Check(False)
- self.canvas.SetXAxis(menu_id)
- else:
- self.canvas.ClearXAxis()
-
-
- def OnEditMinText(self, event):
- try:
- value = float(event.GetString())
- self.canvas.SetMin(value)
- except:
- pass
-
- def OnEditMaxText(self, event):
- try:
- value = float(event.GetString())
- self.canvas.SetMax(value)
- except:
- pass
diff --git a/sw/ground_segment/python/real_time_plot/plotpanel.py b/sw/ground_segment/python/real_time_plot/plotpanel.py
deleted file mode 100644
index 871f4c0d39..0000000000
--- a/sw/ground_segment/python/real_time_plot/plotpanel.py
+++ /dev/null
@@ -1,447 +0,0 @@
-from __future__ import absolute_import, print_function, division
-
-import wx
-from ivy.std_api import *
-from ivy.ivy import IvyIllegalStateError
-import logging
-from textdroptarget import *
-import math
-import random
-import sys
-from os import getenv, path
-import messagepicker
-
-# if PAPARAZZI_SRC or PAPARAZZI_HOME not set, then assume the tree containing this
-# file is a reasonable substitute
-PPRZ_HOME = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../../')))
-PPRZ_SRC = getenv("PAPARAZZI_SRC", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../../')))
-sys.path.append(PPRZ_SRC + "/sw/lib/python")
-sys.path.append(PPRZ_HOME + "/var/lib/python") # pprzlink
-
-import pprz_env
-from pprzlink import messages_xml_map
-from pprzlink.ivy import IvyMessagesInterface
-from pprzlink.message import PprzMessage
-
-class PlotData:
- def __init__(self, ivy_msg_id, title, width, color=None, scale=1.0):
- self.id = ivy_msg_id
- self.title = title
- self.SetPlotSize(width)
- self.x_min = 1e32
- self.x_max = 1e-32
-
- self.avg = 0.0
- self.std_dev = 0.0
- self.real_time = False
-
- self.scale = scale
- self.offset = 0.0
-
- if color is not None:
- self.color = color
- else:
- r, g, b = random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
- self.color = wx.Colour(r, g, b)
-
- def SetRealTime(self, value):
- self.real_time = value
-
- def SetOffset(self, value):
- self.offset = value
-
- def SetScale(self, value):
- self.scale = value
-
- def SetPlotSize(self, size):
- self.size = size
- self.index = size - 1 # holds the index of the next point to add and the first point to draw
- self.data = [] # holds the list of points to plot
- for i in range(size):
- self.data.append(None)
-
- self.avg = 0.0
- self.std_dev = 0.0
-
- def AddPoint(self, point, x_axis):
- self.data[self.index] = point
-
- if self.real_time or (x_axis is not None):
- self.index = (self.index + 1) % self.size # increment index to next point
- self.data[self.index] = None
-
- def DrawTitle(self, dc, margin, width, height):
-
- text = 'avg:%.2f std:%.2f %s' % (self.avg, self.std_dev, self.title)
-
- (w, h) = dc.GetTextExtent(text)
- dc.SetBrush(wx.Brush(self.color))
- dc.DrawRectangle(width - h - margin, height, h, h)
- dc.DrawText(text, width - 2 * margin - w - h, height)
- return h
-
- def DrawCurve(self, dc, width, height, margin, _max_, _min_, x_axis):
- if width != self.size:
- self.SetPlotSize(width)
- return
-
- if (not self.real_time) and (x_axis is None):
- self.index = (self.index + 1) % self.size # increment index to next point
- self.data[self.index] = None
-
- if x_axis is not None:
- (x_min, x_max) = x_axis.GetXMinMax()
-
- dc.SetPen(wx.Pen(self.color, 1))
- if _max_ < _min_:
- (_min_, _max_) = (-1, 1) # prevent divide by zero or inversion
- if _max_ == _min_:
- (_min_, _max_) = (_max_ - 0.5, _max_ + 0.5)
- delta = _max_ - _min_
- dy = (height - margin * 2) / delta
-
- n = 0
- sums = 0.0
- sum_squares = 0.0
- lines = []
- point_1 = None
- for i in range(self.size):
- ix = (i + self.index) % self.size
- point = self.data[ix]
- if point is None:
- continue
- n += 1
- sums = sums + point
- sum_squares = sum_squares + (point * point)
-
- if x_axis is not None:
- x = x_axis.data[ix]
- if x is None:
- continue
- dx = (width - 1) / (x_max - x_min)
- x = int((x - x_min) * dx)
-
- else:
- x = i * width / self.size
-
- scaled_point = (point + self.offset) * self.scale
- y = height - margin - int((scaled_point - _min_) * dy)
-
- if point_1 is not None:
- line = (point_1[0], point_1[1], x, y)
- lines.append(line)
- point_1 = (x, y)
- dc.DrawLineList(lines)
-
- if n > 0:
- self.avg = sums / n
- self.std_dev = math.sqrt(math.fabs((sum_squares / n) - (self.avg * self.avg)))
-
- def GetXMinMax(self):
- x_min = 1e32
- x_max = -1e32
-
- for i in range(self.size):
- point = self.data[i]
- if point is None:
- continue
- x_min = min(x_min, point)
- x_max = max(x_max, point)
-
- if x_max < x_min:
- (x_min, x_max) = (-1, 1) # prevent divide by zero or inversion
- if x_max == x_min:
- (x_min, x_max) = (x_max - 0.5, x_max + 0.5)
-
- self.x_max = x_max
- self.x_min = x_min
- return (x_min, x_max)
-
-
-_IVY_APPNAME = 'RealtimePlot'
-_IVY_STRING = '(%s %s .*$)'
-
-
-# _IVY_STRING = '^([^ ]*) +(%s( .*|$))' ## <-- from original ocaml (doesn't work here, just returns Sender field...)
-
-def create(parent, frame):
- return PlotPanel(parent, frame)
-
-
-class PlotPanel(object):
- def __init__(self, parent, frame):
- self.parent = parent # we are drawing on our parent, so dc comes from this
- self.frame = frame # the frame owns any controls we might need to update
-
- parent.SetDropTarget(TextDropTarget(self)) # calls self.OnDropText when drag and drop complete
-
- self.width = 800
- self.height = 200
- self.margin = min(self.height / 10, 20)
- self.font = wx.Font(self.margin / 2, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
- self.pixmap = wx.EmptyBitmap(self.width, self.height)
- self.plot_size = self.width
- self.max = -1e32
- self.min = 1e32
- self.plot_interval = 200
- self.plots = {}
- self.auto_scale = True
- self.offset = 0.0
- self.scale = 1.0
- self.x_axis = None
-
- messages_xml_map.parse_messages()
-
- self.ivy_interface = IvyMessagesInterface(_IVY_APPNAME)
-
- # start the timer
- self.timer = wx.FutureCall(self.plot_interval, self.OnTimer)
-
- def SetPlotInterval(self, value):
- self.plot_interval = value
- self.timer.Restart(self.plot_interval)
- self.timer = wx.FutureCall(self.plot_interval, self.OnTimer)
-
- def SetAutoScale(self, value):
- self.auto_scale = value
-
- def SetMin(self, value):
- self.min = value
-
- def SetMax(self, value):
- self.max = value
-
- def Pause(self, pause):
- if pause:
- self.timer.Stop()
- else:
- self.timer = wx.FutureCall(self.plot_interval, self.OnTimer)
-
- def ResetScale(self):
- self.max = -1e32
- self.min = 1e32
-
- def OnClose(self):
- self.timer.Stop()
- try:
- IvyStop()
- except IvyIllegalStateError as e:
- print(e)
-
- def OnErase(self, event):
- pass
-
- def ShowMessagePicker(self, parent):
- frame = messagepicker.MessagePicker(parent, self.BindCurve, self.ivy_interface)
- frame.Show()
-
- def OnDropText(self, data):
- [ac_id, category, message, field, scale] = data.encode('ASCII').split(':')
- self.BindCurve(int(ac_id), message, field, scale=float(scale))
-
- def OnIvyMsg(self, agent, *larg):
- # print(larg[0])
- data = larg[0].split(' ')
- ac_id = int(data[0])
- message = data[1]
-
- if ac_id not in self.plots:
- return
-
- if message not in self.plots[ac_id]:
- return
-
- for field in self.plots[ac_id][message]:
- plot = self.plots[ac_id][message][field]
- ix = messages_xml_map.message_dictionary["telemetry"][message].index(field)
- point = float(data[ix + 2])
-
- if self.x_axis is None or self.x_axis.id != plot.id:
- if self.auto_scale:
- scaled_point = (point + plot.offset) * plot.scale
- self.max = max(self.max, scaled_point)
- self.min = min(self.min, scaled_point)
-
- if self.x_axis is not None:
- plot.index = self.x_axis.index
- plot.AddPoint(point, self.x_axis)
-
- def BindCurve(self, ac_id, message, field, color=None, use_as_x=False, scale=1.0):
- # -- add this telemetry to our list of things to plot ...
- message_string = _IVY_STRING % (ac_id, message)
- # print('Binding to %s' % message_string)
-
- if ac_id not in self.plots:
- self.plots[ac_id] = {}
-
- if message not in self.plots[ac_id]:
- self.plots[ac_id][message] = {}
-
- if field in self.plots[ac_id][message]:
- self.plots[ac_id][message][field].color = wx.Color(random.randint(0, 255), random.randint(0, 255),
- random.randint(0, 255))
- return
-
- ivy_id = self.ivy_interface.bind_raw(self.OnIvyMsg, str(message_string))
- title = '%i:%s:%s' % (ac_id, message, field)
- self.plots[ac_id][message][field] = PlotData(ivy_id, title, self.plot_size, color, scale)
- self.frame.AddCurve(ivy_id, title, use_as_x)
- if use_as_x:
- self.x_axis = self.plots[ac_id][message][field]
-
- def CalcMinMax(self, plot):
- if not self.auto_scale: return
- for x in plot.data:
- self.max = max(self.max, x)
- self.min = min(self.min, x)
- self.frame.SetMinMax(self.min, self.max)
-
- def FindPlotName(self, ivy_id):
- for ac_id in self.plots:
- for msg in self.plots[ac_id]:
- for field in self.plots[ac_id][msg]:
- if self.plots[ac_id][msg][field].id == ivy_id:
- return (ac_id, msg, field)
- return (None, None, None)
-
- def FindPlot(self, ivy_id):
- (ac_id, msg, field) = self.FindPlotName(ivy_id)
- if ac_id is None:
- return None
-
- return self.plots[ac_id][msg][field]
-
- def RemovePlot(self, ivy_id):
- (ac_id, msg, field) = self.FindPlotName(ivy_id)
- if ac_id is None:
- return
-
- if (self.x_axis is not None) and (self.x_axis.id == ivy_id):
- self.x_axis = None
-
- self.ivy_interface.unbind(ivy_id)
- del self.plots[ac_id][msg][field]
- if len(self.plots[ac_id][msg]) == 0:
- del self.plots[ac_id][msg]
-
- def OffsetPlot(self, ivy_id, offset):
- plot = self.FindPlot(ivy_id)
- if plot is None:
- return
-
- plot.SetOffset(offset)
- print('panel value: %.2f' % value)
- CalcMinMax(plot)
-
- def ScalePlot(self, ivy_id, offset):
- plot = self.FindPlot(ivy_id)
- if plot is None:
- return
-
- plot.SetScale(offset)
- CalcMinMax(plot)
-
- def SetRealTime(self, ivy_id, value):
- plot = self.FindPlot(ivy_id)
- if plot is None:
- return
-
- plot.SetRealTime(value)
-
- def SetXAxis(self, ivy_id):
- plot = self.FindPlot(ivy_id)
- if plot is None:
- return
-
- self.x_axis = plot
-
- def ClearXAxis(self):
- self.x_axis = None
-
- def OnSize(self, size):
- (width, height) = size
- if self.width == width and self.height == height:
- return
-
- self.pixmap = wx.EmptyBitmap(width, height)
- self.width = width
- self.height = height
- self.plot_size = width
- self.margin = min(self.height / 10, 20)
- self.font = wx.Font(self.margin / 2, wx.DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
-
- def OnTimer(self):
- self.timer.Restart(self.plot_interval)
- self.frame.SetMinMax(self.min, self.max)
- self.DrawFrame()
-
- def DrawFrame(self):
- dc = wx.ClientDC(self.parent)
- bdc = wx.BufferedDC(dc, self.pixmap)
- bdc.SetBackground(wx.Brush("White"))
- bdc.Clear()
-
- self.DrawBackground(bdc, self.width, self.height)
-
- title_y = 2
- for ac_id in self.plots:
- for message in self.plots[ac_id]:
- for field in self.plots[ac_id][message]:
- plot = self.plots[ac_id][message][field]
- if (self.x_axis is not None) and (self.x_axis.id == plot.id):
- continue
- title_height = plot.DrawTitle(bdc, 2, self.width, title_y)
- plot.DrawCurve(bdc, self.width, self.height, self.margin, self.max, self.min, self.x_axis)
-
- title_y += title_height + 2
-
- def DrawBackground(self, dc, width, height):
-
- # Time Graduations
- dc.SetFont(self.font)
-
- if self.x_axis is None:
- t = self.plot_interval * width
- t1 = "0.0s"
- t2 = "-%.1fs" % (t / 2000.0)
- t3 = "-%.1fs" % (t / 1000.0)
- else:
- x_max = self.x_axis.x_max
- x_min = self.x_axis.x_min
- t1 = "%.2f" % x_max
- t2 = "%.2f" % (x_min + (x_max - x_min) / 2.0)
- t3 = "%.2f" % x_min
-
- (w, h) = dc.GetTextExtent(t1)
- dc.DrawText(t1, width - w, height - h)
- # (w,h) = dc.GetTextExtent(t2) #save time since h will be the same
- dc.DrawText(t2, width / 2, height - h)
- # (w,h) = dc.GetTextExtent(t3) #save time since h will be the same
- dc.DrawText(t3, 0, height - h)
-
- # Y graduations
- if self.max == -1e32:
- return
-
- (_min_, _max_) = (self.min, self.max)
- if _max_ < _min_: # prevent divide by zero or inversion
- (_min_, _max_) = (-1, 1)
- if _max_ == _min_:
- (_min_, _max_) = (_max_ - 0.5, _max_ + 0.5)
-
- delta = _max_ - _min_
- dy = (height - self.margin * 2) / delta
- scale = math.log10(delta)
- d = math.pow(10.0, math.floor(scale))
- u = d
- if delta < 2 * d:
- u = d / 5
- elif delta < 5 * d:
- u = d / 2
- tick_min = _min_ - math.fmod(_min_, u)
- for i in range(int(delta / u) + 1):
- tick = tick_min + float(i) * u
- s = str(tick)
- (w, h) = dc.GetTextExtent(s)
- y = height - self.margin - int((tick - _min_) * dy) - h / 2
- dc.DrawText(s, 0, y)
diff --git a/sw/ground_segment/python/real_time_plot/realtimeplotapp.py b/sw/ground_segment/python/real_time_plot/realtimeplotapp.py
deleted file mode 100755
index 2b4df99aa5..0000000000
--- a/sw/ground_segment/python/real_time_plot/realtimeplotapp.py
+++ /dev/null
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-
-import wx
-import getopt
-import sys
-import plotframe
-
-
-class RealTimePlotApp(wx.App):
- def OnInit(self):
- self.main = plotframe.create(None)
- self.main.Show()
- self.SetTopWindow(self.main)
- opts, args = getopt.getopt(sys.argv[1:], "p:", ["plot"])
- for o, a in opts:
- if o in ("-p", "--plot"):
- [ac_id, message, field, color, use_x] = a.split(':')
- self.main.AddPlot(int(ac_id), message, field, color, bool(int(use_x)))
- return True
-
-
-def main():
- application = RealTimePlotApp(0)
- application.MainLoop()
-
-if __name__ == '__main__':
- main()
diff --git a/sw/ground_segment/python/real_time_plot/textdroptarget.py b/sw/ground_segment/python/real_time_plot/textdroptarget.py
deleted file mode 100644
index 28d3cb2551..0000000000
--- a/sw/ground_segment/python/real_time_plot/textdroptarget.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import wx
-
-class TextDropTarget(wx.TextDropTarget):
- """ This object implements Drop Target functionality for Text """
- def __init__(self, reference):
- """ Initialize the Drop Target, passing in the Object Reference to
- indicate what should receive the dropped text """
- wx.TextDropTarget.__init__(self)
- self.reference = reference
-
- def OnDropText(self, x, y, data):
- """ When text is dropped, send it to the object specified """
- self.reference.OnDropText(data)
diff --git a/sw/logalizer/requirements.txt b/sw/logalizer/requirements.txt
new file mode 100644
index 0000000000..899686c3fc
--- /dev/null
+++ b/sw/logalizer/requirements.txt
@@ -0,0 +1,5 @@
+PyQt5
+pyqtgraph
+numpy
+ivy-python
+
diff --git a/sw/logalizer/rt_plotter.py b/sw/logalizer/rt_plotter.py
new file mode 100755
index 0000000000..dc4141e8b6
--- /dev/null
+++ b/sw/logalizer/rt_plotter.py
@@ -0,0 +1,334 @@
+#!/usr/bin/python3
+import sys
+import signal
+import re
+from os import path, getenv
+from PyQt5 import QtCore, QtWidgets, QtGui
+from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QAction, QToolButton, QMenu
+import argparse
+from ui_rt_plotter import Ui_RT_Plotter
+from time import sleep
+import numpy as np
+import pyqtgraph as pg
+from dataclasses import dataclass, Field, field
+from typing import List, Dict, Optional
+
+# if PAPARAZZI_SRC or PAPARAZZI_HOME not set, then assume the tree containing this
+# file is a reasonable substitute
+PPRZ_HOME = getenv("PAPARAZZI_HOME", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../../')))
+PPRZ_SRC = getenv("PAPARAZZI_SRC", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../../')))
+
+sys.path.append(PPRZ_SRC + "/sw/lib/python")
+sys.path.append(PPRZ_HOME + "/var/lib/python") # pprzlink
+
+from pprzlink.ivy import IvyMessagesInterface
+from pprzlink.message import PprzMessage
+
+
+@dataclass()
+class Curve:
+ field: str
+ scale: float
+ offset: float
+ nb: int
+ action: QAction
+ time: np.ndarray = field(default=None)
+ data: np.ndarray = field(default=None)
+ last_data: Optional[float] = field(default=None)
+ plot: pg.PlotDataItem = field(default=None)
+ bind: Field = field(default=None)
+
+
+@dataclass()
+class Constant:
+ value: float
+ action: QAction
+ plot: pg.PlotDataItem
+
+
+class CurvesCollection:
+ def __init__(self):
+ self.data = {} # type: Dict[str, Dict[str, Dict[str, Curve]]] ## {ac_id:{ msg_name:{key:Curve}}}
+
+ def curve_exists(self, ac_id, msg_name, key) -> bool:
+ try:
+ c = self.get_curve(ac_id, msg_name, key)
+ return True
+ except KeyError:
+ return False
+
+ def get_curve(self, ac_id, msg_name, key) -> Curve:
+ """Throw KeyError if this curve does not exists"""
+ return self.data[ac_id][msg_name][key]
+
+ def remove_curve(self, ac_id, msg_name, key):
+ """Throw KeyError if this curve does not exists"""
+ del self.data[ac_id][msg_name][key]
+
+ def add_curve(self, ac_id, msg_name, key, c):
+ dam = self.data.setdefault(ac_id, {}).setdefault(msg_name, {})
+ dam[key] = c
+
+ def get_all_curves(self) -> List[Curve]:
+ all_curves = []
+ for ac_id in self.data:
+ for msg_name in self.data[ac_id]:
+ for key in self.data[ac_id][msg_name]:
+ c = self.data[ac_id][msg_name][key]
+ all_curves.append(c)
+ return all_curves
+
+ def curves(self, ac_id, msg_name) -> List[Curve]:
+ return list(self.data.get(ac_id, {}).get(msg_name, {}).values())
+
+
+class Plotter(QWidget, Ui_RT_Plotter):
+ """
+ Main plotter class
+ """
+
+ def __init__(self, parent, ivy):
+ QWidget.__init__(self, parent=parent)
+ self.setupUi(self)
+ self.plot = pg.PlotWidget()
+ self.plot.setAcceptDrops(True)
+ self.plot.dragMoveEvent = self.dragMoveEvent
+ self.plot.dragEnterEvent = self.dragEnterEvent
+ self.plot.dragLeaveEvent = self.dragLeaveEvent
+ self.plot.dropEvent = self.dropEvent
+ self.plot.setRange(xRange=(-30.0, 0.))
+ self.plot.disableAutoRange(pg.ViewBox.XAxis)
+ self.plot.setMouseEnabled(x=False, y=True)
+ self.plot.addLegend(offset=(1, 1))
+ self.plot.showGrid(x=True, y=True)
+ self.plot.setBackground('w')
+ # self.plot.setLabel('bottom','time (s)')
+ self.gridLayout.addWidget(self.plot, 1, 0, 1, 14)
+ self.menu_button.setPopupMode(QToolButton.InstantPopup)
+ self.menu = QMenu(self.menu_button)
+ self.menu.addAction(self.action_new_plot)
+ self.menu.addAction(self.action_reset)
+ self.menu.addAction(self.action_suspend)
+ self.menu.addAction(self.action_restart)
+ self.menu.addAction(self.action_dark_background)
+ self.menu.addSeparator()
+ self.menu.addAction(self.action_close)
+ self.menu.addAction(self.action_quit)
+ self.menu.addSeparator()
+ self.menu_button.setMenu(self.menu)
+ self.action_new_plot.triggered.connect(self.open_new_plotter)
+ self.action_reset.triggered.connect(self.reset_plotter)
+ self.action_suspend.triggered.connect(self.suspend_plotter)
+ self.action_restart.triggered.connect(self.restart_plotter)
+ self.action_dark_background.triggered.connect(self.set_background)
+ self.action_close.triggered.connect(self.close_window)
+ self.action_quit.triggered.connect(self.quit_plotter)
+ self.constant_input.returnPressed.connect(self.draw_constant)
+ self.ivy = ivy
+ self.pattern = re.compile("^([0-9]+):(\\w+):(\\w+):(\\w+):([0-9]+.[0-9]*).*$")
+ self.data = CurvesCollection()
+ self.constants = {} # type: Dict[float, Constant]
+ self.dt_slider.valueChanged.connect(self.set_dt)
+ self.size_slider.valueChanged.connect(self.set_size)
+ self.autoscale.clicked.connect(lambda: self.plot.enableAutoRange(x=False, y=True))
+ self.timer = QtCore.QTimer()
+ self.timer.setInterval(self.dt_slider.value() * 10)
+ self.timer.timeout.connect(self.update_plot_data)
+ self.timer.start()
+ self.suspended = False
+ self.nb_color = 8
+ self.idx_color = 0
+ # max display size (s) / min update time (s) (with rounding)
+ self.max_size = int(self.size_slider.maximum() / (self.dt_slider.minimum() / 100) + 0.5)
+
+ def get_dt(self):
+ return self.dt_slider.value() / 100.
+
+ def set_dt(self):
+ self.dt_label.setText("{:1.2f} s".format(self.get_dt()))
+ self.timer.setInterval(int(self.dt_slider.value() * 10))
+
+ def set_size(self):
+ size = self.size_slider.value()
+ self.size_label.setText("{} s".format(size))
+ self.plot.setRange(xRange=(-size, 0.))
+
+ def update_plot_data(self):
+ for curve in self.data.get_all_curves():
+ x = curve.time - self.get_dt()
+ y = curve.data
+ if curve.last_data is not None:
+ x = np.roll(x, -1)
+ x[-1] = 0.
+ y = np.roll(y, -1)
+ y[-1] = curve.scale * curve.last_data + curve.offset
+ curve.nb = min(curve.nb + 1, self.max_size)
+ curve.last_data = None
+ curve.data = y
+ curve.time = x
+ if not self.suspended:
+ curve.plot.setData(x[-curve.nb:], y[-curve.nb:])
+
+ def closing(self):
+ """ shutdown Ivy and window """
+ pass
+
+ def dragLeaveEvent(self, e):
+ pass
+
+ def dragMoveEvent(self, e):
+ e.accept()
+
+ def dragEnterEvent(self, e):
+ if e.mimeData().hasFormat('text/plain'):
+ e.accept()
+ else:
+ e.ignore()
+
+ def dropEvent(self, event):
+ event.accept()
+ match = self.pattern.fullmatch(event.mimeData().text())
+ if match is None:
+ return
+ ac_id, class_name, msg_name, field, sc = match.group(1, 2, 3, 4, 5)
+ scale = float(sc) * self.scale_spin.value()
+ offset = self.offset_spin.value()
+ key = '{}:{}+{}'.format(field, scale, offset)
+
+ if self.data.curve_exists(ac_id, msg_name, key):
+ return
+
+ menu_item = QAction("remove {}".format(match.group(0)))
+ plot = self.plot.plot([], [], pen=(self.idx_color, self.nb_color),
+ name='{}:{}:{}:{}'.format(ac_id, class_name, msg_name, key))
+ bind = ivy.subscribe(self.msg_callback, '^({} {} .*)'.format(ac_id, msg_name))
+
+ c = Curve(field=field,
+ data=np.zeros(self.max_size),
+ last_data=None,
+ time=np.zeros(self.max_size),
+ scale=scale,
+ offset=offset,
+ nb=0,
+ action=menu_item,
+ plot=plot,
+ bind=bind)
+
+ self.data.add_curve(ac_id, msg_name, key, c)
+
+ self.idx_color = (self.idx_color + 1) % self.nb_color
+ self.menu.addAction(menu_item)
+ menu_item.triggered.connect(lambda: self.remove_curve(ac_id, msg_name, key))
+
+ def msg_callback(self, ac_id, msg):
+ for c in self.data.curves(str(ac_id), msg.name):
+ c.last_data = float(msg[c.field])
+
+ def remove_curve(self, ac_id, msg_name, key):
+ curve = self.data.get_curve(ac_id, msg_name, key)
+ self.ivy.unbind(curve.bind)
+ plot_id = curve.plot
+ self.data.remove_curve(ac_id, msg_name, key)
+ self.plot.removeItem(plot_id)
+
+ def open_new_plotter(self):
+ self.parent().open_new_window()
+
+ def reset_plotter(self):
+ for c in self.data.get_all_curves():
+ c.nb = 0
+
+ def suspend_plotter(self):
+ self.suspended = True
+
+ def restart_plotter(self):
+ self.suspended = False
+
+ def close_window(self):
+ self.parent().close()
+
+ def quit_plotter(self):
+ QApplication.exit()
+
+ def set_background(self, dark):
+ if dark:
+ self.plot.setBackground('k')
+ else:
+ self.plot.setBackground('w')
+
+ def draw_constant(self):
+ try:
+ value = float(self.constant_input.text())
+ if value not in self.constants:
+ action = QAction('remove {}'.format(value))
+ self.menu.addAction(action)
+ plot = self.plot.plot([-self.size_slider.maximum(), 0], [value, value],
+ name='constant {}'.format(value), pen='b')
+ c = Constant(value=value, action=action, plot=plot)
+ action.triggered.connect(lambda: self.remove_constant(c))
+ self.constants[value] = c
+
+ except ValueError:
+ print("Input constant value is not a number")
+
+ def remove_constant(self, c: Constant):
+ self.plot.removeItem(c.plot)
+ self.menu.removeAction(c.action)
+ del self.constants[c.value]
+
+
+
+class MainWindow(QMainWindow):
+
+ def __init__(self, ivy, new_window):
+ super().__init__()
+ icon = QtGui.QIcon(path.join(PPRZ_HOME, "data", "pictures", "penguin_icon_rtp.png"))
+ self.setWindowIcon(icon)
+ self.setGeometry(0, 0, 900, 300)
+ self.setMinimumSize(600, 200)
+ self.ivy = ivy
+ self.new_window = new_window
+ self.plotter = Plotter(self, ivy)
+ self.setCentralWidget(self.plotter)
+
+ def close_plotter(self):
+ self.plotter.closing()
+
+ def open_new_window(self):
+ self.new_window()
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description="Real-time plotter")
+
+ try:
+ app = QtWidgets.QApplication(sys.argv)
+ ivy = IvyMessagesInterface("natnet2ivy")
+ windows = []
+
+
+ def make_new_window():
+ w = MainWindow(ivy, make_new_window)
+ windows.append(w)
+ w.show()
+
+
+ def closing():
+ ivy.shutdown()
+ for w in windows:
+ w.close_plotter()
+
+
+ def sigint_handler(*args):
+ """Handler for the SIGINT signal."""
+ app.quit()
+
+
+ app.aboutToQuit.connect(closing)
+ signal.signal(signal.SIGINT, sigint_handler)
+ make_new_window()
+ sys.exit(app.exec_())
+
+ except Exception as e:
+ print('exit on exception:', e)
+ ivy.shutdown()
diff --git a/sw/logalizer/rt_plotter.ui b/sw/logalizer/rt_plotter.ui
new file mode 100644
index 0000000000..31ab6a0ed2
--- /dev/null
+++ b/sw/logalizer/rt_plotter.ui
@@ -0,0 +1,302 @@
+
+
+ RT_Plotter
+
+
+
+ 0
+ 0
+ 910
+ 404
+
+
+
+
+ 0
+ 0
+
+
+
+ false
+
+
+ Real-time plotter
+
+
+ -
+
+
-
+
+
+ update
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ 0.50 s
+
+
+ 5
+
+
+
+ -
+
+
+ 30 s
+
+
+ 3
+
+
+
+ -
+
+
+ Qt::ActionsContextMenu
+
+
+ ...
+
+
+
+
+
+
+ -
+
+
+ size
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ scale next by
+
+
+ 0
+
+
+
+ -
+
+
+ update time (s)
+
+
+ 5
+
+
+ 100
+
+
+ 50
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ constant
+
+
+
+ -
+
+
+ display time interval
+
+
+ 10
+
+
+ 240
+
+
+ 30
+
+
+ 30
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+ scaling factor
+
+
+ -9999.000000000000000
+
+
+ 9999.989999999999782
+
+
+ 1.000000000000000
+
+
+
+ -
+
+
+ restart autoscale on Y axis
+
+
+ auto scale
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 45
+ 0
+
+
+
+ enter a number and press enter to draw a line
+
+
+
+ -
+
+
+ offset
+
+
+ -9999.000000000000000
+
+
+ 9999.989999999999782
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Fixed
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+ New plot
+
+
+ Ctrl+N
+
+
+
+
+ Reset
+
+
+ reset data for all plots
+
+
+ Ctrl+L
+
+
+
+
+ Suspend
+
+
+ freeze plotter view
+
+
+ Ctrl+S
+
+
+
+
+ Restart
+
+
+ restart suspended plotter
+
+
+ Ctrl+X
+
+
+
+
+ Close
+
+
+ close plotter window
+
+
+ Ctrl+W
+
+
+
+
+ Quit
+
+
+ quit realtime plotter (close all windows)
+
+
+ Ctrl+Q
+
+
+
+
+ true
+
+
+ Dark background
+
+
+ change background from white to black
+
+
+ Ctrl+B
+
+
+
+
+
+
diff --git a/sw/logalizer/ui_rt_plotter.py b/sw/logalizer/ui_rt_plotter.py
new file mode 100644
index 0000000000..2ca59cfe19
--- /dev/null
+++ b/sw/logalizer/ui_rt_plotter.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file 'rt_plotter.ui'
+#
+# Created by: PyQt5 UI code generator 5.14.1
+#
+# WARNING! All changes made in this file will be lost!
+
+
+from PyQt5 import QtCore, QtGui, QtWidgets
+
+
+class Ui_RT_Plotter(object):
+ def setupUi(self, RT_Plotter):
+ RT_Plotter.setObjectName("RT_Plotter")
+ RT_Plotter.resize(910, 404)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.MinimumExpanding, QtWidgets.QSizePolicy.MinimumExpanding)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(RT_Plotter.sizePolicy().hasHeightForWidth())
+ RT_Plotter.setSizePolicy(sizePolicy)
+ RT_Plotter.setAcceptDrops(False)
+ self.verticalLayout = QtWidgets.QVBoxLayout(RT_Plotter)
+ self.verticalLayout.setObjectName("verticalLayout")
+ self.gridLayout = QtWidgets.QGridLayout()
+ self.gridLayout.setObjectName("gridLayout")
+ self.label_3 = QtWidgets.QLabel(RT_Plotter)
+ self.label_3.setObjectName("label_3")
+ self.gridLayout.addWidget(self.label_3, 0, 1, 1, 1)
+ self.dt_label = QtWidgets.QLabel(RT_Plotter)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.dt_label.sizePolicy().hasHeightForWidth())
+ self.dt_label.setSizePolicy(sizePolicy)
+ self.dt_label.setObjectName("dt_label")
+ self.gridLayout.addWidget(self.dt_label, 0, 2, 1, 1)
+ self.size_label = QtWidgets.QLabel(RT_Plotter)
+ self.size_label.setObjectName("size_label")
+ self.gridLayout.addWidget(self.size_label, 0, 5, 1, 1)
+ self.menu_button = QtWidgets.QToolButton(RT_Plotter)
+ self.menu_button.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
+ icon = QtGui.QIcon.fromTheme("format-justify-fill")
+ self.menu_button.setIcon(icon)
+ self.menu_button.setObjectName("menu_button")
+ self.gridLayout.addWidget(self.menu_button, 0, 13, 1, 1)
+ self.label_5 = QtWidgets.QLabel(RT_Plotter)
+ self.label_5.setObjectName("label_5")
+ self.gridLayout.addWidget(self.label_5, 0, 4, 1, 1)
+ self.label_2 = QtWidgets.QLabel(RT_Plotter)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.label_2.sizePolicy().hasHeightForWidth())
+ self.label_2.setSizePolicy(sizePolicy)
+ self.label_2.setObjectName("label_2")
+ self.gridLayout.addWidget(self.label_2, 0, 10, 1, 1)
+ self.dt_slider = QtWidgets.QSlider(RT_Plotter)
+ self.dt_slider.setMinimum(5)
+ self.dt_slider.setMaximum(100)
+ self.dt_slider.setSliderPosition(50)
+ self.dt_slider.setOrientation(QtCore.Qt.Horizontal)
+ self.dt_slider.setObjectName("dt_slider")
+ self.gridLayout.addWidget(self.dt_slider, 0, 3, 1, 1)
+ self.label = QtWidgets.QLabel(RT_Plotter)
+ self.label.setObjectName("label")
+ self.gridLayout.addWidget(self.label, 0, 7, 1, 1)
+ self.size_slider = QtWidgets.QSlider(RT_Plotter)
+ self.size_slider.setMinimum(10)
+ self.size_slider.setMaximum(240)
+ self.size_slider.setProperty("value", 30)
+ self.size_slider.setSliderPosition(30)
+ self.size_slider.setOrientation(QtCore.Qt.Horizontal)
+ self.size_slider.setObjectName("size_slider")
+ self.gridLayout.addWidget(self.size_slider, 0, 6, 1, 1)
+ self.scale_spin = QtWidgets.QDoubleSpinBox(RT_Plotter)
+ self.scale_spin.setMinimum(-9999.0)
+ self.scale_spin.setMaximum(9999.99)
+ self.scale_spin.setProperty("value", 1.0)
+ self.scale_spin.setObjectName("scale_spin")
+ self.gridLayout.addWidget(self.scale_spin, 0, 11, 1, 1)
+ self.autoscale = QtWidgets.QPushButton(RT_Plotter)
+ self.autoscale.setObjectName("autoscale")
+ self.gridLayout.addWidget(self.autoscale, 0, 0, 1, 1)
+ self.constant_input = QtWidgets.QLineEdit(RT_Plotter)
+ sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Fixed)
+ sizePolicy.setHorizontalStretch(0)
+ sizePolicy.setVerticalStretch(0)
+ sizePolicy.setHeightForWidth(self.constant_input.sizePolicy().hasHeightForWidth())
+ self.constant_input.setSizePolicy(sizePolicy)
+ self.constant_input.setMinimumSize(QtCore.QSize(45, 0))
+ self.constant_input.setObjectName("constant_input")
+ self.gridLayout.addWidget(self.constant_input, 0, 8, 1, 1)
+ self.offset_spin = QtWidgets.QDoubleSpinBox(RT_Plotter)
+ self.offset_spin.setMinimum(-9999.0)
+ self.offset_spin.setMaximum(9999.99)
+ self.offset_spin.setObjectName("offset_spin")
+ self.gridLayout.addWidget(self.offset_spin, 0, 12, 1, 1)
+ spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Minimum)
+ self.gridLayout.addItem(spacerItem, 0, 9, 1, 1)
+ self.verticalLayout.addLayout(self.gridLayout)
+ self.action_new_plot = QtWidgets.QAction(RT_Plotter)
+ self.action_new_plot.setObjectName("action_new_plot")
+ self.action_reset = QtWidgets.QAction(RT_Plotter)
+ self.action_reset.setObjectName("action_reset")
+ self.action_suspend = QtWidgets.QAction(RT_Plotter)
+ self.action_suspend.setObjectName("action_suspend")
+ self.action_restart = QtWidgets.QAction(RT_Plotter)
+ self.action_restart.setObjectName("action_restart")
+ self.action_close = QtWidgets.QAction(RT_Plotter)
+ self.action_close.setObjectName("action_close")
+ self.action_quit = QtWidgets.QAction(RT_Plotter)
+ self.action_quit.setObjectName("action_quit")
+ self.action_dark_background = QtWidgets.QAction(RT_Plotter)
+ self.action_dark_background.setCheckable(True)
+ self.action_dark_background.setObjectName("action_dark_background")
+
+ self.retranslateUi(RT_Plotter)
+ QtCore.QMetaObject.connectSlotsByName(RT_Plotter)
+
+ def retranslateUi(self, RT_Plotter):
+ _translate = QtCore.QCoreApplication.translate
+ RT_Plotter.setWindowTitle(_translate("RT_Plotter", "Real-time plotter"))
+ self.label_3.setText(_translate("RT_Plotter", "update"))
+ self.dt_label.setText(_translate("RT_Plotter", "0.50 s"))
+ self.size_label.setText(_translate("RT_Plotter", "30 s"))
+ self.menu_button.setText(_translate("RT_Plotter", "..."))
+ self.label_5.setText(_translate("RT_Plotter", "size"))
+ self.label_2.setText(_translate("RT_Plotter", "scale next by"))
+ self.dt_slider.setToolTip(_translate("RT_Plotter", "update time (s)"))
+ self.label.setText(_translate("RT_Plotter", "constant"))
+ self.size_slider.setToolTip(_translate("RT_Plotter", "display time interval"))
+ self.scale_spin.setToolTip(_translate("RT_Plotter", "scaling factor"))
+ self.autoscale.setToolTip(_translate("RT_Plotter", "restart autoscale on Y axis"))
+ self.autoscale.setText(_translate("RT_Plotter", "auto scale"))
+ self.constant_input.setToolTip(_translate("RT_Plotter", "enter a number and press enter to draw a line"))
+ self.offset_spin.setToolTip(_translate("RT_Plotter", "offset"))
+ self.action_new_plot.setText(_translate("RT_Plotter", "New plot"))
+ self.action_new_plot.setShortcut(_translate("RT_Plotter", "Ctrl+N"))
+ self.action_reset.setText(_translate("RT_Plotter", "Reset"))
+ self.action_reset.setToolTip(_translate("RT_Plotter", "reset data for all plots"))
+ self.action_reset.setShortcut(_translate("RT_Plotter", "Ctrl+L"))
+ self.action_suspend.setText(_translate("RT_Plotter", "Suspend"))
+ self.action_suspend.setToolTip(_translate("RT_Plotter", "freeze plotter view"))
+ self.action_suspend.setShortcut(_translate("RT_Plotter", "Ctrl+S"))
+ self.action_restart.setText(_translate("RT_Plotter", "Restart"))
+ self.action_restart.setToolTip(_translate("RT_Plotter", "restart suspended plotter"))
+ self.action_restart.setShortcut(_translate("RT_Plotter", "Ctrl+X"))
+ self.action_close.setText(_translate("RT_Plotter", "Close"))
+ self.action_close.setToolTip(_translate("RT_Plotter", "close plotter window"))
+ self.action_close.setShortcut(_translate("RT_Plotter", "Ctrl+W"))
+ self.action_quit.setText(_translate("RT_Plotter", "Quit"))
+ self.action_quit.setToolTip(_translate("RT_Plotter", "quit realtime plotter (close all windows)"))
+ self.action_quit.setShortcut(_translate("RT_Plotter", "Ctrl+Q"))
+ self.action_dark_background.setText(_translate("RT_Plotter", "Dark background"))
+ self.action_dark_background.setToolTip(_translate("RT_Plotter", "change background from white to black"))
+ self.action_dark_background.setShortcut(_translate("RT_Plotter", "Ctrl+B"))