diff --git a/sw/logalizer/Makefile b/sw/logalizer/Makefile index 6bfe1ca8ab..3593875606 100644 --- a/sw/logalizer/Makefile +++ b/sw/logalizer/Makefile @@ -25,7 +25,7 @@ Q=@ OCAMLC = ocamlc OCAMLOPT = ocamlopt -INCLUDES= -I +xml-light +INCLUDES= -I +xml-light -I +lablgtk2 -I ../lib/ocaml all: play plotter plot @@ -33,13 +33,28 @@ play : log_file.ml play.ml @echo OL $@ $(Q)$(OCAMLC) $(INCLUDES) -custom -o $@ unix.cma str.cma xml-light.cma glibivy-ocaml.cma -I +lablgtk2 -I ../lib/ocaml lablgtk.cma lib-pprz.cma gtkInit.cmo $^ -plotter : plotter.ml +plotter : plotter.cmo @echo OL $@ - $(Q)$(OCAMLC) $(INCLUDES) -custom -o $@ unix.cma str.cma xml-light.cma glibivy-ocaml.cma -I +lablgtk2 -I ../lib/ocaml lablgtk.cma lib-pprz.cma gtkInit.cmo $^ + $(Q)$(OCAMLC) $(INCLUDES) -custom -o $@ unix.cma str.cma xml-light.cma glibivy-ocaml.cma lablgtk.cma lib-pprz.cma gtkInit.cmo $^ -plot : log_file.ml plot.ml +plot : log_file.cmx gtk_export.cmx export.cmx plot.cmx @echo OL $@ - $(Q)$(OCAMLOPT) $(INCLUDES) -o $@ unix.cmxa str.cmxa xml-light.cmxa glibivy-ocaml.cmxa -I +lablgtk2 -I ../lib/ocaml lablgtk.cmxa lib-pprz.cmxa gtkInit.cmx $^ + $(Q)$(OCAMLOPT) $(INCLUDES) -o $@ unix.cmxa str.cmxa xml-light.cmxa glibivy-ocaml.cmxa lablgtk.cmxa lib-pprz.cmxa lablglade.cmxa gtkInit.cmx $^ + +%.cmo: %.ml + @echo OC $< + $(Q)$(OCAMLC) $(OCAMLCFLAGS) $(INCLUDES) -c $< +%.cmi: %.mli + @echo OCI $< + $(Q)$(OCAMLC) $(OCAMLCFLAGS) $(INCLUDES) -c $< +%.cmx: %.ml + @echo OOC $< + $(Q)$(OCAMLOPT) $(OCAMLCFLAGS) $(INCLUDES) -c $< + + + +gtk_export.ml : export.glade + lablgladecc2 -root export -hide-default $< | grep -B 1000000 " end" > $@ @@ -121,6 +136,17 @@ tmclient: tmclient.c ffjoystick: ffjoystick.c gcc -g -O2 -Wall `pkg-config glib-2.0 --cflags` -o $@ $^ `pkg-config glib-2.0 --libs` `pcre-config --libs` -lglibivy -lm - + ctrlstick: ctrlstick.c gcc -g -O2 -Wall `pkg-config glib-2.0 --cflags` -o $@ $^ `pkg-config glib-2.0 --libs` `pcre-config --libs` -lglibivy + +# +# Dependencies +# + +.depend: Makefile + ocamldep -I ../lib/ocaml *.ml* > .depend + +ifneq ($(MAKECMDGOALS),clean) +-include .depend +endif diff --git a/sw/logalizer/export.glade b/sw/logalizer/export.glade new file mode 100644 index 0000000000..2084637b3e --- /dev/null +++ b/sw/logalizer/export.glade @@ -0,0 +1,225 @@ + + + + + + + True + Save CSV + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + True + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_DIALOG + GDK_GRAVITY_NORTH_WEST + True + False + + + + True + False + 0 + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + 191 + True + True + True + False + False + True + False + False + False + + + + + 0 + True + True + + + + + + True + False + 0 + + + + True + Timestamp: + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + False + + + + + + + 0 + True + True + + + + + + True + True + True + True + 0 + 0.25 + True + + False + 4 + + + 0 + True + True + + + + + + True + s + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + True + False + + + + + + + 0 + True + True + + + + + 0 + True + True + + + + + + True + False + 0 + + + + True + True + gtk-cancel + True + GTK_RELIEF_NORMAL + True + + + 0 + True + False + + + + + + True + Save the checked aircraft values in the airframe file + True + gtk-save-as + True + GTK_RELIEF_NORMAL + True + + + 0 + True + False + + + + + 2 + False + True + + + + + + + diff --git a/sw/logalizer/export.ml b/sw/logalizer/export.ml new file mode 100644 index 0000000000..f18f96bf54 --- /dev/null +++ b/sw/logalizer/export.ml @@ -0,0 +1,198 @@ +(* + * $Id$ + * + * GUI to export some values of a log + * + * Copyright (C) 2008, Cyril Allignol, Pascal Brisset + * + * This file is part of paparazzi. + * + * paparazzi is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * paparazzi is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with paparazzi; see the file COPYING. If not, write to + * the Free Software Foundation, 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + *) + +open Printf +let (//) = Filename.concat +let class_name="telemetry" + +let cols = new GTree.column_list +let col_message = cols#add Gobject.Data.string +let col_field = cols#add Gobject.Data.string +let col_to_export = cols#add Gobject.Data.boolean +let col_visible = cols#add Gobject.Data.boolean + + +(** Toggling a tree element *) +let item_toggled ~(model : GTree.tree_store) ~column path = + let row = model#get_iter path in + let b = model#get ~row ~column in + model#set ~row ~column (not b) + + + +let display_columns = fun treeview model -> + let renderer = GTree.cell_renderer_text [`XALIGN 0.] in + let vc = GTree.view_column ~title:"Message" ~renderer:(renderer, ["text", col_message]) () in + ignore (treeview#append_column vc); + let renderer = GTree.cell_renderer_text [`XALIGN 0.] in + let vc = GTree.view_column ~title:"Field" ~renderer:(renderer, ["text", col_field]) () in + ignore (treeview#append_column vc); + + let renderer = GTree.cell_renderer_toggle [`XALIGN 0.] in + let vc = GTree.view_column ~title:"To export" ~renderer:(renderer, ["active", col_to_export; "visible", col_visible]) () in + vc#set_clickable true; + ignore (renderer#connect#toggled ~callback:(item_toggled ~model ~column:col_to_export)); + ignore (treeview#append_column vc) + + + +let fill_data = fun (model:GTree.tree_store) messages_xml -> + List.iter (fun msg -> + let row = model#append () in + model#set ~row ~column:col_message (ExtXml.attrib msg "name"); + List.iter (fun field -> + let row = model#append ~parent:row () in + model#set ~row ~column:col_visible true; + model#set ~row ~column:col_field (ExtXml.attrib field "name")) + (Xml.children msg)) + (Xml.children messages_xml) + +type timestamp = + Msg of string + | Period of float (* in s *) + +let export_values = fun (model:GTree.tree_store) data timestamp filename -> + let fields_to_export = ref [] in + model#foreach (fun _path row -> + if model#get ~row ~column:col_to_export then begin + let field = model#get ~row ~column:col_field + and parent = match model#iter_parent row with Some p -> p | None -> failwith "export_value: no parent ???" in + let msg = model#get ~row:parent ~column:col_message in + fields_to_export := (msg, field) :: !fields_to_export + end; + false); + + let f = open_out filename in + (* Print the header *) + fprintf f "Time"; + List.iter (fun (m,field) -> fprintf f ";%s:%s" m field) !fields_to_export; + fprintf f "\n%!"; + + let last_values = Hashtbl.create 97 + and time = ref (match data with (t, _, _)::_ -> t | _ -> 0.) in + + let print_last_values = fun t -> + fprintf f "%.3f" t; + List.iter + (fun (m,field) -> + let v = try Pprz.string_of_value (Hashtbl.find last_values (m,field)) with Not_found -> "" in + fprintf f ";%s" v) + !fields_to_export; + fprintf f "\n%!" in + + List.iter (fun (t, msg, fields) -> + begin + match timestamp with + | Period p -> + if t >= !time then begin + print_last_values !time; + time := !time +. p + end + | _ -> () + end; + + List.iter (fun (f, v) -> + Hashtbl.replace last_values (msg, f) v) + fields; + + match timestamp with + Msg m when m = msg -> + print_last_values t + | _ -> ()) + data; + + close_out f;; + + +(** The save file dialog box *) +let save_values = fun w log_filename save -> + let filename = Env.paparazzi_home // "var" // "logs" // log_filename ^ ".csv" in + match GToolbox.select_file ~title:"Save Values" ~filename () with + None -> () + | Some file -> + save file; + w#export#destroy () + + + +(** The popup window displaying values to export *) +let popup = fun log_filename data -> + (* Build the list window *) + let file = Env.paparazzi_src // "sw" // "logalizer" // "export.glade" in + let w = new Gtk_export.export ~file () in + let icon = GdkPixbuf.from_file Env.icon_file in + w#export#set_icon (Some icon); + + (* Build the tree model *) + let model = GTree.tree_store cols in + + (** Attach the model to the view *) + w#treeview_messages#set_model (Some model#coerce); + + (** Render the columns *) + display_columns w#treeview_messages model; + + (** Fill the colums *) + let xml = Pprz.messages_xml () in + let xml_class = ExtXml.child ~select:(fun c -> ExtXml.attrib c "name" = class_name) xml "class" in + fill_data model xml_class; + + (* The combo box for the timestamp choice *) + let strings = "Periodic" :: List.map (fun msg -> Xml.attrib msg "name") (Xml.children xml_class) in + let (combo, (tree, column)) = GEdit.combo_box_text ~packing:w#box_choose_period#add ~strings () in + tree#foreach (fun _path row -> combo#set_active_iter (Some row); true); (* Select the first *) + + let get_timestamp = fun () -> + match combo#active_iter with + | None -> failwith "get_timestamp" + | Some row -> + combo#model#get ~row ~column in + + (* Connect the timestamp chooser to the period entry *) + ignore (combo#connect#changed + (fun () -> + let data = get_timestamp () in + w#entry_period#misc#set_sensitive (data = "Periodic"))); + + + + (* The combo box for the extrapolation *) + let strings = ["Last Value"; "Linear Extrapol"] in + let (combo, (tree, column)) = GEdit.combo_box_text ~packing:w#box_choose_interpol#add ~strings () in + tree#foreach (fun _path row -> combo#set_active_iter (Some row); true); (* Select the first *) + + ignore (w#button_cancel#connect#clicked (fun () -> w#export#destroy ())); + + (** Connect the Save button to the write action *) + let callback = fun () -> + let timestamp = + let combo_value = get_timestamp () in + if combo_value = "Periodic" then + Period (float_of_string w#entry_period#text) + else + Msg combo_value in + save_values w log_filename (export_values model data timestamp) in + ignore (w#button_save#connect#clicked callback) diff --git a/sw/logalizer/plot.ml b/sw/logalizer/plot.ml index 6bd0a952af..f77a437279 100644 --- a/sw/logalizer/plot.ml +++ b/sw/logalizer/plot.ml @@ -326,7 +326,7 @@ let write_kml = fun plot log_name values -> -let add_ac_submenu = fun ?(factor=object method text="1" end) plot menubar (curves_menu_fact: GMenu.menu GMenu.factory) ac menu_name l -> +let add_ac_submenu = fun ?(factor=object method text="1" end) plot menubar (curves_menu_fact: GMenu.menu GMenu.factory) ac menu_name l raw_msgs -> let menu = GMenu.menu () in let menuitem = GMenu.menu_item ~label:menu_name () in menuitem#set_submenu menu; @@ -364,7 +364,10 @@ let add_ac_submenu = fun ?(factor=object method text="1" end) plot menubar (curv let callback = fun () -> let gps_values = List.assoc "GPS" l in write_kml plot menu_name gps_values in - ignore (menu_fact#add_item ~callback "Export KML path") + ignore (menu_fact#add_item ~callback "Export KML path"); + let callback = fun () -> + Export.popup menu_name raw_msgs in + ignore (menu_fact#add_item ~callback "Export CSV") @@ -412,8 +415,8 @@ let load_log = fun ?factor (plot:plot) (menubar:GMenu.menu_shell GMenu.factory) Scanf.sscanf l "%f %s %[^\n]" (fun t ac m -> if not (Hashtbl.mem acs ac) then - Hashtbl.add acs ac (Hashtbl.create 97); - let msgs = Hashtbl.find acs ac in + Hashtbl.add acs ac (Hashtbl.create 97, ref []); + let msgs, raw_msgs = Hashtbl.find acs ac in (*Elements of [acs] are assoc lists of [fields] indexed by msg id*) let msg_id, vs = P.values_of_string m in @@ -422,7 +425,10 @@ let load_log = fun ?factor (plot:plot) (menubar:GMenu.menu_shell GMenu.factory) let fields = Hashtbl.find msgs msg_id in (* Elements of [fields] are values indexed by field name *) - List.iter (fun (f, v) -> Hashtbl.add fields f (t, v)) vs + List.iter (fun (f, v) -> Hashtbl.add fields f (t, v)) vs; + + let msg_name = (P.message_of_id msg_id).Pprz.name in + raw_msgs := (t, msg_name, vs) :: !raw_msgs ) with exc -> prerr_endline (Printexc.to_string exc) @@ -432,7 +438,8 @@ let load_log = fun ?factor (plot:plot) (menubar:GMenu.menu_shell GMenu.factory) close_in f; (* Compile the data to ease the menu building *) Hashtbl.iter (* For all A/Cs *) - (fun ac msgs -> + (fun ac (msgs, raw_msgs) -> + let raw_msgs = List.rev !raw_msgs in let menu_name = sprintf "%s:%s" (Filename.chop_extension (Filename.basename xml_file)) ac in (* First sort by message id *) @@ -457,9 +464,9 @@ let load_log = fun ?factor (plot:plot) (menubar:GMenu.menu_shell GMenu.factory) msgs in (* Store data for other windows *) - logs_menus := (ac, menu_name, msgs) :: !logs_menus; + logs_menus := (ac, menu_name, (msgs, raw_msgs)) :: !logs_menus; - add_ac_submenu ?factor plot menubar curves_fact ac menu_name msgs; + add_ac_submenu ?factor plot menubar curves_fact ac menu_name msgs raw_msgs; ) acs @@ -582,8 +589,8 @@ let rec plot_window = fun init -> tooltips#set_tip factor#coerce ~text:"Scale next curve (e.g. 0.0174 to convert deg in rad, 57.3 to convert rad in deg, 1.8+32 to convert Celsius into Fahrenheit)"; List.iter - (fun (ac, menu_name, msgs) -> - add_ac_submenu ~factor:(factor:>text_value) plot factory curves_menu_fact ac menu_name msgs) + (fun (ac, menu_name, (msgs, raw_msgs)) -> + add_ac_submenu ~factor:(factor:>text_value) plot factory curves_menu_fact ac menu_name msgs raw_msgs) !logs_menus; ignore(open_log_item#connect#activate ~callback:(fun () -> let factor = (factor:>text_value) in open_log ~factor plot factory curves_menu_fact ()));