add generic tool to translate events from an input device into messages on the ivy bus

This commit is contained in:
Pascal Brisset
2009-10-20 11:00:34 +00:00
parent ef2d9f69f4
commit 77e4b33172
5 changed files with 476 additions and 7 deletions
+23 -6
View File
@@ -23,23 +23,40 @@
# Quiet compilation
Q=@
OCAMLC = ocamlc
OCAMLLIB = ../../lib/ocaml
TOOLSDIR = ../../tools
OCAMLINCLUDES= -I $(OCAMLLIB) -I +lablgtk2 -I +xml-light -I $(TOOLSDIR)
LIBPPRZCMA=$(OCAMLLIB)/lib-pprz.cma
all: main_stick
main_stick: main_stick.c usb_stick.c
main_stick: main_stick.o usb_stick.o
gcc -g -O2 -Wall `pkg-config glib-2.0 --cflags` -o $@ $^ `pkg-config glib-2.0 --libs` `pcre-config --libs` -lglibivy
main_stick_debug: main_stick.c usb_stick.c
main_stick_debug: main_stick.o usb_stick.o
gcc -g -O2 -Wall `pkg-config glib-2.0 --cflags` -o $@ $^ `pkg-config glib-2.0 --libs` `pcre-config --libs` -lglibivy -DSTICK_DBG
apm_stick: apm_stick.c usb_stick.c
apm_stick: apm_stick.o usb_stick.o
gcc -g -O2 -Wall `pkg-config glib-2.0 --cflags` -o $@ $^ `pkg-config glib-2.0 --libs` `pcre-config --libs` -lglibivy
xbox_stick: xbox_stick.c usb_stick.c
xbox_stick: xbox_stick.o usb_stick.o
gcc -g -O2 -Wall `pkg-config glib-2.0 --cflags` -o $@ $^ `pkg-config glib-2.0 --libs` `pcre-config --libs` -lglibivy
attack3_stick: attack3_stick.c usb_stick.c
attack3_stick: attack3_stick.o usb_stick.o
gcc -g -O2 -Wall `pkg-config glib-2.0 --cflags` -o $@ $^ `pkg-config glib-2.0 --libs` `pcre-config --libs` -lglibivy
input2ivy: usb_stick.o ml_usb_stick.o input2ivy.cmo
$(OCAMLC) $(OCAMLINCLUDES) -custom -o $@ unix.cma str.cma glibivy-ocaml.cma xml-light.cma lib-pprz.cma lablgtk.cma $(TOOLSDIR)/fp_syntax.cmo $(TOOLSDIR)/fp_lexer.cmo $(TOOLSDIR)/fp_parser.cmo $(TOOLSDIR)/fp_proc.cmo $^
%.o : %.c
gcc -c -O2 -Wall `pkg-config glib-2.0 --cflags` $<
%.cmo : %.ml
@echo OC $<
$(Q)$(OCAMLC) $(OCAMLINCLUDES) -c $<
clean:
rm -f *~ core *.o *.bak .depend main_stick main_stick_debug apm_stick xbox_stick attack3_stick
rm -f *~ core *.o *.bak .depend main_stick main_stick_debug apm_stick xbox_stick attack3_stick *.cmo *.cmi input2ivy
@@ -11,7 +11,7 @@
<button index="7" name="button8"/>
</input>
<messages period="0.1">
<message class="datalink" name="BOOZ2_FMS_COMMAND" min_period="1">
<message class="datalink" name="BOOZ2_FMS_COMMAND">
<field name="h_mode" value="IndexOfEnum(NAV)"/>
<field name="v_mode" value="IndexOfEnum(NAV)"/>
<field name="v_sp" value="(up-down)*42"/>
+352
View File
@@ -0,0 +1,352 @@
(*
* $Id$
*
* Forwarding events from an USB input device to the ivy bus
*
* Copyright (C) 2009 ENAC, 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 conf_dir = Env.paparazzi_home // "conf"
let verbose = ref false (* Command line option *)
(** Messages libraries *)
module DL = Pprz.Messages(struct let name = "datalink" end)
module G = Pprz.Messages(struct let name = "ground" end)
(** Syntax for expressions *)
module Syntax = Fp_syntax (* From ../../tools/ *)
(** Hash table of name-index associations for the settings of the A/C *)
let index_of_settings = Hashtbl.create 13
(**Hash table of name-index associations for the flightplan blocks of the A/C*)
let index_of_blocks = Hashtbl.create 13
(** External C functions to access the input device *)
external stick_init : string -> int = "ml_stick_init"
(** [stick_init device] Return 0 on success. Search for a device if [device]
is the empty string *)
external stick_read : unit -> int * int * int array = "ml_stick_read"
(** Return the number of buttons, an integer of bits for the buttons values
and an array of signed integers for the axis *)
(** Range for the input axis *)
let max_input = 127
let min_input = -127
(** Representation of an input value *)
type input =
Axis of int
| Button of int
(** Description of a message *)
type msg = {
msg_name : string;
msg_class : string;
fields : (string * Syntax.expression) list;
on_event : Syntax.expression option
}
(** Represenation of an input device and of the messages to send *)
type actions = {
period_ms : int;
inputs : (string*input) list;
messages : msg list
}
(** Get a message description from its name (and class name) *)
let get_message = fun class_name msg_name ->
match class_name with
"datalink" -> snd (DL.message_of_name msg_name)
| "ground" -> snd (G.message_of_name msg_name)
| _ -> failwith class_name
(** Get the A/C id from its name in conf/conf.xml *)
let ac_id_of_name = fun ac_name ->
let conf_xml = Xml.parse_file (conf_dir // "conf.xml") in
try
let aircraft = ExtXml.child ~select:(fun x -> Xml.attrib x "name" = ac_name) conf_xml "aircraft" in
ExtXml.int_attrib aircraft "ac_id"
with
Not_found ->
failwith (sprintf "A/C '%s' not found" ac_name)
(** Fill the index_of_settings table from var/AC/settings.xml *)
let hash_index_of_settings = fun ac_name ->
let xml_file = Env.paparazzi_home // "var" // ac_name // "settings.xml" in
let xml = Xml.parse_file xml_file in
let index = ref 0 in
let rec loop = fun xml ->
if Xml.tag xml = "dl_settings" then
List.iter loop (Xml.children xml)
else begin (* dl_setting *)
Hashtbl.add index_of_settings (Xml.attrib xml "var") !index;
incr index
end in
loop (ExtXml.child xml "dl_settings")
(** Fill the index_of_blocks table from var/AC/flight_plan.xml *)
let hash_index_of_blocks = fun ac_name ->
let xml_file = Env.paparazzi_home // "var" // ac_name // "flight_plan.xml" in
let dump = Xml.parse_file xml_file in
let flight_plan = ExtXml.child dump "flight_plan" in
let blocks = ExtXml.child flight_plan "blocks" in
List.iter (fun block ->
Hashtbl.add
index_of_blocks
(Xml.attrib block "name")
(ExtXml.int_attrib block "no"))
(Xml.children blocks)
(* Return the rank of an element in a list, first is 0 *)
let rank = fun x l ->
let rec loop i = function
[] -> raise Not_found
| y::ys -> if x = y then i else loop (i+1) ys in
loop 0 l
(** Eval IndexOfEnum, IndexOfSetting and IndexOfBlock built-in functions
in an expression *)
let eval_settings_and_blocks = fun field_descr expr ->
let rec loop = function
Syntax.Call ("IndexOfEnum", [Syntax.Ident enum]) -> begin
try Syntax.Int (rank enum field_descr.Pprz.enum) with
Not_found -> failwith (sprintf "IndexOfEnum: unknown value '%s'" enum)
end
| Syntax.Call ("IndexOfSetting", [Syntax.Ident var]) -> begin
try Syntax.Int (Hashtbl.find index_of_settings var) with
Not_found -> failwith (sprintf "IndexOfSetting: unknown var '%s'" var)
end
| Syntax.Call ("IndexOfBlock", [Syntax.Ident name]) -> begin
try Syntax.Int (Hashtbl.find index_of_blocks name) with
Not_found -> failwith (sprintf "IndexOfBlock: unknown block '%s'" name)
end
| Syntax.Call (ident, exprs) | Syntax.CallOperator (ident, exprs) ->
Syntax.Call (ident, List.map loop exprs)
| e -> e in
loop expr
(** Parse an XML list of input channels *)
let parse_input = fun input ->
List.map
(fun x ->
let name = Xml.attrib x "name"
and index = ExtXml.int_attrib x "index" in
let value =
match Xml.tag x with
"axis" -> Axis index
| "button" -> Button index
| _ -> failwith "parse_input: unexepcted tag" in
(name, value))
(Xml.children input)
(** Parse a 'à la C' expression *)
let parse_value = fun s ->
Fp_proc.parse_expression s
(** Parse a message field and eval *)
let parse_msg_field = fun msg_descr field ->
let name = Xml.attrib field "name" in
let field_descr = List.assoc name msg_descr.Pprz.fields in
let value = eval_settings_and_blocks field_descr (parse_value (Xml.attrib field "value")) in
(name, value)
(** Parse a complete message and build its representation *)
let parse_msg = fun msg ->
let msg_name = Xml.attrib msg "name"
and msg_class = Xml.attrib msg "class" in
let msg_descr = get_message msg_class msg_name in
let fields = List.map (parse_msg_field msg_descr) (Xml.children msg) in
let on_event =
try Some (parse_value (Xml.attrib msg "on_event")) with _ -> None in
{ msg_name = msg_name;
msg_class = msg_class;
fields = fields;
on_event = on_event
}
(** Parse the complete (input and messages) XML desxription *)
let parse_descr = fun xml_file ->
let xml = Xml.parse_file xml_file in
let inputs = parse_input (ExtXml.child xml "input")
and messages_xml = ExtXml.child xml "messages" in
let period_ms =truncate (1000.*.ExtXml.float_attrib messages_xml "period")
and messages = List.map parse_msg (Xml.children messages_xml) in
{ period_ms = period_ms; inputs = inputs; messages = messages }
(** Verbose List.assco *)
let my_assoc = fun x l ->
try List.assoc x l with Not_found ->
failwith (sprintf "my_assoc: %s not found" x)
(** Access to an input value, button or axis *)
let eval_input = fun buttons axis input ->
match input with
Axis i -> axis.(i)
| Button i -> (buttons lsr i) land 0x1
(** Scale a value in the given bounds *)
let scale = fun x min max ->
min + ((x - min_input) * (max - min)) / (max_input - min_input)
(** Eval a function call (TO BE COMPLETED) *)
let eval_call = fun f args ->
match f, args with
"-", [a1; a2] -> a1 - a2
| "+", [a1; a2] -> a1 + a2
| "*", [a1; a2] -> a1 * a2
| "&&", [a1; a2] -> a1 land a2
| "Scale", [x; min; max] -> scale (x) (min) (max)
| f, args -> failwith (sprintf "eval_call: unknown function '%s'" f)
(** Eval an expression *)
let eval_expr = fun buttons axis inputs expr ->
let rec eval = function
Syntax.Ident ident ->
let input = my_assoc ident inputs in
eval_input buttons axis input
| Syntax.Int int -> int
| Syntax.Float float -> failwith "eval_expr: float"
| Syntax.Call (ident, exprs) | Syntax.CallOperator (ident, exprs) ->
eval_call ident (List.map eval exprs)
| Syntax.Index _ -> failwith "eval_expr: index" in
eval expr
(** Store for the last sent values: msg_name->values *)
let last_values = Hashtbl.create 5
(** Get the previous sent values for a given message *)
let get_previous_values = fun msg_name ->
try Hashtbl.find last_values msg_name with Not_found -> (false, [])
(** Record the current values for a given message *)
let record_values = fun msg_name values ->
Hashtbl.replace last_values msg_name values
(**Send an ivy message if needed: new values and/or 'on_event' condition true*)
let execute_action = fun ac_id inputs buttons axis message ->
let values =
List.map
(fun (name, expr) -> (name, Pprz.Int (eval_expr buttons axis inputs expr)))
message.fields
and on_event =
match message.on_event with
None -> true
| Some expr -> eval_expr buttons axis inputs expr <> 0 in
let previous_values = get_previous_values message.msg_name in
if (on_event, values) <> previous_values && on_event then begin
let vs = ("ac_id", Pprz.Int ac_id) :: values in
match message.msg_class with
"datalink" -> DL.message_send "input2ivy" message.msg_name vs
| "ground" -> G.message_send "input2ivy" message.msg_name vs
| c -> failwith (sprintf "execute_action: unknown class '%s'" c)
end;
record_values message.msg_name (on_event, values)
(** Output on stderr the values from the input device *)
let print_inputs = fun nb_buttons buttons axis ->
fprintf stderr "buttons: ";
for i = 0 to nb_buttons - 1 do
fprintf stderr "%d:%d " i (eval_input buttons axis (Button i))
done;
fprintf stderr "\naxis: ";
for i = 0 to Array.length axis - 1 do
fprintf stderr "%d:%d " i (eval_input buttons axis (Axis i))
done;
fprintf stderr "\n%!"
(** Get the values from the input values and send messages *)
let execute_actions = fun actions ac_id ->
try
let (nb_buttons, buttons, axis) = stick_read () in
if !verbose then
print_inputs nb_buttons buttons axis;
List.iter (execute_action ac_id actions.inputs buttons axis) actions.messages
with
exc -> prerr_endline (Printexc.to_string exc)
(************************************* MAIN **********************************)
let () =
let ivy_bus = ref "127.255.255.255:2010" in
let device_name = ref ""
and ac_name = ref "MYAC"
and xml_descr = ref "" in
let anon_fun = (fun x -> xml_descr := x) in
let speclist =
[ "-b", Arg.String (fun x -> ivy_bus := x), "Bus\tDefault is 127.255.255.255:2010";
"-ac", Arg.Set_string ac_name, "<A/C name>";
"-d", Arg.Set_string device_name, "<device name>";
"-v", Arg.Set verbose, "Verbose mode (useful to identify the channels of an input device)";
"-", Arg.String anon_fun, "<xml file of actions>"
]
and usage_msg = "Usage: " in
Arg.parse speclist anon_fun usage_msg;
if !xml_descr = "" then begin
Arg.usage speclist usage_msg;
exit 1
end;
let ac_id = ac_id_of_name !ac_name in
hash_index_of_settings !ac_name;
hash_index_of_blocks !ac_name;
let actions = parse_descr !xml_descr in
if stick_init !device_name <> 0 then
failwith (sprintf "Error: cannot open device %s\n" !device_name);
(** Connect to the Ivy bus *)
Ivy.init "Paparazzi joystick" "READY" (fun _ _ -> ());
Ivy.start !ivy_bus;
ignore (Glib.Timeout.add actions.period_ms (fun () -> execute_actions actions ac_id; true));
(** Start the main loop *)
let loop = Glib.Main.create true in
while Glib.Main.is_running loop do ignore (Glib.Main.iteration true) done
+69
View File
@@ -0,0 +1,69 @@
/*
$Id$
Copyright (C) 2009 ENAC, Pascal Brisset, Gautier Hattenberger
Ocaml bindings for USB stick
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.
*/
#include <stdio.h>
#include <string.h>
#include <caml/mlvalues.h>
#include <caml/alloc.h>
#include <caml/memory.h>
#include <caml/callback.h>
#include <caml/fail.h>
#include "usb_stick.h"
/** Open an input device. Try to discover it if name is empty */
value
ml_stick_init(value device_name_val) {
char * device_name = String_val(device_name_val);
if (strlen(device_name) == 0)
device_name = NULL;
int opened = stick_init(device_name);
return Val_int(opened);
}
/** Return a triple of
- the number of buttons
- one integer with the buttons values
- the array of axis values */
CAMLprim value
ml_stick_read(value _unit) {
CAMLparam0();
CAMLlocal3 (result, buttons, axis);
stick_read();
buttons = Val_int(stick_button_values);
axis = caml_alloc_tuple(stick_axis_count);
unsigned int i;
for(i = 0; i < stick_axis_count; i++)
Store_field(axis, i, Val_int(stick_axis_values[i]));
result = caml_alloc_tuple(3);
Store_field(result, 0, Val_int(stick_button_count));
Store_field(result, 1, buttons);
Store_field(result, 2, axis);
CAMLreturn(result);
}
@@ -0,0 +1,31 @@
<joystick>
<input>
<axis index="0" name="roll"/>
<axis index="1" name="pitch"/>
<axis index="3" name="yaw"/>
<axis index="4" name="tilt"/>
<button index="3" name="X"/>
<button index="0" name="down"/>
<button index="1" name="up"/>
<button index="4" name="Y"/>
</input>
<messages period="0.1">
<message class="datalink" name="BOOZ2_FMS_COMMAND">
<field name="h_mode" value="IndexOfEnum(NAV)"/>
<field name="v_mode" value="IndexOfEnum(NAV)"/>
<field name="v_sp" value="(up-down)*42"/>
<field name="h_sp_1" value="roll"/>
<field name="h_sp_2" value="pitch"/>
<field name="h_sp_3" value="yaw"/>
</message>
<message class="ground" name="DL_SETTING">
<field name="index" value="IndexOfSetting(booz2_cam_tilt)"/>
<field name="value" value="Scale(tilt, 1000, 2000)"/>
</message>
<message class="ground" name="JUMP_TO_BLOCK" on_event="X && Y">
<field name="block_id" value="IndexOfBlock('land here')"/>
</message>
</messages>
</joystick>