Skip to content

Commit

Permalink
Merge pull request #1073 from paparazzi/python_ivy_msg_interface
Browse files Browse the repository at this point in the history
Rewrote IvyMessagesInterface to also pass the msg_class to the callback and tried to clean it up a little.

closes #1072
  • Loading branch information
flixr committed Jan 28, 2015
2 parents 1c48e17 + 97df5d9 commit e4d9cc2
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 105 deletions.
10 changes: 2 additions & 8 deletions 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()
Expand Down
82 changes: 58 additions & 24 deletions sw/ground_segment/python/messages_app/messagesframe.py
Expand Up @@ -4,38 +4,71 @@
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
DATA_WIDTH = 100
HEIGHT = 800
BORDER = 1


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, 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:
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, ac_id, name, values)
wx.CallAfter(self.gui_update, msg_class, msg_name, ac_id, values)

def find_page(self, book, name):
if book.GetPageCount() < 1:
return 0
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):
Expand All @@ -53,7 +86,7 @@ def update_leds_real(self):
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)
Expand All @@ -66,7 +99,7 @@ def setup_image_list(self, notebook):
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)
Expand All @@ -77,14 +110,14 @@ def add_new_aircraft(self, ac_id):
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
Expand All @@ -109,22 +142,22 @@ def add_new_message(self, aircraft, name):
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)
Expand All @@ -136,9 +169,10 @@ def __init__(self):
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()
@@ -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):
Expand All @@ -42,14 +27,14 @@ def __init__del__(self):
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
Expand All @@ -65,20 +50,28 @@ def OnIvyMsg(self, agent, *larg):
data += s.split(' ')
else:
data.append(s)
try:
ac_id = int(data[0])
name = data[1]
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)
# ignore ivy message with less than 3 elements
if len(data) < 3:
return

if __name__ == '__main__':
test()
# 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:]))
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)
57 changes: 26 additions & 31 deletions sw/lib/python/messages_xml_map.py
Expand Up @@ -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:
Expand All @@ -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)
Expand All @@ -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()

0 comments on commit e4d9cc2

Please sign in to comment.