--- /dev/null
+notifications:
+ recipients:
+ - siraaj@khandkar.net
+
+language:
+ erlang
+
+otp_release:
+ - 17.0
+ - R16B02
+
+script:
+ "make travis_ci"
--- /dev/null
+.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
--- /dev/null
+-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
+ }).
--- /dev/null
+%%% vim: set filetype=erlang:
+{ deps
+, [ {hope , ".*" , {git , "https://github.com/ibnfirnas/hope.git" , {tag , "3.7.0"}}}
+ ]
+}.
+
+{cover_enabled, true}.
+
+{clean_files, ["test/*.beam"]}.
--- /dev/null
+{application, x_plane_data,
+ [
+ {description, "X-Plane UDP data packet parser."},
+ {vsn, "0.0.0"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib,
+ hope
+ ]},
+ {env, []}
+ ]}.
--- /dev/null
+-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(<<Packet/binary>>) ->
+ 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:?BYTE_SIZE_OF_EACH_BLOCK/bytes, Blocks/binary>>) ->
+ [Block | blocks_split(Blocks)].
--- /dev/null
+-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(<<Block/binary>>) ->
+ of_bin(Block, ?DEFAULT_MAX_INDEX).
+
+-spec of_bin(binary(), non_neg_integer()) ->
+ hope_result:t(t(), parsing_error()).
+of_bin(<<Block/binary>>, 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(<<Block/binary>>, _) ->
+ % 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}.
--- /dev/null
+-define(DEFAULT_MAX_INDEX, 133). % As of X-Plane 10.36r1
--- /dev/null
+-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 = <<MaxIndex:32/little-integer, FakeBlockData/binary>>,
+ FakeBlockBadIndex = <<BadIndex:32/little-integer, FakeBlockData/binary>>,
+ {error, {block_index_byte_out_of_range, {BadIndex,_,_,_,_,_,_,_,_}}} =
+ x_plane_data:of_bin(<<Packet/binary, FakeBlockBadIndex/binary>>),
+ {error, packet_unrecognized} =
+ x_plane_data:of_bin(<<"bad-header", Packet/binary>>),
+ {error, packet_length_invalid} =
+ x_plane_data:of_bin(<<Packet/binary, "extra-stuff">>),
+ {ok, Data} =
+ x_plane_data:of_bin(<<Packet/binary, FakeBlockOk/binary>>),
+ {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=">>
+ ].