Files
paparazzi/sw/ground_segment/cockpit/live.ml
T
Gautier Hattenberger 5c28c13c84 [gcs] connect the 'back to nav' strip button
also remove some warnings since it seems to be more confusing than
inactive buttons in the strip
2014-04-09 22:51:15 +02:00

1436 lines
53 KiB
OCaml
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(*
* Real time handling of flying A/Cs
*
* Copyright (C) 2004-2006 ENAC, Pascal Brisset, Antoine Drouin
*
* 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.
*
*)
module G = MapCanvas
open Latlong
module LL = Latlong
open Printf
module Tele_Pprz = Pprz.Messages(struct let name = "telemetry" end)
module Ground_Pprz = Pprz.Messages(struct let name = "ground" end)
module Alert_Pprz = Pprz.Messages(struct let name = "alert" end)
let (//) = Filename.concat
let gcs_id = "GCS"
let approaching_alert_time = 3.
let track_size = ref 500
let _auto_hide_fp = ref false
let min_height = 200
let lines_height = 30
let is_int = fun x ->
try let _ = int_of_string x in true with _ -> false
let ok_modes = ["MANUAL"; "AUTO1"; "AUTO2"]
let rotate = fun a (x, y) ->
let cosa = cos a and sina = sin a in
(cosa *.x +. sina *.y, -. sina*.x +. cosa *. y)
let rec list_casso x = function
[] -> raise Not_found
| (a,b)::abs -> if x = b then a else list_casso x abs
let rec list_iter3 = fun f l1 l2 l3 ->
match l1, l2, l3 with
[], [], [] -> ()
| x1::x1s, x2::x2s, x3::x3s ->
f x1 x2 x3;
list_iter3 f x1s x2s x3s
| _ -> invalid_arg "list_iter3"
type color = string
type aircraft = {
ac_name : string;
ac_speech_name : string;
config : Pprz.values;
track : MapTrack.track;
color: color;
fp_group : MapFP.flight_plan;
fp_show : GMenu.check_menu_item;
wp_HOME : MapWaypoints.waypoint option;
fp : Xml.xml;
blocks : (int * string) list;
mutable last_ap_mode : string;
mutable last_stage : int * int;
ir_page : Pages.infrared;
gps_page : Pages.gps;
pfd_page : Horizon.pfd;
link_page : Pages.link;
misc_page : Pages.misc;
dl_settings_page : Page_settings.settings option;
rc_settings_page : Pages.rc_settings option;
pages : GObj.widget;
notebook_label : GMisc.label;
strip : Strip.t;
mutable first_pos : bool;
mutable last_block_name : string;
mutable in_kill_mode : bool;
mutable speed : float;
mutable alt : float;
mutable target_alt : float;
mutable flight_time : int;
mutable wind_speed : float;
mutable wind_dir : float; (* Rad, clockwise from North *)
mutable ground_prox : bool;
mutable got_track_status_timer : int;
mutable last_dist_to_wp : float;
mutable dl_values : float array;
mutable last_unix_time : float;
mutable airspeed : float
}
let aircrafts = Hashtbl.create 3
exception AC_not_found
let find_ac = fun ac_id ->
try
Hashtbl.find aircrafts ac_id
with
Not_found -> raise AC_not_found
let active_ac = ref ""
let get_ac = fun vs ->
let ac_id = Pprz.string_assoc "ac_id" vs in
find_ac ac_id
let show_fp = fun ac ->
ac.fp_group#show ();
ac.fp_show#set_active true
let hide_fp = fun ac ->
ac.fp_group#hide ();
ac.fp_show#set_active false
(* callback for FP check button in menu *)
let show_mission = fun ac on_off ->
let a = find_ac ac in
if on_off then
a.fp_group#show ()
else
a.fp_group#hide ()
let auto_hide_fp = fun hide ->
let _hide_fp = fun () ->
Hashtbl.iter (fun _ a -> hide_fp a) aircrafts;
if !active_ac <> "" then begin
let a = find_ac !active_ac in
show_fp a
end;
in
_auto_hide_fp := hide;
if hide then _hide_fp () else Hashtbl.iter (fun _ a -> show_fp a) aircrafts
let select_ac = fun acs_notebook ac_id ->
if !active_ac <> ac_id then
let ac = Hashtbl.find aircrafts ac_id in
(* Show the buttons in the active strip and hide the previous active one *)
ac.strip#show_buttons ();
if !active_ac <> "" then begin
let ac' = find_ac !active_ac in
ac'.strip#hide_buttons ();
ac'.notebook_label#set_width_chars (String.length ac'.notebook_label#text);
if !_auto_hide_fp then hide_fp ac'
end;
(* Set the new active *)
active_ac := ac_id;
if !_auto_hide_fp then show_fp ac;
(* Select and enlarge the label of the A/C notebook *)
let n = acs_notebook#page_num ac.pages in
acs_notebook#goto_page n;
ac.notebook_label#set_width_chars 20;
module M = Map.Make (struct type t = string let compare = compare end)
let log =
let last = ref M.empty in
fun ?(say = false) (a:Pages.alert) ac_id s ->
if not (M.mem ac_id !last) || M.find ac_id !last <> s then begin
last := M.add ac_id s (M.remove ac_id !last);
if say then Speech.say s;
a#add s
end
let log_and_say = fun a ac_id s -> log ~say:true a ac_id s
let resize_track = fun ac track ->
match
GToolbox.input_string ~text:(string_of_int track#size) ~title:ac "Track size"
with
None -> ()
| Some s -> track#resize (int_of_string s)
let send_move_waypoint_msg = fun ac i w ->
let wgs84 = w#pos in
let vs = ["ac_id", Pprz.String ac;
"wp_id", Pprz.Int i;
"lat", Pprz.Float ((Rad>>Deg)wgs84.posn_lat);
"long", Pprz.Float ((Rad>>Deg)wgs84.posn_long);
"alt", Pprz.Float w#alt
] in
Ground_Pprz.message_send "gcs" "MOVE_WAYPOINT" vs
let commit_changes = fun ac ->
let a = find_ac ac in
List.iter
(fun w ->
let (i, w) = a.fp_group#index w in
if w#moved then
send_move_waypoint_msg ac i w)
a.fp_group#waypoints
let center = fun geomap track () ->
match track#last with
None -> ()
| Some geo ->
geomap#center geo;
geomap#canvas#misc#draw None
let blocks_of_stages = fun stages ->
let blocks = ref [] in
List.iter (fun x ->
let name = ExtXml.attrib x "block_name"
and id = ExtXml.int_attrib x "block" in
if not (List.mem_assoc id !blocks) then
blocks := (id, name) :: !blocks)
(Xml.children stages);
List.sort compare !blocks
let jump_to_block = fun ac_id id ->
Ground_Pprz.message_send "gcs" "JUMP_TO_BLOCK"
["ac_id", Pprz.String ac_id; "block_id", Pprz.Int id]
let dl_setting = fun ac_id idx value ->
let vs = ["ac_id", Pprz.String ac_id; "index", Pprz.Int idx;"value", Pprz.Float value] in
Ground_Pprz.message_send "dl" "DL_SETTING" vs
let get_dl_setting = fun ac_id idx ->
let vs = ["ac_id", Pprz.String ac_id; "index", Pprz.Int idx] in
Ground_Pprz.message_send "dl" "GET_DL_SETTING" vs
let menu_entry_of_block = fun ac_id (id, name) ->
let send_msg = fun () -> jump_to_block ac_id id in
`I (name, send_msg)
let reset_waypoints = fun fp () ->
List.iter (fun w ->
let (_i, w) = fp#index w in
w#reset_moved ())
fp#waypoints
let icon = ref None
let show_snapshot = fun (geomap:G.widget) geo_FL geo_BR point pixbuf name ev ->
match ev with
| `BUTTON_PRESS _ev ->
let image = GMisc.image ~pixbuf () in
let icon = image#coerce in
begin
match GToolbox.question_box ~title:name ~buttons:["Delete"; "Close"] ~icon "" with
1 ->
point#destroy ()
| _ -> ()
end;
true
| `LEAVE_NOTIFY _ev ->
begin
match !icon with
None -> ()
| Some i -> i#destroy ()
end;
false
| `ENTER_NOTIFY _ev ->
let w = GdkPixbuf.get_width pixbuf
and h = GdkPixbuf.get_height pixbuf in
icon := Some (geomap#display_pixbuf ((0,0), geo_FL) ((w,h), geo_BR) pixbuf);
point#raise_to_top ();
false
| _ -> false
let mark = fun (geomap:G.widget) ac_id track plugin_frame ->
let i = ref 1 in fun () ->
match track#last with
Some geo ->
begin
let group = geomap#background in
let point = geomap#circle ~group ~fill_color:"blue" geo 5. in
point#raise_to_top ();
let lat = (Rad>>Deg)geo.posn_lat
and long = (Rad>>Deg)geo.posn_long in
Tele_Pprz.message_send ac_id "MARK"
["ac_id", Pprz.String ac_id;
"lat", Pprz.Float lat;
"long", Pprz.Float long];
let frame =
match plugin_frame with
None -> geomap#canvas#coerce
| Some pf -> pf#coerce in
let width, height = Gdk.Drawable.get_size frame#misc#window in
let dest = GdkPixbuf.create width height() in
GdkPixbuf.get_from_drawable ~dest ~width ~height frame#misc#window;
let name = sprintf "Snapshot-%s-%d_%f_%f_%f.png" ac_id !i lat long (track#last_heading) in
let png = sprintf "%s/var/logs/%s" Env.paparazzi_home name in
GdkPixbuf.save png "png" dest;
incr i;
(* Computing the footprint: front_left and back_right *)
let cam_aperture = 2.4/.1.9 in (* width over distance FIXME *)
let alt = track#last_altitude -. float (Srtm.of_wgs84 geo) in
let width = cam_aperture *. alt in
let height = width *. 3. /. 4. in
let utm = utm_of WGS84 geo in
let a = (Deg>>Rad)track#last_heading in
let (xfl,yfl) = rotate a (-.width/.2., height/.2.)
and (xbr,ybr) = rotate a (width/.2., -.height/.2.) in
let geo_FL = of_utm WGS84 (utm_add utm (xfl,yfl))
and geo_BR = of_utm WGS84 (utm_add utm (xbr,ybr)) in
ignore (point#connect#event (show_snapshot geomap geo_FL geo_BR point dest name))
end
| None -> ()
(** Light display of attributes in the flight plan. *)
let attributes_pretty_printer = fun attribs ->
(* Remove the optional attributes *)
let valid = fun a ->
let a = String.lowercase a in
a <> "no" && a <> "strip_icon" && a <> "strip_button" && a <> "pre_call"
&& a <> "post_call" && a <> "key" && a <> "group" in
let sprint_opt = fun b s ->
if String.length b > 0 then
sprintf " %s%s%s" s b s
else
""
in
let elt = Xml.Element("", attribs, []) in
let pre_call = ExtXml.attrib_or_default elt "pre_call" ""
and post_call = ExtXml.attrib_or_default elt "post_call" "" in
let attribs = List.filter (fun (a, _) -> valid a) attribs in
(* Don't print the name of the attribute if there is only one *)
match attribs with
[(_, v)] -> v ^ sprint_opt pre_call "]" ^ sprint_opt post_call "["
| _ -> XmlEdit.string_of_attribs attribs
(** Load a mission. Returns the XML window *)
let load_mission = fun ?editable color geomap xml ->
Map2d.set_georef_if_none geomap (MapFP.georef_of_xml xml);
new MapFP.flight_plan ~format_attribs:attributes_pretty_printer ?editable ~show_moved:true geomap color Env.flight_plan_dtd xml
let get_bat_levels = fun af_xml ->
let default_catastrophic_level = 9.
and default_max_level = 12.5 in
try
let bat_section = ExtXml.child af_xml ~select:(fun x -> Xml.attrib x "name" = "BAT") "section" in
let fvalue = fun name default ->
try ExtXml.float_attrib (ExtXml.child bat_section ~select:(fun x -> ExtXml.attrib x "name" = name) "define") "value" with _ -> default in
fvalue "CATASTROPHIC_BAT_LEVEL" default_catastrophic_level, fvalue "MAX_BAT_LEVEL" default_max_level
with _ -> (default_catastrophic_level, default_max_level)
let get_alt_shift = fun af_xml ->
let default_plus_plus = 30.
and default_plus = 5.
and default_minus = -5. in
try
let gcs_section = ExtXml.child af_xml ~select:(fun x -> Xml.attrib x "name" = "GCS") "section" in
let fvalue = fun name default ->
try ExtXml.float_attrib (ExtXml.child gcs_section ~select:(fun x -> ExtXml.attrib x "name" = name) "define") "value" with _ -> default in
fvalue "ALT_SHIFT_PLUS_PLUS" default_plus_plus,
fvalue "ALT_SHIFT_PLUS" default_plus,
fvalue "ALT_SHIFT_MINUS" default_minus
with _ -> (default_plus_plus, default_plus, default_minus)
let get_speech_name = fun af_xml def_name ->
let default_speech_name = def_name in
try
let gcs_section = ExtXml.child af_xml ~select:(fun x -> Xml.attrib x "name" = "GCS") "section" in
let fvalue = fun name default ->
try ExtXml.attrib (ExtXml.child gcs_section ~select:(fun x -> ExtXml.attrib x "name" = name) "define") "value" with _ -> default in
fvalue "SPEECH_NAME" default_speech_name
with _ -> default_speech_name
let get_icon_and_track_size = fun af_xml ->
(* firmware name as default if fixedwing or rotorcraft *)
let firmware = ExtXml.child af_xml "firmware" in
let firmware_name = ExtXml.attrib firmware "name" in
try
(* search AC_ICON in GCS section *)
let gcs_section = ExtXml.child af_xml ~select:(fun x -> Xml.attrib x "name" = "GCS") "section" in
let fvalue = fun name default ->
try ExtXml.attrib (ExtXml.child gcs_section ~select:(fun x -> ExtXml.attrib x "name" = name) "define") "value" with _ -> default in
match fvalue "AC_ICON" "fixedwing" with
| "home" -> ("home", 1) (* no track for home icon *)
| x -> (x, !track_size)
with _ -> (firmware_name, !track_size)
let key_press_event = fun keys do_action ev ->
try
let (modifiers, action) = List.assoc (GdkEvent.Key.keyval ev) keys in
let ev_modifiers = GdkEvent.Key.state ev in
if List.for_all (fun m -> List.mem m ev_modifiers) modifiers then begin
do_action action;
true
end else
false
with
| _ -> false
(*****************************************************************************)
let create_ac = fun alert (geomap:G.widget) (acs_notebook:GPack.notebook) (ac_id:string) config ->
let color = Pprz.string_assoc "default_gui_color" config
and name = Pprz.string_assoc "ac_name" config in
(** Get the flight plan **)
let fp_url = Pprz.string_assoc "flight_plan" config in
let fp_file = Http.file_of_url fp_url in
let fp_xml_dump = ExtXml.parse_file ~noprovedtd:true fp_file in
let stages = ExtXml.child fp_xml_dump "stages" in
let blocks = blocks_of_stages stages in
(** Get the airframe file *)
let af_url = Pprz.string_assoc "airframe" config in
let af_file = Http.file_of_url af_url in
let af_xml = ExtXml.parse_file af_file in
(** Get an alternate speech name if available *)
let speech_name = get_speech_name af_xml name in
(* Aicraft menu decorated with a colored box *)
let image = GBin.event_box ~width:10 ~height:10 () in
image#coerce#misc#modify_bg [`NORMAL, `NAME color];
let ac_mi = GMenu.image_menu_item ~label:name ~image ~packing:geomap#menubar#append () in
let ac_menu = GMenu.menu () in
ac_mi#set_submenu ac_menu;
let ac_menu_fact = new GMenu.factory ac_menu in
let fp_show = ac_menu_fact#add_check_item "Fligh Plan" ~active:true in
ignore (fp_show#connect#toggled (fun () -> show_mission ac_id fp_show#active));
let (icon, size) = get_icon_and_track_size af_xml in
let track = new MapTrack.track ~size ~icon ~name ~color:color geomap in
geomap#register_to_fit (track:>MapCanvas.geographic);
let center_ac = center geomap track in
ignore (ac_menu_fact#add_item "Center A/C" ~callback:center_ac);
ignore (ac_menu_fact#add_item "Clear Track" ~callback:(fun () -> track#clear_map2D));
ignore (ac_menu_fact#add_item "Resize Track" ~callback:(fun () -> resize_track ac_id track));
let reset_wp_menu = ac_menu_fact#add_item "Reset Waypoints" in
let jump_block_entries = List.map (menu_entry_of_block ac_id) blocks in
let commit_moves = fun () ->
commit_changes ac_id in
let sm = ac_menu_fact#add_submenu "Datalink" in
let dl_menu = [
`M ("Jump to block", jump_block_entries);
`I ("Commit Moves", commit_moves)] in
GToolbox.build_menu sm ~entries:dl_menu;
let cam = ac_menu_fact#add_check_item "Cam footprint" ~active:false in
ignore (cam#connect#toggled (fun () -> track#set_cam_state cam#active));
let params = ac_menu_fact#add_check_item "A/C label" ~active:false in
ignore (params#connect#toggled (fun () -> track#set_params_state params#active));
(** Add a new tab in the A/Cs notebook, with a colored label *)
let eb = GBin.event_box () in
let _label = GMisc.label ~text:name ~packing:eb#add () in
eb#coerce#misc#modify_bg [`NORMAL, `NAME color;`ACTIVE, `NAME color];
(** Put a notebook for this A/C *)
let ac_frame = GBin.frame () in
ignore (acs_notebook#append_page ~tab_label:eb#coerce ac_frame#coerce);
let ac_notebook = GPack.notebook ~packing: ac_frame#add () in
let visible = fun w ->
ac_notebook#page_num w#coerce = ac_notebook#current_page in
(** Add a strip *)
let min_bat, max_bat = get_bat_levels af_xml in
let alt_shift_plus_plus, alt_shift_plus, alt_shift_minus = get_alt_shift af_xml in
let param = { Strip.color = color; min_bat = min_bat; max_bat = max_bat;
alt_shift_plus_plus = alt_shift_plus_plus;
alt_shift_plus = alt_shift_plus;
alt_shift_minus = alt_shift_minus; } in
(*let strip = Strip.add config color min_bat max_bat in*)
let strip = Strip.add config param in
strip#connect (fun () -> select_ac acs_notebook ac_id);
strip#connect_mark (mark geomap ac_id track !Plugin.frame);
(** Build the XML flight plan, connect then "jump_to_block" *)
let fp_xml = ExtXml.child fp_xml_dump "flight_plan" in
let fp = load_mission ~editable:false color geomap fp_xml in
fp#connect_activated (fun node ->
if XmlEdit.tag node = "block" then
let block = XmlEdit.attrib node "name" in
let id = list_casso block blocks in
jump_to_block ac_id id);
ignore (reset_wp_menu#connect#activate (reset_waypoints fp));
(** Monitor waypoints changes *)
List.iter
(fun w ->
let (i, w) = fp#index w in
w#set_commit_callback (fun () -> send_move_waypoint_msg ac_id i w))
fp#waypoints;
(** Add waypoints as geo references *)
List.iter
(fun w ->
let (_i, w) = fp#index w in
geomap#add_info_georef (sprintf "%s.%s" name w#name) (w :> < pos : Latlong.geographic >))
fp#waypoints;
(** Add the short cut buttons in the strip *)
let tooltips = GData.tooltips () in
let keys = ref [] in (* Associations between keys and block ids *)
List.iter (fun block ->
let id = ExtXml.int_attrib block "no" in
begin (* Is it a key short cut ? *)
try
let key, modifiers = GtkData.AccelGroup.parse (Pprz.key_modifiers_of_string (Xml.attrib block "key")) in
keys := (key, (modifiers, id)) :: !keys
with
_ -> ()
end;
try (* Is it a strip button ? *)
let label = ExtXml.attrib block "strip_button"
and block_name = ExtXml.attrib block "name"
and group = ExtXml.attrib_or_default block "group" "" in
let b =
try (* Is it an icon ? *)
let icon = Xml.attrib block "strip_icon" in
let b = GButton.button () in
let pixbuf = GdkPixbuf.from_file (Env.gcs_icons_path // icon) in
ignore (GMisc.image ~pixbuf ~packing:b#add ());
(* Drag for Drop *)
let papget = Papget_common.xml "goto_block" "button"
[ "block_name", block_name;
"icon", icon] in
Papget_common.dnd_source b#coerce papget;
(* Associates the label as a tooltip *)
tooltips#set_tip b#coerce ~text:label;
b
with
Xml.No_attribute _ -> (* It's not an icon *)
GButton.button ~label ()
| exc ->
fprintf stderr "Error: '%s' Using a standard button" (Printexc.to_string exc);
GButton.button ~label ()
in
strip#add_widget b#coerce ~group;
ignore (b#connect#clicked (fun _ -> jump_to_block ac_id id))
with
_ -> ())
(Xml.children (ExtXml.child (ExtXml.child fp_xml_dump "flight_plan") "blocks"));
(** Handle key shortcuts for block selection *)
let key_press = key_press_event !keys (fun block_id -> jump_to_block ac_id block_id) in
ignore (geomap#canvas#event#connect#after#key_press key_press);
(** Insert the flight plan tab *)
let fp_label = GMisc.label ~text: "Flight Plan" () in
ignore ((ac_notebook:GPack.notebook)#append_page ~tab_label:fp_label#coerce fp#window#coerce);
let infrared_label = GMisc.label ~text: "Infrared" () in
let infrared_frame = GBin.frame ~show:false ~shadow_type:`NONE () in
ignore (ac_notebook#append_page ~tab_label: infrared_label#coerce infrared_frame#coerce);
let ir_page = new Pages.infrared infrared_frame in
let gps_label = GMisc.label ~text: "GPS" () in
let gps_frame = GBin.frame ~shadow_type: `NONE () in
ignore (ac_notebook#append_page ~tab_label: gps_label#coerce gps_frame#coerce);
let gps_page = new Pages.gps ~visible gps_frame in
let pfd_label = GMisc.label ~text: "PFD" () in
let pfd_frame = GBin.frame ~shadow_type: `NONE () in
ignore (ac_notebook#append_page ~tab_label: pfd_label#coerce pfd_frame#coerce);
let pfd_page = new Horizon.pfd pfd_frame
and _pfd_page_num = ac_notebook#page_num pfd_frame#coerce in
let link_label = GMisc.label ~text: "Link" () in
let link_frame = GBin.frame ~shadow_type: `NONE () in
ignore (ac_notebook#append_page ~tab_label: link_label#coerce link_frame#coerce);
let link_page = new Pages.link ~visible link_frame in
let misc_label = GMisc.label ~text: "Misc" () in
let misc_frame = GBin.frame ~shadow_type: `NONE () in
ignore (ac_notebook#append_page ~tab_label:misc_label#coerce misc_frame#coerce);
let misc_page = new Pages.misc ~packing:misc_frame#add misc_frame in
let settings_url = Pprz.string_assoc "settings" config in
let settings_file = Http.file_of_url settings_url in
let settings_xml =
try
if String.compare "replay" settings_file <> 0 then
ExtXml.parse_file ~noprovedtd:true settings_file
else
Xml.Element("empty", [], [])
with exc ->
prerr_endline (Printexc.to_string exc);
Xml.Element("empty", [], [])
in
let dl_setting_callback = fun idx value ->
if classify_float value = FP_normal || classify_float value = FP_zero then
dl_setting ac_id idx value
else
get_dl_setting ac_id idx
in
let dl_settings_page =
try
let xml_settings = Xml.children (ExtXml.child settings_xml "dl_settings") in
let settings_tab = new Page_settings.settings ~visible xml_settings dl_setting_callback (fun group x -> strip#add_widget ~group x) in
(** Connect key shortcuts *)
let key_press = fun ev ->
key_press_event settings_tab#keys (fun commit -> commit ()) ev in
ignore (geomap#canvas#event#connect#after#key_press key_press);
let tab_label = GPack.hbox () in
let _label = (GMisc.label ~text:"Settings" ~packing:tab_label#pack ()) in
let button_save_settings = GButton.button ~packing:tab_label#pack () in
ignore (GMisc.image ~stock:`SAVE ~packing:button_save_settings#add ());
button_save_settings#set_border_width 0;
ignore (button_save_settings#connect#clicked (fun () -> settings_tab#save af_file));
ignore (ac_notebook#append_page ~tab_label:tab_label#coerce settings_tab#widget);
Some settings_tab
with exc ->
log alert ac_id (Printexc.to_string exc);
None in
let rc_settings_page =
try
let xml_settings = Xml.children (ExtXml.child settings_xml "rc_settings") in
if xml_settings = [] then
raise Exit
else
let settings_tab = new Pages.rc_settings ~visible xml_settings in
let tab_label = (GMisc.label ~text:"RC Settings" ())#coerce in
ignore (ac_notebook#append_page ~tab_label settings_tab#widget);
Some settings_tab
with _ -> None in
let wp_HOME =
let rec loop = function
[] -> None
| w::ws ->
let (_i, w) = fp#index w in
if w#name = "HOME" then Some w else loop ws in
loop fp#waypoints in
let ac = { track = track; color = color; last_dist_to_wp = 0.;
fp_group = fp; fp_show = fp_show ; config = config ;
wp_HOME = wp_HOME; fp = fp_xml;
ac_name = name; ac_speech_name = speech_name;
blocks = blocks; last_ap_mode= "";
last_stage = (-1,-1);
ir_page = ir_page; flight_time = 0;
gps_page = gps_page;
pfd_page = pfd_page;
link_page = link_page;
misc_page = misc_page;
dl_settings_page = dl_settings_page;
rc_settings_page = rc_settings_page;
strip = strip; first_pos = true;
last_block_name = ""; alt = 0.; target_alt = 0.;
in_kill_mode = false; speed = 0.;
wind_dir = 42.; ground_prox = true;
wind_speed = 0.;
pages = ac_frame#coerce;
notebook_label = _label;
got_track_status_timer = 1000;
dl_values = [||]; last_unix_time = 0.;
airspeed = 0.
} in
Hashtbl.add aircrafts ac_id ac;
select_ac acs_notebook ac_id;
(** Periodically send the wind estimation through
a WIND_INFO message packed into a RAW_DATALINK *)
let send_wind = fun () ->
if misc_page#periodic_send then begin
(* FIXME: Disabling the timer would be preferable *)
try
let a = (pi/.2. -. ac.wind_dir)
and w = ac.wind_speed in
let wind_east = sprintf "%.1f" (-. cos a *. w)
and wind_north = sprintf "%.1f" (-. sin a *. w)
and airspeed = sprintf "%.1f" ac.airspeed in
let msg_items = ["WIND_INFO"; ac_id; "42"; wind_east; wind_north;airspeed] in
let value = String.concat ";" msg_items in
let vs = ["ac_id", Pprz.String ac_id; "message", Pprz.String value] in
Ground_Pprz.message_send "dl" "RAW_DATALINK" vs;
with
exc -> log alert ac_id (sprintf "send_wind (%s): %s" ac_id (Printexc.to_string exc))
end;
true
in
if is_int ac_id then
ignore (Glib.Timeout.add 10000 send_wind);
begin
match dl_settings_page with
Some settings_tab ->
(** Connect the strip buttons *)
let connect = fun ?(warning=true) setting_name strip_connect ->
try
let id = settings_tab#assoc setting_name in
strip_connect (fun x -> dl_setting_callback id x)
with Not_found ->
if warning then
fprintf stderr "Warning: %s not setable from GCS strip (i.e. not listed in the xml settings file)\n" setting_name in
connect "flight_altitude" (fun f -> ac.strip#connect_shift_alt (fun x -> f (ac.target_alt+.x)));
connect "launch" ~warning:false ac.strip#connect_launch;
connect "kill_throttle" ac.strip#connect_kill;
(* try to connect either pprz_mode (fixedwing) or autopilot_mode (rotorcraft) *)
connect "pprz_mode" ~warning:false (ac.strip#connect_mode 2.);
connect "autopilot_mode" ~warning:false (ac.strip#connect_mode 13.);
connect "nav_shift" ~warning:false ac.strip#connect_shift_lateral;
connect "autopilot_flight_time" ac.strip#connect_flight_time;
let get_ac_unix_time = fun () -> ac.last_unix_time in
connect ~warning:false "snav_desired_tow" (ac.strip#connect_apt get_ac_unix_time);
begin (* Periodically update the appointment *)
try
let id = settings_tab#assoc "snav_desired_tow" in
let set_appointment = fun _ ->
begin try
let v = ac.dl_values.(id) in
let t = Unix.gmtime (Latlong.unix_time_of_tow (truncate v)) in
ac.strip#set_label "apt" (sprintf "%d:%02d:%02d" t.Unix.tm_hour t.Unix.tm_min t.Unix.tm_sec)
with _ -> () end;
true
in
ignore (Glib.Timeout.add 1000 set_appointment)
with Not_found -> ()
end;
(** Connect the GPS reset button *)
begin
try
let gps_reset_id = settings_tab#assoc "gps.reset" in
gps_page#connect_reset
(fun x -> dl_setting_callback gps_reset_id (float x))
with Not_found -> ()
end
| None -> ()
end;
(* Monitor track status *)
let monitor_track_status = fun () ->
ac.got_track_status_timer <- ac.got_track_status_timer + 1;
if ac.got_track_status_timer > 5 then
ac.track#delete_desired_track ();
true in
ignore (Glib.Timeout.add 1000 monitor_track_status);;
let ok_color = "green"
let warning_color = "orange"
let alert_color = "red"
(** Bind to message while catching all the esceptions of the callback *)
let safe_bind = fun msg cb ->
let safe_cb = fun sender vs ->
try cb sender vs with
AC_not_found -> () (* A/C not yet registed; silently ignore *)
| x -> fprintf stderr "%s: safe_bind (%s:%a): %s\n%!" Sys.argv.(0) msg (fun c vs -> List.iter (fun (_,v) -> fprintf c "%s " (Pprz.string_of_value v)) vs) vs (Printexc.to_string x) in
ignore (Ground_Pprz.message_bind msg safe_cb)
let alert_bind = fun msg cb ->
let safe_cb = fun sender vs ->
try cb sender vs with _ -> () in
ignore (Alert_Pprz.message_bind msg safe_cb)
let tele_bind = fun msg cb ->
let safe_cb = fun sender vs ->
try cb sender vs with
AC_not_found -> () (* A/C not yet registed; silently ignore *)
| x -> fprintf stderr "tele_bind (%s): %s\n%!" msg (Printexc.to_string x) in
ignore (Tele_Pprz.message_bind msg safe_cb)
let ask_config = fun alert geomap fp_notebook ac ->
let get_config = fun _sender values ->
if not (Hashtbl.mem aircrafts ac) then
create_ac alert geomap fp_notebook ac values
in
Ground_Pprz.message_req "gcs" "CONFIG" ["ac_id", Pprz.String ac] get_config
let one_new_ac = fun alert (geomap:G.widget) fp_notebook ac ->
if not (Hashtbl.mem aircrafts ac) then
ask_config alert geomap fp_notebook ac
let get_wind_msg = fun (geomap:G.widget) _sender vs ->
let ac = get_ac vs in
let value = fun field_name -> Pprz.float_assoc field_name vs in
let airspeed = value "mean_aspeed" in
ac.airspeed <- airspeed;
ac.strip#set_airspeed airspeed;
ac.misc_page#set_value "Mean airspeed" (sprintf "%.1f" airspeed);
ac.wind_speed <- value "wspeed";
let deg_dir = value "dir" in
ac.wind_dir <- (Deg>>Rad)deg_dir;
ac.misc_page#set_value "Wind speed" (sprintf "%.1f" ac.wind_speed);
ac.misc_page#set_value "Wind direction" (sprintf "%.1f" deg_dir);
let ac_id = Pprz.string_assoc "ac_id" vs in
if !active_ac = ac_id && ac.wind_speed > 1. then begin
geomap#wind_sock#set_color ac.color;
geomap#wind_sock#item#show ();
geomap#set_wind_sock deg_dir (sprintf "%.1f" ac.wind_speed)
end
let get_fbw_msg = fun alarm _sender vs ->
let ac = get_ac vs in
let status = Pprz.string_assoc "rc_status" vs
and rate = (Pprz.int_assoc "rc_rate" vs) / 5 in
(* divide by 5 to have normal values between 0 and 10 *)
(* RC rate max approx. 50 Hz *)
ac.strip#set_rc rate status;
let mode = Pprz.string_assoc "rc_mode" vs in
if mode = "FAILSAFE" then begin
log_and_say alarm ac.ac_name (sprintf "%s, mayday, AP Failure. Switch to manual." ac.ac_speech_name)
end
let get_link_status_msg = fun alarm sender vs ->
let ac = find_ac sender in
let link_id = Pprz.int_assoc "link_id" vs in
let time_since_last_msg = Pprz.float_assoc "time_since_last_msg" vs in
let rx_msgs_rate = Pprz.float_assoc "rx_msgs_rate" vs in
let ping_time = Pprz.float_assoc "ping_time" vs in
if (not (ac.link_page#link_exists link_id)) then begin
ac.link_page#add_link link_id;
log_and_say alarm ac.ac_name (sprintf "%s, new link detected: %i" ac.ac_speech_name link_id)
end;
let link_changed = ac.link_page#update_link link_id time_since_last_msg rx_msgs_rate ping_time in
let (links_up, _) = ac.link_page#links_ratio () in
match (link_changed, links_up) with
(_, 0) -> log_and_say alarm ac.ac_name (sprintf "%s, all links lost" ac.ac_speech_name)
| (Pages.Linkup, _)-> log_and_say alarm ac.ac_name (sprintf "%s, link %i re-connected" ac.ac_speech_name link_id)
| (Pages.Nochange, _) -> ()
| (Pages.Linkdown, _) -> log_and_say alarm ac.ac_name (sprintf "%s, link %i lost" ac.ac_speech_name link_id)
let get_engine_status_msg = fun _sender vs ->
let ac = get_ac vs in
ac.strip#set_throttle ~kill:ac.in_kill_mode (Pprz.float_assoc "throttle" vs);
ac.strip#set_bat (Pprz.float_assoc "bat" vs)
let get_if_calib_msg = fun _sender vs ->
let ac = get_ac vs in
match ac.rc_settings_page with
None -> ()
| Some p ->
p#set_rc_setting_mode (Pprz.string_assoc "if_mode" vs);
p#set (Pprz.float_assoc "if_value1" vs) (Pprz.float_assoc "if_value2" vs)
let listen_wind_msg = fun (geomap:G.widget) ->
safe_bind "WIND" (get_wind_msg geomap)
let listen_fbw_msg = fun a ->
safe_bind "FLY_BY_WIRE" (get_fbw_msg a)
let listen_engine_status_msg = fun () ->
safe_bind "ENGINE_STATUS" get_engine_status_msg
let listen_if_calib_msg = fun () ->
safe_bind "INFLIGH_CALIB" get_if_calib_msg
let listen_link_status_msg = fun a ->
tele_bind "LINK_STATUS" (get_link_status_msg a)
let list_separator = Str.regexp ","
let aircrafts_msg = fun alert (geomap:G.widget) fp_notebook acs ->
let acs = Pprz.string_assoc "ac_list" acs in
let acs = Str.split list_separator acs in
List.iter (one_new_ac alert geomap fp_notebook) acs
let listen_dl_value = fun () ->
let get_dl_value = fun _sender vs ->
let ac = get_ac vs in
match ac.dl_settings_page with
Some settings ->
let csv = Pprz.string_assoc "values" vs in
let values = Array.map float_of_string (Array.of_list (Str.split list_separator csv)) in
ac.dl_values <- values;
for i = 0 to min (Array.length values) settings#length - 1 do
settings#set i (try values.(i) with _ -> failwith (sprintf "values.(%d)" i))
done
| None -> () in
safe_bind "DL_VALUES" get_dl_value
let highlight_fp = fun ac b s ->
if (b, s) <> ac.last_stage then begin
ac.last_stage <- (b, s);
ac.fp_group#highlight_stage b s
end
let check_approaching = fun ac geo alert ->
match ac.track#last with
None -> ()
| Some ac_pos ->
let d = LL.wgs84_distance ac_pos geo in
if d < ac.speed *. approaching_alert_time then
log_and_say alert ac.ac_name (sprintf "%s, approaching" ac.ac_speech_name)
let ac_alt_graph = [14,0;-5,0;-7,-6]
let translate = fun l dx dy -> List.map (fun (x, y) -> (x + dx, y + dy)) l
let rotate_and_translate = fun l angle dx dy ->
translate (List.map (fun (x, y) -> (
truncate ((cos angle) *. (float x) -. (sin angle) *. (float y)),
truncate ((sin angle) *. (float x) +. (cos angle) *. (float y)))
) l) dx dy
let flip = fun l -> List.map (fun (x, y) -> (-x, y)) l
let draw_altgraph = fun (da_object:Gtk_tools.pixmap_in_drawin_area) (geomap:MapCanvas.widget) aircrafts ->
(** First estimate the coverage of the window *)
let width_c, height_c = Gdk.Drawable.get_size geomap#canvas#misc#window in
let (xc0, yc0) = geomap#canvas#get_scroll_offsets in
let (east, _y0) = geomap#window_to_world (float xc0) (float (yc0+height_c))
and (west, _y1) = geomap#window_to_world (float (xc0+width_c)) (float yc0) in
let da = da_object#drawing_area in
let width, height = Gdk.Drawable.get_size da#misc#window in
let dr = da_object#get_pixmap () in
dr#set_background `BLACK;
dr#set_foreground `BLACK;
(* Background *)
dr#rectangle ~x:0 ~y:0 ~width ~height ~filled:true ();
(* Text *)
let context = da#misc#create_pango_context in
let print_string = fun x y string color ->
let layout = context#create_layout in
let from_codeset = "ISO-8859-15"
and to_codeset = "UTF-8" in
Pango.Layout.set_text layout (Glib.Convert.convert ~from_codeset ~to_codeset string);
let (_w,h) = Pango.Layout.get_pixel_size layout in
dr#put_layout ~x ~y:(y-h) ~fore:(`NAME color) layout in
(* find min and max alt *)
let max_alt = ref 0
and min_alt = ref 35786000 in
Hashtbl.iter (fun _ac_id ac ->
let track = ac.track in
let alt = (truncate track#last_altitude) in
let ground_alt = alt - (truncate (track#height ())) in
if ground_alt < !min_alt then min_alt := ground_alt;
if alt > !max_alt then max_alt := alt
) aircrafts;
min_alt := min !min_alt !max_alt;
if (!min_alt mod lines_height) < (min 10 (lines_height / 2)) then
min_alt := (!min_alt / lines_height - 1) * lines_height
else
min_alt := (!min_alt / lines_height) * lines_height;
min_alt := max !min_alt 0;
let height_alt = max (!max_alt - !min_alt + 10) min_height in
(* lines *)
dr#set_foreground (`NAME "grey");
let n = height_alt / lines_height in
for i = 0 to n do
let y = height - i * height / n in
dr#line ~x:0 ~y ~x:width ~y;
print_string 6 y (sprintf "%d" (!min_alt + i * lines_height)) "red";
done;
(* aircrafts *)
Hashtbl.iter (fun _ac_id ac ->
dr#set_foreground (`NAME ac.color);
let track = ac.track in
match track#last with
Some pos ->
let (xac, _yac) = geomap#world_of pos in
let w = float width in
let eac = (truncate (w *. (xac -. east) /. (west -. east))) in
let alt = (truncate track#last_altitude) in
let aac = height - height * (alt - !min_alt) / height_alt in
let h = track#last_heading in
let climb_angle = ref 0. in
if track#last_speed > 0. then
climb_angle := (atan2 track#last_climb track#last_speed);
dr#set_line_attributes ~width:4 ~cap:`ROUND ();
dr#set_foreground (`NAME "white");
if h > 0. && h <= 180. then begin
dr#lines (rotate_and_translate ac_alt_graph (-. !climb_angle) eac aac);
dr#set_line_attributes ~width:2 ();
dr#set_foreground (`NAME ac.color);
dr#lines (rotate_and_translate ac_alt_graph (-. !climb_angle) eac aac);
end
else begin
dr#lines (rotate_and_translate (flip ac_alt_graph) !climb_angle eac aac);
dr#set_line_attributes ~width:2 ();
dr#set_foreground (`NAME ac.color);
dr#lines (rotate_and_translate (flip ac_alt_graph) !climb_angle eac aac);
end;
(* altitude from ground if available *)
let alt_from_ground = truncate (track#height ()) in
let gac = aac + height * alt_from_ground / height_alt in
dr#set_line_attributes ~width:1 ~cap:`NOT_LAST ();
dr#line ~x:eac ~y:aac ~x:eac ~y:gac;
(* history *)
let v_path = track#v_path in
for i = 0 to Array.length v_path - 1 do
let (x, _y) = geomap#world_of (fst v_path.(i)) in
let e = (truncate (w *. (x -. east) /. (west -. east))) in
let a = height - height * ((truncate (snd v_path.(i))) - !min_alt) / height_alt in
dr#point ~x:e ~y:a;
done
| None -> ()
) aircrafts;
(new GDraw.drawable da#misc#window)#put_pixmap ~x:0 ~y:0 dr#pixmap
(****** Display the position provided by a connected GPS receiver (../tmtc/gpsd2ivy) *******************)
module GCS_icon = struct
let status = ref None
let color = "black"
let fill_color = "brown"
let radius = 5.
let outdated_color = "red"
let timeout = 10000 (* ms : time before changing to outdated color *)
let display = fun (geomap:G.widget) vs ->
let item =
match !status with
None -> (* First call, create the graphical object *)
GnoCanvas.ellipse ~fill_color ~props:[`WIDTH_PIXELS 2]
~x1: ~-.radius ~y1: ~-.radius ~x2:radius ~y2:radius
geomap#canvas#root
| Some (item, timeout_handle) -> (* Remove the timeouted color modification *)
Glib.Timeout.remove timeout_handle;
item in
item#set [`OUTLINE_COLOR color];
let change_color_if_not_updated =
Glib.Timeout.add 10000 (fun () -> item#set [`OUTLINE_COLOR outdated_color]; false) in
(* Store the object and the timeout to change its color *)
status := Some (item, change_color_if_not_updated);
let lat = Pprz.float_assoc "lat" vs
and lon = Pprz.float_assoc "long" vs in
let wgs84 = LL.make_geo_deg lat lon in
geomap#move_item item wgs84
end (* module GCS_icon *)
(******************************** FLIGHT_PARAMS ******************************)
let listen_flight_params = fun geomap auto_center_new_ac alert alt_graph ->
let get_fp = fun _sender vs ->
let ac_id = Pprz.string_assoc "ac_id" vs in
if ac_id = gcs_id then
GCS_icon.display geomap vs
else
let ac = get_ac vs in
let pfd_page = ac.pfd_page in
let a = fun s -> Pprz.float_assoc s vs in
let alt = a "alt"
and climb = a "climb"
and speed = a "speed" in
pfd_page#set_attitude (a "roll") (a "pitch");
pfd_page#set_alt alt;
pfd_page#set_climb climb;
pfd_page#set_speed speed;
let wgs84 = { posn_lat=(Deg>>Rad)(a "lat"); posn_long = (Deg>>Rad)(a "long") } in
ac.track#move_icon wgs84 (a "heading") alt speed climb;
ac.speed <- speed;
let unix_time = a "unix_time" in
if unix_time > ac.last_unix_time then begin
let utc = Unix.gmtime unix_time in
geomap#set_utc_time utc.Unix.tm_hour utc.Unix.tm_min utc.Unix.tm_sec;
ac.last_unix_time <- unix_time
end;
if auto_center_new_ac && ac.first_pos then begin
center geomap ac.track ();
ac.first_pos <- false
end;
let set_label = fun lbl_name value ->
ac.strip#set_label lbl_name (sprintf "%.0fm" value)
in
set_label "altitude" alt;
ac.strip#set_speed speed;
ac.strip#set_climb climb;
let agl = (a "agl") in
ac.alt <- alt;
ac.strip#set_agl agl;
if not ac.ground_prox && ac.flight_time > 10 && agl < 20. then begin
log_and_say alert ac.ac_name (sprintf "%s, %s" ac.ac_speech_name "Ground Proximity Warning");
ac.ground_prox <- true
end else if agl > 25. then
ac.ground_prox <- false;
try
if not (alt_graph#drawing_area#misc#parent = None) then
draw_altgraph alt_graph geomap aircrafts
with _ -> ()
in
safe_bind "FLIGHT_PARAM" get_fp;
let get_ns = fun _sender vs ->
let ac = get_ac vs in
let a = fun s -> Pprz.float_assoc s vs in
let wgs84 = { posn_lat = (Deg>>Rad)(a "target_lat"); posn_long = (Deg>>Rad)(a "target_long") } in
ac.track#move_carrot wgs84;
let cur_block = Pprz.int_assoc "cur_block" vs
and cur_stage = Pprz.int_assoc "cur_stage" vs in
highlight_fp ac cur_block cur_stage;
let set_label = fun l f ->
ac.strip#set_label l (sprintf "%.0fm" (Pprz.float_assoc f vs)) in
set_label "target_altitude" "target_alt";
let target_alt = Pprz.float_assoc "target_alt" vs in
ac.strip#set_label "diff_target_alt" (sprintf "%+.0fm" (ac.alt -. target_alt));
ac.target_alt <- target_alt;
let b = try List.assoc cur_block ac.blocks with Not_found -> failwith (sprintf "Error: unknown block %d for A/C %s" cur_block ac.ac_name) in
if b <> ac.last_block_name then begin
log_and_say alert ac.ac_name (sprintf "%s, %s" ac.ac_speech_name b);
ac.last_block_name <- b;
ac.strip#set_label "block_name" b
end;
let block_time = Int32.to_int (Pprz.int32_assoc "block_time" vs)
and stage_time = Int32.to_int (Pprz.int32_assoc "stage_time" vs) in
let bt = sprintf "%02d:%02d" (block_time / 60) (block_time mod 60) in
ac.strip#set_label "block_time" bt;
let st = sprintf "%02d:%02d" (stage_time / 60) (stage_time mod 60) in
ac.strip#set_label "stage_time" st;
(* Estimated Time Arrival to next waypoint *)
let d = Pprz.float_assoc "dist_to_wp" vs in
let label =
if d = 0. || ac.speed = 0. then
"N/A"
else
sprintf "%.0fs" (d /. ac.speed) in
ac.strip#set_label "eta_time" label;
ac.last_dist_to_wp <- d;
(* Estimated Time to HOME *)
try
match ac.wp_HOME with
Some wp_HOME ->
let (bearing_to_HOME_deg, d) = Latlong.bearing ac.track#pos wp_HOME#pos in
let bearing_to_HOME = (Deg>>Rad)bearing_to_HOME_deg in
let wind_north = -. ac.wind_speed *. cos ac.wind_dir
and wind_east = -. ac.wind_speed *. sin ac.wind_dir in
let c = ac.wind_speed *. ac.wind_speed -. ac.airspeed *. ac.airspeed
and scal = wind_east *. sin bearing_to_HOME +. wind_north *. cos bearing_to_HOME in
let delta = 4. *. (scal*.scal -. c) in
let ground_speed_to_HOME = scal +. sqrt delta /. 2. in
let time_to_HOME = d /. ground_speed_to_HOME in
ac.misc_page#set_value "Time to HOME" (sprintf "%.0fs" time_to_HOME)
| _ -> ()
with
_NotSoImportant -> ()
in
safe_bind "NAV_STATUS" get_ns;
let get_cam_status = fun _sender vs ->
let ac = get_ac vs in
let a = fun s -> Pprz.float_assoc s vs in
let cam_wgs84 = { posn_lat = (Deg>>Rad)(a "cam_lat"); posn_long = (Deg>>Rad)(a "cam_long") }
and target_wgs84 = { posn_lat = (Deg>>Rad)(a "cam_target_lat"); posn_long = (Deg>>Rad)(a "cam_target_long") } in
ac.track#move_cam cam_wgs84 target_wgs84
in
safe_bind "CAM_STATUS" get_cam_status;
let get_circle_status = fun _sender vs ->
let ac = get_ac vs in
ac.got_track_status_timer <- 0;
let a = fun s -> Pprz.float_assoc s vs in
let wgs84 = { posn_lat = (Deg>>Rad)(a "circle_lat"); posn_long = (Deg>>Rad)(a "circle_long") } in
ac.track#draw_circle wgs84 (float_of_string (Pprz.string_assoc "radius" vs))
in
safe_bind "CIRCLE_STATUS" get_circle_status;
let get_segment_status = fun _sender vs ->
let ac = get_ac vs in
ac.got_track_status_timer <- 0;
let a = fun s -> Pprz.float_assoc s vs in
let geo1 = { posn_lat = (Deg>>Rad)(a "segment1_lat"); posn_long = (Deg>>Rad)(a "segment1_long") }
and geo2 = { posn_lat = (Deg>>Rad)(a "segment2_lat"); posn_long = (Deg>>Rad)(a "segment2_long") } in
ac.track#draw_segment geo1 geo2;
(* Check if approaching the end of the segment *)
check_approaching ac geo2 alert
in
safe_bind "SEGMENT_STATUS" get_segment_status;
let get_survey_status = fun _sender vs ->
let ac = get_ac vs in
let a = fun s -> Pprz.float_assoc s vs in
let geo1 = { posn_lat = (Deg>>Rad)(a "south_lat"); posn_long = (Deg>>Rad)(a "west_long") }
and geo2 = { posn_lat = (Deg>>Rad)(a "north_lat"); posn_long = (Deg>>Rad)(a "east_long") } in
ac.track#draw_zone geo1 geo2
in
safe_bind "SURVEY_STATUS" get_survey_status;
let get_ap_status = fun _sender vs ->
let ac = get_ac vs in
let flight_time = Int32.to_int (Pprz.int32_assoc "flight_time" vs) in
ac.track#update_ap_status (float_of_int flight_time);
ac.flight_time <- flight_time;
let ap_mode = Pprz.string_assoc "ap_mode" vs in
if ap_mode <> ac.last_ap_mode then begin
log_and_say alert ac.ac_name (sprintf "%s, %s" ac.ac_speech_name ap_mode);
ac.last_ap_mode <- ap_mode;
let label = Pprz.string_assoc "ap_mode" vs in
ac.strip#set_label "AP" (if label="MANUAL" then "MANU" else label);
let color =
match ap_mode with
"AUTO2" | "NAV" -> ok_color
| "AUTO1" | "R_RCC" | "A_RCC" | "ATT_C" | "R_ZH" | "A_ZH" | "HOVER" | "HOV_C" | "H_ZH" -> "#10F0E0"
| "MANUAL" | "RATE" | "ATT" | "RC_D" -> warning_color
| _ -> alert_color in
ac.strip#set_color "AP" color;
end;
let status_filter_mode = Pprz.string_assoc "state_filter_mode" vs in
let gps_mode =
if (status_filter_mode <> "UNKNOWN") && (status_filter_mode <> "OK") && (status_filter_mode <> "GPS_LOST")
then status_filter_mode
else Pprz.string_assoc "gps_mode" vs in
ac.strip#set_label "GPS" gps_mode;
ac.strip#set_color "GPS" (if gps_mode<>"3D" then alert_color else ok_color);
let ft =
sprintf "%02d:%02d:%02d" (flight_time / 3600) ((flight_time / 60) mod 60) (flight_time mod 60) in
ac.strip#set_label "flight_time" ft;
let kill_mode = Pprz.string_assoc "kill_mode" vs in
if kill_mode <> "OFF" then begin
if not ac.in_kill_mode then
log_and_say alert ac.ac_name (sprintf "%s, mayday, kill mode" ac.ac_speech_name);
ac.in_kill_mode <- true
end else
ac.in_kill_mode <- false;
match ac.rc_settings_page with
None -> ()
| Some p ->
p#set_rc_mode ap_mode
in
safe_bind "AP_STATUS" get_ap_status;
listen_dl_value ();;
let listen_waypoint_moved = fun () ->
let get_values = fun _sender vs ->
let ac = get_ac vs in
let wp_id = Pprz.int_assoc "wp_id" vs in
let a = fun s -> Pprz.float_assoc s vs in
let geo = { posn_lat = (Deg>>Rad)(a "lat"); posn_long = (Deg>>Rad)(a "long") }
and altitude = a "alt" in
try
let w = ac.fp_group#get_wp wp_id in
w#set ~altitude ~update:true geo
with
Not_found -> () (* Silently ignore unknown waypoints *)
in
safe_bind "WAYPOINT_MOVED" get_values
let get_alert_bat_low = fun a _sender vs ->
let ac = get_ac vs in
let level = Pprz.string_assoc "level" vs in
log_and_say a ac.ac_name (sprintf "%s, %s %s" ac.ac_speech_name "BAT LOW" level)
let listen_alert = fun a ->
alert_bind "BAT_LOW" (get_alert_bat_low a)
let get_svsinfo = fun alarm _sender vs ->
let ac = get_ac vs in
let gps_page = ac.gps_page in
let svids = Str.split list_separator (Pprz.string_assoc "svid" vs)
and cn0s = Str.split list_separator (Pprz.string_assoc "cno" vs)
and flagss = Str.split list_separator (Pprz.string_assoc "flags" vs)
and ages = Str.split list_separator (Pprz.string_assoc "msg_age" vs) in
let a = Array.create (List.length svids) (0,0,0,0) in
let rec loop = fun i s c f ages ->
match (s, c, f, ages) with
[], [], [], [] -> ()
| s::ss, c::cs, f::fs, age::ages ->
a.(i) <- (int_of_string s, int_of_string c, int_of_string f, int_of_string age);
loop (i+1) ss cs fs ages
| _ -> assert false in
loop 0 svids cn0s flagss ages;
let pacc = Pprz.int_assoc "pacc" vs in
gps_page#svsinfo pacc a;
if pacc > 1500 && pacc < 9999 then
log_and_say alarm "gcs" (sprintf "GPS acc: %d m" (pacc / 100))
let listen_svsinfo = fun a -> safe_bind "SVSINFO" (get_svsinfo a)
let message_request = Ground_Pprz.message_req
let get_ts = fun _sender vs ->
let ac = get_ac vs in
let t = Pprz.float_assoc "time_since_last_msg" vs in
if ac.link_page#multiple_links () then
begin
let (links_up, total_links) = ac.link_page#links_ratio () in
let link_ratio_string = sprintf "%i/%i" links_up total_links in
ac.strip#set_label "telemetry_status" (if t > 2. then sprintf "%.0f" t else link_ratio_string);
ac.strip#set_color "telemetry_status" (if t > 5. then alert_color else if links_up < total_links then warning_color else ok_color)
end
else
begin
ac.strip#set_label "telemetry_status" (if t > 2. then sprintf "%.0f" t else " ");
ac.strip#set_color "telemetry_status" (if t > 5. then alert_color else ok_color)
end
let listen_telemetry_status = fun () ->
safe_bind "TELEMETRY_STATUS" get_ts
let mark_dcshot = fun (geomap:G.widget) _sender vs ->
let ac = find_ac !active_ac in
let photonumber = Pprz.string_assoc "photo_nr" vs in
(* let ac = get_ac vs in *)
match ac.track#last with
Some geo ->
begin
let group = geomap#background in
let point = geomap#photoprojection ~group ~fill_color:"yellow" ~number:photonumber geo 3. in
point#raise_to_top ()
end
| None -> ()
(* mark geomap ac.ac_name track !Plugin.frame *)
let listen_dcshot = fun _geom ->
tele_bind "DC_SHOT" (mark_dcshot _geom)
let listen_error = fun a ->
let get_error = fun _sender vs ->
let msg = Pprz.string_assoc "message" vs in
log_and_say a "gcs" msg in
safe_bind "TELEMETRY_ERROR" get_error
let listen_tcas = fun a ->
let get_alarm_tcas = fun a txt _sender vs ->
let ac = find_ac _sender in
let other_ac = get_ac vs in
let resolve = try
match Pprz.int_assoc "resolve" vs with
1 -> "=> LEVEL"
| 2 -> "=> CLIMB"
| 3 -> "=> DESCEND"
| _ -> ""
with _ -> "" in
log_and_say a ac.ac_name (sprintf "%s : %s -> %s %s" txt ac.ac_speech_name other_ac.ac_speech_name resolve)
in
tele_bind "TCAS_TA" (get_alarm_tcas a "tcas TA");
tele_bind "TCAS_RA" (get_alarm_tcas a "TCAS RA")
let listen_acs_and_msgs = fun geomap ac_notebook my_alert auto_center_new_ac alt_graph ->
(** Probe live A/Cs *)
let probe = fun () ->
message_request "gcs" "AIRCRAFTS" [] (fun _sender vs -> aircrafts_msg my_alert geomap ac_notebook vs) in
let _ = GMain.Idle.add (fun () -> probe (); false) in
(** New aircraft message *)
safe_bind "NEW_AIRCRAFT" (fun _sender vs -> one_new_ac my_alert geomap ac_notebook (Pprz.string_assoc "ac_id" vs));
(** Listen for all messages on ivy *)
listen_flight_params geomap auto_center_new_ac my_alert alt_graph;
listen_wind_msg geomap;
listen_fbw_msg my_alert;
listen_engine_status_msg ();
listen_link_status_msg my_alert;
listen_if_calib_msg ();
listen_waypoint_moved ();
listen_svsinfo my_alert;
listen_telemetry_status ();
listen_alert my_alert;
listen_error my_alert;
listen_tcas my_alert;
listen_dcshot geomap;
(** Select the active aircraft on notebook page selection *)
let callback = fun i ->
let ac_page = ac_notebook#get_nth_page i in
Hashtbl.iter
(fun ac_id ac ->
if ac.pages#get_oid = ac_page#get_oid
then select_ac ac_notebook ac_id)
aircrafts in
ignore (ac_notebook#connect#switch_page ~callback);
(** Center the active aircraft *)
let center_active = fun () ->
if !active_ac <> "" then
let ac = find_ac !active_ac in
center geomap ac.track () in
let key_press = fun ev ->
match GdkEvent.Key.keyval ev with
| k when k = GdkKeysyms._c -> center_active () ; true
| _ -> false in
ignore (geomap#canvas#event#connect#after#key_press key_press)