From cfe4f77b7b6746c9cae311b37e92f8f0ce378680 Mon Sep 17 00:00:00 2001 From: Siraaj Khandkar Date: Thu, 30 Jul 2015 01:13:40 -0400 Subject: [PATCH] Implement UDP data parsing. --- .travis.yml | 13 ++++ Makefile | 48 ++++++++++++ include/x_plane_data.hrl | 32 ++++++++ rebar.config | 9 +++ src/x_plane_data.app.src | 12 +++ src/x_plane_data.erl | 49 ++++++++++++ src/x_plane_datum.erl | 134 +++++++++++++++++++++++++++++++++ src/x_plane_datum_defaults.hrl | 1 + test/x_plane_data_SUITE.erl | 76 +++++++++++++++++++ 9 files changed, 374 insertions(+) create mode 100644 .travis.yml create mode 100644 Makefile create mode 100644 include/x_plane_data.hrl create mode 100644 rebar.config create mode 100644 src/x_plane_data.app.src create mode 100644 src/x_plane_data.erl create mode 100644 src/x_plane_datum.erl create mode 100644 src/x_plane_datum_defaults.hrl create mode 100644 test/x_plane_data_SUITE.erl diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c38a8fb --- /dev/null +++ b/.travis.yml @@ -0,0 +1,13 @@ +notifications: + recipients: + - siraaj@khandkar.net + +language: + erlang + +otp_release: + - 17.0 + - R16B02 + +script: + "make travis_ci" diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3be17b7 --- /dev/null +++ b/Makefile @@ -0,0 +1,48 @@ +.PHONY: \ + travis_ci \ + fresh-build \ + compile \ + clean \ + deps \ + deps-get \ + deps-update \ + dialyze \ + test + +all: \ + clean \ + deps \ + compile \ + test \ + dialyze + +travis_ci: \ + deps \ + compile \ + test + +fresh-build: \ + clean \ + compile + +compile: + @rebar compile + +clean: + @rebar clean + +deps: \ + deps-get \ + deps-update + +deps-get: + @rebar get-deps + +deps-update: + @rebar update-deps + +dialyze: + @dialyzer ebin deps/hope/ebin + +test: + @rebar ct skip_deps=true --verbose=0 diff --git a/include/x_plane_data.hrl b/include/x_plane_data.hrl new file mode 100644 index 0000000..e922992 --- /dev/null +++ b/include/x_plane_data.hrl @@ -0,0 +1,32 @@ +-record(x_plane_datum_speeds, + { vind_kias :: float() % 1 + , vind_keas :: float() % 2 + , vtrue_ktas :: float() % 3 + , vtrue_ktgs :: float() % 4 + % 5 + , vind_mph :: float() % 6 + , vtrue_mphas :: float() % 7 + , vtrue_mphgs :: float() % 8 + }). + +-record(x_plane_datum_pitch_roll_heading, + { pitch_deg :: float() % 1 + , roll_deg :: float() % 2 + , hding_true :: float() % 3 + , hding_mag :: float() % 4 + % 5 + % 6 + % 7 + % 8 + }). + +-record(x_plane_datum_lat_lon_alt, + { lat_deg :: float() % 1 + , lon_deg :: float() % 2 + , alt_ftmsl :: float() % 3 + , alt_ftagl :: float() % 4 + , on_runwy :: float() % 5 + , alt_ind :: float() % 6 + , lat_south :: float() % 7 + , lat_west :: float() % 8 + }). diff --git a/rebar.config b/rebar.config new file mode 100644 index 0000000..59dea81 --- /dev/null +++ b/rebar.config @@ -0,0 +1,9 @@ +%%% vim: set filetype=erlang: +{ deps +, [ {hope , ".*" , {git , "https://github.com/ibnfirnas/hope.git" , {tag , "3.7.0"}}} + ] +}. + +{cover_enabled, true}. + +{clean_files, ["test/*.beam"]}. diff --git a/src/x_plane_data.app.src b/src/x_plane_data.app.src new file mode 100644 index 0000000..c4e86a9 --- /dev/null +++ b/src/x_plane_data.app.src @@ -0,0 +1,12 @@ +{application, x_plane_data, + [ + {description, "X-Plane UDP data packet parser."}, + {vsn, "0.0.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib, + hope + ]}, + {env, []} + ]}. diff --git a/src/x_plane_data.erl b/src/x_plane_data.erl new file mode 100644 index 0000000..207673d --- /dev/null +++ b/src/x_plane_data.erl @@ -0,0 +1,49 @@ +-module(x_plane_data). + +-include("x_plane_datum_defaults.hrl"). + +-export_type( + [ t/0 + ]). + +-export( + [ of_bin/1 + ]). + +-type parsing_error() :: + packet_unrecognized + | packet_length_invalid + | x_plane_datum:parsing_error() + . + +-type t() :: + [x_plane_datum:t()]. + +-define(BYTE_SIZE_OF_EACH_BLOCK, 36). + +-spec of_bin(binary()) -> + hope_result:t(t(), parsing_error()). +of_bin(<>) -> + of_bin(Packet, ?DEFAULT_MAX_INDEX). + +-spec of_bin(binary(), non_neg_integer()) -> + hope_result:t(t(), parsing_error()). +of_bin(<<"DATA", _PacketIndexByte:1/bytes, ContiguousBlocks/binary>>, MaxIndex) -> + % Packet index byte seems to be changing from X-Plane version to verion. + % What is it's meaning? + if byte_size(ContiguousBlocks) rem ?BYTE_SIZE_OF_EACH_BLOCK =:= 0 -> + Blocks = blocks_split(ContiguousBlocks), + ParseBlock = fun (B) -> x_plane_datum:of_bin(B, MaxIndex) end, + hope_list:map_result(Blocks, ParseBlock) + ; true -> + {error, packet_length_invalid} + end; +of_bin(<<_/binary>>, _) -> + {error, packet_unrecognized}. + +-spec blocks_split(binary()) -> + [binary()]. +blocks_split(<<>>) -> + []; +blocks_split(<>) -> + [Block | blocks_split(Blocks)]. diff --git a/src/x_plane_datum.erl b/src/x_plane_datum.erl new file mode 100644 index 0000000..cd55c2f --- /dev/null +++ b/src/x_plane_datum.erl @@ -0,0 +1,134 @@ +-module(x_plane_datum). + +-include("x_plane_datum_defaults.hrl"). +-include("include/x_plane_data.hrl"). + +-export_type( + [ t/0 + , label/0 + , anonymous/0 + , identified/0 + , parsing_error/0 + ]). + +-export( + [ of_bin/1 % Use default max index + , of_bin/2 % Specify max index + ]). + +-type parsing_error() :: + {block_structure_invalid, binary()} + | {block_index_byte_out_of_range, anonymous()} + . + +-type anonymous() :: + { non_neg_integer() + , float() + , float() + , float() + , float() + , float() + , float() + , float() + , float() + }. + +-type label() :: + speeds + | pitch_roll_heading + | lat_lon_alt + . + +-type identified() :: + #x_plane_datum_speeds{} + | #x_plane_datum_pitch_roll_heading{} + | #x_plane_datum_lat_lon_alt{} + . + +-type t() :: + {non_neg_integer() , anonymous()} + | {label() , identified()} + . + +-spec of_bin(binary()) -> + hope_result:t(t(), parsing_error()). +of_bin(<>) -> + of_bin(Block, ?DEFAULT_MAX_INDEX). + +-spec of_bin(binary(), non_neg_integer()) -> + hope_result:t(t(), parsing_error()). +of_bin(<>, MaxIndex) -> + case anonymous_of_bin(Block, MaxIndex) + of {ok, Anonymous} -> + IdentifiedOrIndexed = identify_or_index(Anonymous), + {ok, IdentifiedOrIndexed} + ; {error, _}=Error -> + Error + end. + +-spec anonymous_of_bin(binary(), non_neg_integer()) -> + hope_result:t(anonymous(), parsing_error()). +anonymous_of_bin( + << Index:32/little-integer + , V1:32/little-float + , V2:32/little-float + , V3:32/little-float + , V4:32/little-float + , V5:32/little-float + , V6:32/little-float + , V7:32/little-float + , V8:32/little-float + >>, + MaxIndex +) -> + Anonymous = {Index, V1, V2, V3, V4, V5, V6, V7, V8}, + if Index > 0 andalso Index =< MaxIndex -> + {ok, Anonymous} + ; true -> + {error, {block_index_byte_out_of_range, Anonymous}} + end; +anonymous_of_bin(<>, _) -> + % This case shouldn't possible with a correct packet length, but we want to + % allow for possibility of using this module independently of it's parent, + % data module. + {error, {block_structure_invalid, Block}}. + +-spec identify_or_index(anonymous()) -> + t(). +identify_or_index({3, V1, V2, V3, V4, _, V6, V7, V8}) -> + Datum = + #x_plane_datum_speeds + { vind_kias = V1 + , vind_keas = V2 + , vtrue_ktas = V3 + , vtrue_ktgs = V4 + + , vind_mph = V6 + , vtrue_mphas = V7 + , vtrue_mphgs = V8 + }, + {speeds, Datum}; +identify_or_index({17, V1, V2, V3, V4, _, _, _, _}) -> + Datum = + #x_plane_datum_pitch_roll_heading + { pitch_deg = V1 + , roll_deg = V2 + , hding_true = V3 + , hding_mag = V4 + }, + {pitch_roll_heading, Datum}; +identify_or_index({20, V1, V2, V3, V4, V5, V6, V7, V8}) -> + Datum = + #x_plane_datum_lat_lon_alt + { lat_deg = V1 + , lon_deg = V2 + , alt_ftmsl = V3 + , alt_ftagl = V4 + , on_runwy = V5 + , alt_ind = V6 + , lat_south = V7 + , lat_west = V8 + }, + {lat_lon_alt, Datum}; +identify_or_index({Index, _, _, _, _, _, _, _, _}=Anonymous) -> + {Index, Anonymous}. diff --git a/src/x_plane_datum_defaults.hrl b/src/x_plane_datum_defaults.hrl new file mode 100644 index 0000000..9168e48 --- /dev/null +++ b/src/x_plane_datum_defaults.hrl @@ -0,0 +1 @@ +-define(DEFAULT_MAX_INDEX, 133). % As of X-Plane 10.36r1 diff --git a/test/x_plane_data_SUITE.erl b/test/x_plane_data_SUITE.erl new file mode 100644 index 0000000..3c69676 --- /dev/null +++ b/test/x_plane_data_SUITE.erl @@ -0,0 +1,76 @@ +-module(x_plane_data_SUITE). + +-include_lib("x_plane_data.hrl"). + +%% CT callbacks +-export( + [ all/0 + , groups/0 + ]). + +%% Test cases +-export( + [ t_basic_sanity_check/1 + ]). + +-define(GROUP, x_plane_data). + +%% ============================================================================ +%% CT callbacks +%% ============================================================================ + +all() -> + [ {group, ?GROUP} + ]. + +groups() -> + Tests = + [ t_basic_sanity_check + ], + Properties = [parallel], + [ {?GROUP, Properties, Tests} + ]. + + +%% ============================================================================= +%% Test cases +%% ============================================================================= + +t_basic_sanity_check(_Cfg) -> + Test = + fun (PacketBase64) -> + Packet = base64:decode(PacketBase64), + MaxIndex = 133, + BadIndex = MaxIndex + 1, + FakeBlockData = list_to_binary(lists:seq(1, 32)), + FakeBlockOk = <>, + FakeBlockBadIndex = <>, + {error, {block_index_byte_out_of_range, {BadIndex,_,_,_,_,_,_,_,_}}} = + x_plane_data:of_bin(<>), + {error, packet_unrecognized} = + x_plane_data:of_bin(<<"bad-header", Packet/binary>>), + {error, packet_length_invalid} = + x_plane_data:of_bin(<>), + {ok, Data} = + x_plane_data:of_bin(<>), + {some, #x_plane_datum_speeds{}} = + hope_kv_list:get(Data, speeds), + {some, #x_plane_datum_pitch_roll_heading{}} = + hope_kv_list:get(Data, pitch_roll_heading), + {some, #x_plane_datum_lat_lon_alt{}} = + hope_kv_list:get(Data, lat_lon_alt), + {some, {MaxIndex,_,_,_,_,_,_,_,_}} = + hope_kv_list:get(Data, MaxIndex), + ok + end, + lists:foreach(Test, sample_packets_base64_encoded()). + + + +%% ============================================================================= +%% Sample data +%% ============================================================================= + +sample_packets_base64_encoded() -> + [ <<"REFUQUADAAAAbcpGQLt81EBfZNlATnUoNwDAecSow2RAnCv6QLrbQTcRAAAA3i8VQFL3ZT6dPfFCx4IFQwDAecQAwHnEAMB5xADAecQUAAAA1ZciQg6ik8JGBv9AdDxoPgAAgD9G/o3CAAAgQgAAlsI=">> + ]. -- 2.20.1