diff --git a/sw/ground_segment/python/messages_app/messagesapp.py b/sw/ground_segment/python/messages_app/messagesapp.py index 6b44cb483a..3c26c27961 100755 --- a/sw/ground_segment/python/messages_app/messagesapp.py +++ b/sw/ground_segment/python/messages_app/messagesapp.py @@ -1,23 +1,17 @@ #!/usr/bin/env python import wx -import getopt -import sys import messagesframe + class MessagesApp(wx.App): def OnInit(self): self.main = messagesframe.MessagesFrame() 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 = MessagesApp(0) application.MainLoop() diff --git a/sw/ground_segment/python/messages_app/messagesframe.py b/sw/ground_segment/python/messages_app/messagesframe.py index ac1e32573e..5db50f55c8 100644 --- a/sw/ground_segment/python/messages_app/messagesframe.py +++ b/sw/ground_segment/python/messages_app/messagesframe.py @@ -4,12 +4,12 @@ import sys import os import time import threading -import math PPRZ_HOME = os.getenv("PAPARAZZI_HOME") sys.path.append(PPRZ_HOME + "/sw/lib/python") -import messages_tool +from ivy_msg_interface import IvyMessagesInterface +import messages_xml_map WIDTH = 450 LABEL_WIDTH = 166 @@ -17,13 +17,47 @@ DATA_WIDTH = 100 HEIGHT = 800 BORDER = 1 -class MessagesFrame(wx.Frame): - def message_recv(self, ac_id, name, values): - if ac_id in self.aircrafts and name in self.aircrafts[ac_id].messages: - if time.time() - self.aircrafts[ac_id].messages[name].last_seen < 0.2: - return - wx.CallAfter(self.gui_update, ac_id, name, values) +class Message: + def __init__(self, class_name, name): + messages_xml_map.parse_messages() + self.field_value = [] + self.field_names = messages_xml_map.message_dictionary[class_name][name] + self.field_controls = [] + self.index = None + self.last_seen = time.clock() + self.name = name + + +class Aircraft: + def __init__(self, ac_id): + self.ac_id = ac_id + self.messages = {} + self.messages_book = None + + +class MessagesFrame(wx.Frame): + def message_recv(self, msg_class, msg_name, ac_id, values): + """Handle incoming messages + + Callback function for IvyMessagesInterface + + :param msg_class: message classe ("ground" or "telemetry") + :param msg_class: string + :param msg_name: message name + :type msg_name: str + :param ac_id: aircraft id + :type ac_id: int + :param values: message values + :type values: list + """ + # only show messages of the requested class + if msg_class != self.msg_class: + return + if ac_id in self.aircrafts and msg_name in self.aircrafts[ac_id].messages: + if time.time() - self.aircrafts[ac_id].messages[msg_name].last_seen < 0.2: + return + wx.CallAfter(self.gui_update, msg_class, msg_name, ac_id, values) def find_page(self, book, name): if book.GetPageCount() < 1: @@ -31,11 +65,10 @@ class MessagesFrame(wx.Frame): start = 0 end = book.GetPageCount() - while (start < end): + while start < end: if book.GetPageText(start) > name: return start - start = start + 1 - + start += 1 return start def update_leds(self): @@ -53,7 +86,7 @@ class MessagesFrame(wx.Frame): self.timer.start() def setup_image_list(self, notebook): - imageList = wx.ImageList(24,24) + imageList = wx.ImageList(24, 24) image = wx.Image(PPRZ_HOME + "/data/pictures/gray_led24.png") bitmap = wx.BitmapFromImage(image) @@ -66,7 +99,7 @@ class MessagesFrame(wx.Frame): notebook.AssignImageList(imageList) def add_new_aircraft(self, ac_id): - self.aircrafts[ac_id] = messages_tool.Aircraft(ac_id) + self.aircrafts[ac_id] = Aircraft(ac_id) ac_panel = wx.Panel(self.notebook, -1) self.notebook.AddPage(ac_panel, str(ac_id)) messages_book = wx.Notebook(ac_panel, style=wx.NB_LEFT) @@ -77,14 +110,14 @@ class MessagesFrame(wx.Frame): sizer.Layout() self.aircrafts[ac_id].messages_book = messages_book - def add_new_message(self, aircraft, name): + def add_new_message(self, aircraft, msg_class, name): messages_book = aircraft.messages_book - aircraft.messages[name] = messages_tool.Message("telemetry", name) + aircraft.messages[name] = Message(msg_class, name) field_panel = wx.Panel(messages_book) grid_sizer = wx.FlexGridSizer(len(aircraft.messages[name].field_names), 2) index = self.find_page(messages_book, name) - messages_book.InsertPage(index, field_panel, name, imageId = 1) + messages_book.InsertPage(index, field_panel, name, imageId=1) aircraft.messages[name].index = index # update indexes of pages which are to be moved @@ -109,22 +142,22 @@ class MessagesFrame(wx.Frame): field_panel.SetSizer(grid_sizer) field_panel.Layout() - def gui_update(self, ac_id, name, values): + def gui_update(self, msg_class, msg_name, ac_id, values): if ac_id not in self.aircrafts: self.add_new_aircraft(ac_id) aircraft = self.aircrafts[ac_id] - if name not in aircraft.messages: - self.add_new_message(aircraft, name) + if msg_name not in aircraft.messages: + self.add_new_message(aircraft, msg_class, msg_name) - aircraft.messages_book.SetPageImage(aircraft.messages[name].index, 1) - self.aircrafts[ac_id].messages[name].last_seen = time.time() + aircraft.messages_book.SetPageImage(aircraft.messages[msg_name].index, 1) + self.aircrafts[ac_id].messages[msg_name].last_seen = time.time() for index in range(0, len(values)): - aircraft.messages[name].field_controls[index].SetLabel(values[index]) + aircraft.messages[msg_name].field_controls[index].SetLabel(values[index]) - def __init__(self): + def __init__(self, msg_class="telemetry"): wx.Frame.__init__(self, id=-1, parent=None, name=u'MessagesFrame', size=wx.Size(WIDTH, HEIGHT), style=wx.DEFAULT_FRAME_STYLE, title=u'Messages') self.Bind(wx.EVT_CLOSE, self.OnClose) self.notebook = wx.Notebook(self) @@ -136,9 +169,10 @@ class MessagesFrame(wx.Frame): sizer.Layout() self.timer = threading.Timer(0.1, self.update_leds) self.timer.start() - self.interface = messages_tool.IvyMessagesInterface(self.message_recv) + self.msg_class = msg_class + self.interface = IvyMessagesInterface(self.message_recv) def OnClose(self, event): self.timer.cancel() - self.interface.Shutdown() + self.interface.shutdown() self.Destroy() diff --git a/sw/lib/python/messages_tool.py b/sw/lib/python/ivy_msg_interface.py similarity index 51% rename from sw/lib/python/messages_tool.py rename to sw/lib/python/ivy_msg_interface.py index 7eaa067024..62130146ae 100644 --- a/sw/lib/python/messages_tool.py +++ b/sw/lib/python/ivy_msg_interface.py @@ -1,39 +1,24 @@ from __future__ import absolute_import, print_function -import messages_xml_map from ivy.std_api import * import logging -import time import os +import sys import re -class Message: - def __init__(self, class_name, name): - messages_xml_map.ParseMessages() - self.field_value = [] - self.field_names = messages_xml_map.message_dictionary[class_name][name] - self.field_controls = [] - self.index = None - self.last_seen = time.clock() - self.name = name - -class Aircraft: - def __init__(self, id): - self.ac_id = id - self.messages = {} - self.messages_book = None class IvyMessagesInterface(): - def __init__(self, callback, initIvy = True): + def __init__(self, callback, init=True, verbose=True, bind_regex="(.*)"): self.callback = callback self.ivy_id = 0 - self.InitIvy(initIvy) + self.verbose = verbose + self.init_ivy(init, bind_regex) - def Stop(self): + def stop(self): IvyUnBindMsg(self.ivy_id) - def Shutdown(self): - self.Stop() + def shutdown(self): + self.stop() IvyStop() def __init__del__(self): @@ -42,14 +27,14 @@ class IvyMessagesInterface(): except: pass - def InitIvy(self, initIvy): - if initIvy: + def init_ivy(self, init, bind_regex): + if init: IvyInit("Messages %i" % os.getpid(), "READY", 0, lambda x,y: y, lambda x,y: y) logging.getLogger('Ivy').setLevel(logging.WARN) IvyStart("") - self.ivy_id = IvyBindMsg(self.OnIvyMsg, "(.*)") + self.ivy_id = IvyBindMsg(self.on_ivy_msg, bind_regex) - def OnIvyMsg(self, agent, *larg): + def on_ivy_msg(self, agent, *larg): """ Split ivy message up into the separate parts Basically parts/args in string are separated by space, but char array can also contain a space: |f,o,o, ,b,a,r| in old format or "foo bar" in new format @@ -65,20 +50,28 @@ class IvyMessagesInterface(): data += s.split(' ') else: data.append(s) - try: - ac_id = int(data[0]) - name = data[1] + # ignore ivy message with less than 3 elements + if len(data) < 3: + return + + # check which message class it is + # pass non-telemetry messages with ac_id 0 + if data[0] in ["ground", "ground_dl", "dl"]: + msg_class = data[0] + msg_name = data[1] + ac_id = 0 values = list(filter(None, data[2:])) - self.callback(ac_id, name, values) - except ValueError: - pass - except: - raise - -def test(): - message = Message("WHIRLY") - print(message) - print(message.field_names) - -if __name__ == '__main__': - test() + elif data[0] == "sim": + return + else: + try: + ac_id = int(data[0]) + except ValueError: + if self.verbose: + print("ignoring message " + ' '.join(data)) + sys.stdout.flush() + return + msg_class = "telemetry" + msg_name = data[1] + values = list(filter(None, data[2:])) + self.callback(msg_class, msg_name, ac_id, values) diff --git a/sw/lib/python/messages_xml_map.py b/sw/lib/python/messages_xml_map.py index 9cf660677f..d29cd5b5f6 100755 --- a/sw/lib/python/messages_xml_map.py +++ b/sw/lib/python/messages_xml_map.py @@ -3,43 +3,28 @@ from __future__ import absolute_import, print_function import os -import sys -import getopt -messages_path = '%s/conf/messages.xml' % os.getenv("PAPARAZZI_HOME") +default_messages_file = '%s/conf/messages.xml' % os.getenv("PAPARAZZI_HOME") message_dictionary = {} message_dictionary_types = {} message_dictionary_id_name = {} message_dictionary_name_id = {} -def Usage(scmd): - lpathitem = scmd.split('/') - fmt = '''Usage: %s [-h | --help] [-f FILE | --file=FILE] -where -\t-h | --help print this message -\t-f FILE | --file=FILE where FILE is path to messages.xml -''' - print(fmt % lpathitem[-1]) -def GetOptions(): - try: - optlist, left_args = getopt.getopt(sys.argv[1:],'hf:', ['help','file=']) - except getopt.GetoptError: - # print help information and exit: - Usage(sys.argv[0]) - sys.exit(2) - for o, a in optlist: - if o in ("-h", "--help"): - Usage(sys.argv[0]) - sys.exit() - elif o in ("-f", "--file"): - messages_path = a +class MessagesNotFound(Exception): + def __init__(self, filename): + self.filename = filename + + def __str__(self): + return "messages file " + repr(self.filename) + " not found" -def ParseMessages(): +def parse_messages(messages_file=default_messages_file): + if not os.path.isfile(messages_file): + raise MessagesNotFound(messages_file) from lxml import etree - tree = etree.parse( messages_path) + tree = etree.parse(messages_file) for the_class in tree.xpath("//msg_class[@name]"): class_name = the_class.attrib['name'] if class_name not in message_dictionary: @@ -53,7 +38,7 @@ def ParseMessages(): message_id = the_message.attrib['id'] else: message_id = the_message.attrib['ID'] - if (message_id[0:2] == "0x"): + if message_id[0:2] == "0x": message_id = int(message_id, 16) else: message_id = int(message_id) @@ -67,12 +52,22 @@ def ParseMessages(): for the_field in the_message.xpath('field[@name]'): # for now, just save the field names -- in the future maybe expand this to save a struct? - message_dictionary[class_name][message_name].append( the_field.attrib['name']) - message_dictionary_types[class_name][message_id].append( the_field.attrib['type']) + message_dictionary[class_name][message_name].append(the_field.attrib['name']) + message_dictionary_types[class_name][message_id].append(the_field.attrib['type']) + def test(): - GetOptions() - ParseMessages() + import argparse + parser = argparse.ArgumentParser() + parser.add_argument("-f", "--file", help="path to messages.xml file", default=default_messages_file) + parser.add_argument("-l", "--list", help="list parsed messages", action="store_true", dest="list_messages") + parser.add_argument("-c", "--class", help="message class", dest="msg_class", default="telemetry") + args = parser.parse_args() + parse_messages(args.file) + if args.list_messages: + print("Listing %i messages in '%s' msg_class" % (len(message_dictionary[args.msg_class]), args.msg_class)) + for msg in message_dictionary[args.msg_class]: + print(msg) if __name__ == '__main__': test()