diff --git a/include/common.hrl b/include/common.hrl index 38e6e37..2290ebb 100644 --- a/include/common.hrl +++ b/include/common.hrl @@ -16,7 +16,7 @@ -ifdef(debug). -define(DBGTRC(Msg), io:format("~s:~s (~p) => ~s~n",[?MODULE,?FUNCTION_NAME,?LINE,Msg])). - -define(DBGSTR(Msg), io_lib:format("~s:~s (~B) => -s~n",[?MODULE,?FUNCTION_NAME,?LINE,Msg])). + -define(DBGSTR(Msg), io_lib:format("~s:~s (~B) => ~s~n",[?MODULE,?FUNCTION_NAME,?LINE,Msg])). -define(DBGSTR(FMsg,Args), io_lib:format("~s:~s (~B) => " FMsg "~n",[?MODULE,?FUNCTION_NAME,?LINE] ++ [Args])). -else. -define(DBGTRC(Msg), true). diff --git a/src/ovsdb_ap.erl b/src/ovsdb_ap.erl new file mode 100644 index 0000000..60ab1a4 --- /dev/null +++ b/src/ovsdb_ap.erl @@ -0,0 +1,214 @@ +%%%---------------------------------------------------------------------------- +%%% @author helge +%%% @copyright (C) 2020, Arilia Wireless Inc. +%%% @doc +%%% +%%% @end +%%% Created : 16. Nov 2020 12:44 p.m. +%%%---------------------------------------------------------------------------- +-module(ovsdb_ap). +-author("helge"). + +-behaviour(gen_server). + +-include("../include/common.hrl"). + +-define(SERVER, ?MODULE). + + + +%%----------------------------------------------------------------------------- +%% @doc The SPEC for starting a AP node is a proplist with the following fileds. +%% +%% {manager, ::pid()} - the pid of the simmanager used to get configuration. +%% {ap_serial, ::string()} - the serial number of the AP. +%% {ap_type, ::binary()} - type of AP to simulate e.g. EA8300. +%% {uuid, ::term()} - arbitrary unique id to identify this simnode AP. +%% @end +%%----------------------------------------------------------------------------- + + +%% API +-export([start/2]). +-export([uuid/1]). + + +%% gen_server callbacks +-export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2, code_change/3]). + + + +%% data structures + +-record(ap_state, { + sim_node :: pid(), % controlling simnode + sim_manager :: pid(), % manager to be contacted to get configuration + serial :: string(), % serial number of the access point + type :: binary(), % device type e.g. EA8300 + uuid :: term(), % unique ID for this AP node + timer :: owls_timers:tms(), + status = init :: init | ready | running | stopped, % internal status + config = #{} :: map(), + statistics = #{} :: map() +}). + + + + +%%%============================================================================ +%%% API +%%%============================================================================ + + +-spec start (Handler, Spec) -> {ok, Pid} | {error, Reason} when + Handler :: pid(), + Spec :: proplists:proplist(), + Pid :: pid(), + Reason :: term(). + +start (Handler, Spec) -> + gen_server:start(?MODULE, {Handler, Spec}, []). + + + + +-spec uuid (Node) -> Uuid when + Node :: pid(), + Uuid :: term(). + +uuid (Node) -> + gen_server:call(Node,ap_uuid). + + + + +%%%============================================================================ +%%% GEN_SERVER callbacks +%%%============================================================================ + +-spec init ({Handler, Spec}) -> {ok, State} when + Handler :: pid(), + Spec :: proplists:proplist(), + State :: #ap_state{}. + +init ({Handler,Spec}) -> + process_flag(trap_exit, true), + InitialState = prepare_state(Handler,Spec), + gen_server:cast(self(),start_up), + {ok, InitialState}. + + + + +-spec handle_cast (Request, State) -> {noreply, NewState} | {stop, Reason, NewState} when + Request :: term(), + State :: #ap_state{}, + NewState :: #ap_state{}, + Reason :: string(). + +handle_cast (start_up, State) -> + {ok, NewState} = startup_ap(State), + State#ap_state.sim_node ! {ready, {self(), State#ap_state.uuid}}, + {noreply, NewState}; + +handle_cast (R,State) -> + ?L_E(?DBGSTR("got unknown request: ~p",[R])), + {noreply, State}. + + + + +-spec handle_call (Request, From, State) -> {reply, Reply, NewState} | {stop, Reason, Reply, NewState} when + Request :: term(), + From :: {pid(),Tag::term()}, + State :: #ap_state{}, + Reply :: term(), + Reason :: term(), + NewState :: #ap_state{}. + +handle_call (ap_uuid,_,State) -> + {reply, State#ap_state.uuid, State}; + +handle_call (Request, From, State) -> + ?L_E(?DBGSTR("got unknow request ~p from ~p",[Request,From])), + {reply, invalid, State}. + + + + +-spec handle_info (Msg, State) -> {noreply, NewState} when + Msg :: term(), + State :: #ap_state{}, + NewState :: #ap_state{}. + +handle_info({'EXIT', _Pid, _Reason}, State) -> + %% @TODO: implement proper restart strategy for linked processes + {noreply, State}; + + +handle_info (Msg,State) -> + ?L_E(?DBGSTR("got unexpected info message ~p",[Msg])), + {noreply, State}. + + + + +-spec terminate (Reason, State) -> ok when + Reason :: shutdown | normal, + State :: #ap_state{}. + +terminate (_Reason, _State) -> + ok. + + + + +-spec code_change (OldVersion, OldState, Extra) -> {ok, NewState} when + OldVersion :: term(), + OldState ::#ap_state{}, + Extra :: term(), + NewState :: #ap_state{}. + +code_change (_,OldState,_) -> + ?L_E(?DBGSTR("code change requested")), + {ok, OldState}. + + + + +%%%============================================================================ +%%% internal functions +%%%============================================================================ + + +%---------prepare_state/2----------------convert Spec proplist into internal state + +-spec prepare_state (Node, Spec) -> State when + Node :: pid(), + Spec :: proplists:proplist(), + State :: #ap_state{}. + +prepare_state (Node,Spec) -> + #ap_state{ + sim_node = Node, + sim_manager = proplists:get_value(manager,Spec,Node), + serial = proplists:get_value(ap_serial,Spec,"1P000000000"), + type = proplists:get_value(ap_type,Spec,<<"EA8300">>), + uuid = proplists:get_value(uuid,Spec,erlang:phash2({node(),erlang:system_time()})), + timer = owls_timers:new(millisecond) + }. + + + + +%--------startup_ap/1--------------------initiate startup sequence +-spec startup_ap (State) -> {ok, NewState} | {error, Reason} when + State :: #ap_state{}, + NewState :: #ap_state{}, + Reason :: string(). + +startup_ap (#ap_state{status=init}=State) -> + {ok,State#ap_state{status=ready}}; + +startup_ap (_) -> + {error,"not in initial state, cant start-up"}. \ No newline at end of file diff --git a/src/ovsdb_client.erl b/src/ovsdb_client.erl deleted file mode 100644 index 0bd6e42..0000000 --- a/src/ovsdb_client.erl +++ /dev/null @@ -1,128 +0,0 @@ -%%%------------------------------------------------------------------- -%%% @author helge -%%% @copyright (C) 2020, Arilia Wireless Inc. -%%% @doc -%%% -%%% @end -%%% Created : 16. Nov 2020 12:44 p.m. -%%%------------------------------------------------------------------- --module(ovsdb_client). --author("helge"). - --behaviour(gen_server). - --include("../include/common.hrl"). - --define(SERVER, ?MODULE). - - - -%% API --export([start_link/2]). - - -%% gen_server callbacks --export([init/1, handle_cast/2, handle_call/3]). %, handle_cast/2, handle_info/2, terminate/2, code_change/3]). - - - -%% data structures - --record(client_state, { - sim_node :: pid(), % controlling simnode - sim_manager :: pid(), % manager to be contacted to get configuration - serial :: string(), % serial number of the access point - type :: binary(), % device type e.g. EA8300 - timer :: owls_timers:tms(), - satus = init :: init | ready | running | stopped, % internal status - config = #{} :: map(), - statistics = #{} :: map() -}). - - - - -%%%============================================================================ -%%% API -%%%============================================================================ - - --spec start_link(CallbackNode::pid(), Spec::proplists:proplist()) -> - {ok, Pid :: pid()}. - -start_link(Node,Spec) -> - gen_server:start_link(?MODULE, {Node,Spec}, []). - - - - -%%%============================================================================ -%%% GEN_SERVER callbacks -%%%============================================================================ - --spec init ({Node, Spec}) -> {ok, State} when - Node :: pid(), - Spec :: proplists:proplist(), - State :: #client_state{}. - -init ({Node,Spec}) -> - InitialState = prepare_state(Node,Spec), - gen_server:cast(self(),start_up), - {ok, InitialState}. - - - - --spec handle_cast (Request, State) -> {noreply, NewState} | {stop, Reason, NewState} when - Request :: term(), - State :: #client_state{}, - NewState :: #client_state{}, - Reason :: string(). - -handle_cast (start_up, State) -> - %TODO: startup - {noreply, State}; - -handle_cast (R,State) -> - ?L_E(?DBGSTR("got unknown request: ~p",[R])), - {noreply, State}. - - - - --spec handle_call (Request, From, State) -> {reply, Reply, NewState} | {stop, Reason, Reply, NewState} when - Request :: term(), - From :: {pid(),Tag::term()}, - State :: #client_state{}, - Reply :: term(), - Reason :: term(), - NewState :: #client_state{}. - -handle_call (Request, From, State) -> - ?L_E(?DBGSTR("got unknow request ~p from ~p",[Request,From])), - {reply, invalid, State}. - - - - - -%%%============================================================================ -%%% internal functions -%%%============================================================================ - - -%---------prepare_state------------------convert Spec proplist into internal state - --spec prepare_state (Node, Spec) -> State when - Node :: pid(), - Spec :: proplists:proplist(), - State :: #client_state{}. - -prepare_state (Node,Spec) -> - #client_state{ - sim_node = Node, - sim_manager = proplists:get_value(manager,Spec,Node), - serial = proplists:get_value(ap_serial,Spec,"1P000000000"), - type = proplists:get_value(ap_type,Spec,<<"EA8300">>), - timer = owls_timers:new(millisecond) - }. diff --git a/src/ovsdb_client_handler.erl b/src/ovsdb_client_handler.erl new file mode 100644 index 0000000..9f24059 --- /dev/null +++ b/src/ovsdb_client_handler.erl @@ -0,0 +1,181 @@ +%%%----------------------------------------------------------------------------- +%%% @author helge +%%% @copyright (C) 2020, Arilia Wireless Inc. +%%% @doc +%%% +%%% @end +%%% Created : 18. November 2020 @ 15:29:05 +%%%----------------------------------------------------------------------------- +-module(ovsdb_client_handler). +-author("helge"). + +-behaviour(gen_server). + +-define(SERVER, ?MODULE). + +%% API +-export([start_link/0]). +-export([set_configuration/1, start/0, stop/0, pause/0, resume/0, cancel/0, report/0]). + +%% gen_server callbacks +-export([init/1, handle_cast/2, handle_call/3, handle_info/2, terminate/2, code_change/3]). + + + +%% data structures + +-record(hdl_state, { + clients = [] :: [{Id::term(),pid()}], + config :: term() +}). + + + +%%%============================================================================ +%%% API +%%%============================================================================ + + +-spec start_link () -> {ok, Pid} | {error, Reason} when + Pid :: pid(), + Reason :: term(). + +start_link () -> + gen_server:start_link({local, ?SERVER},?MODULE, [], []). + + + +-spec set_configuration (Cfg) -> {ok, set} | {error, Reason} when + Cfg :: term(), + Reason :: term(). + +set_configuration (Cfg) -> + gen_server:call(?SERVER,{set_config, Cfg}). + + + +-spec start () -> {ok, started} | {timeout, Reason} | {error, Reason} when + Reason :: term(). + +start () -> + gen_server:call(?SERVER,start_sim). + + + +-spec stop () -> ok. + +stop () -> + gen_server:cast(?SERVER,stop_sim). + + + +-spec pause () -> {ok, paused} | {timeout, Reason} | {error, Reason} when + Reason :: term(). + +pause () -> + gen_server:call(?SERVER,pause_sim). + + + +-spec resume () -> {ok, resumed} | {timeout, Reason} | {error, Reason} when + Reason :: term(). + +resume () -> + gen_server:call(?SERVER,resume_sim). + + + +-spec cancel () -> {ok, cancelled} | {timeout, Reason} | {error, Reason} when + Reason :: term(). + +cancel () -> + gen_server:call(?SERVER,cancel_sim). + + + +-spec report () -> {ok, Report} | {error, Reason} when + Report :: term(), + Reason :: term(). + +report () -> + gen_server:call(?SERVER,get_report). + + + +%%%============================================================================ +%%% GEN_SERVER callbacks +%%%============================================================================ + +-spec init (Args) -> {ok, State} when + Args :: term(), + State :: #hdl_state{}. + +init (_) -> + {ok, #hdl_state{}}. + + + + +-spec handle_cast (Request, State) -> {noreply, NewState} | {stop, Reason, NewState} when + Request :: term(), + State :: #hdl_state{}, + NewState :: #hdl_state{}, + Reason :: term(). + +handle_cast (_,State) -> + {noreply, State}. + + + + +-spec handle_call (Request, From, State) -> {reply, Reply, NewState} | {stop, Reason, Reply, NewState} when + Request :: term(), + From :: {pid(),Tag::term()}, + State :: #hdl_state{}, + Reply :: term(), + Reason :: term(), + NewState :: #hdl_state{}. + +handle_call (_, _, State) -> + {reply, invalid, State}. + + + + +-spec handle_info (Msg, State) -> {noreply, NewState} when + Msg :: term(), + State :: #hdl_state{}, + NewState :: #hdl_state{}. + +handle_info(_, State) -> + {noreply, State}. + + + + +-spec terminate (Reason, State) -> ok when + Reason :: shutdown | {shutdown, term()} | normal, + State :: #hdl_state{}. + +terminate (_Reason, _State) -> + ok. + + + + +-spec code_change (OldVersion, OldState, Extra) -> {ok, NewState} when + OldVersion :: term(), + OldState ::#hdl_state{}, + Extra :: term(), + NewState :: #hdl_state{}. + +code_change (_,OldState,_) -> + {ok, OldState}. + + + + +%%%============================================================================ +%%% internal functions +%%%============================================================================ +