From 107b626945c401bffe00644d196f8a5a351637e7 Mon Sep 17 00:00:00 2001 From: Luca Succi Date: Fri, 24 Oct 2025 16:01:30 +0200 Subject: [PATCH 1/3] Rename state waiting_ip into waiting_network --- src/grisp_connect_client.erl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/grisp_connect_client.erl b/src/grisp_connect_client.erl index 71f2c57..97d706a 100644 --- a/src/grisp_connect_client.erl +++ b/src/grisp_connect_client.erl @@ -25,7 +25,7 @@ % State Functions -export([idle/3]). --export([waiting_ip/3]). +-export([waiting_network/3]). -export([connecting/3]). -export([connected/3]). @@ -127,7 +127,7 @@ init([]) -> % The error list is put in a persistent term to not add noise to the state. persistent_term:put({?MODULE, self()}, generic_errors()), NextState = case AutoConnect of - true -> waiting_ip; + true -> waiting_network; false -> idle end, {ok, NextState, Data}. @@ -152,17 +152,17 @@ idle(enter, _OldState, idle({call, From}, wait_connected, _) -> {keep_state_and_data, [{reply, From, {error, not_connecting}}]}; idle(cast, connect, Data) -> - {next_state, waiting_ip, Data}; + {next_state, waiting_network, Data}; ?HANDLE_COMMON. -% @doc State waiting_ip is used to check the device has an IP address. +% @doc State waiting_network is used to check the device has an IP address. % The first time entering this state, the check will be performed right away. % If the device do not have an IP address, it will wait a fixed amount of time % and check again, without incrementing the retry counter. -waiting_ip(enter, _OldState, _Data) -> +waiting_network(enter, _OldState, _Data) -> % First IP check do not have any delay {keep_state_and_data, [{state_timeout, 0, check_ip}]}; -waiting_ip(state_timeout, check_ip, Data) -> +waiting_network(state_timeout, check_ip, Data) -> case grisp_connect_utils:check_inet_ipv4() of {ok, IP} -> ?LOG_DEBUG(#{description => ?FORMAT("IP Address available: ~s", @@ -171,7 +171,7 @@ waiting_ip(state_timeout, check_ip, Data) -> {next_state, connecting, Data}; invalid -> ?LOG_DEBUG(#{description => <<"Waiting for an IP address do connect to grisp.io">>, - event => waiting_ip}), + event => waiting_network}), {keep_state_and_data, [{state_timeout, 1000, check_ip}]} end; ?HANDLE_COMMON. @@ -331,7 +331,7 @@ reconnect(Data = #data{retry_count = RetryCount, last_error = LastError}, % When reconnecting we always increment the retry counter, even if we % where connected and it was reset to 0, the next step will always be % retry number 1. It should never reconnect right away. - {next_state, waiting_ip, + {next_state, waiting_network, Data#data{retry_count = RetryCount + 1, last_error = Error}}. % Connection Functions From c95d0185a5542573d4bf4d583a0a395a3a0233ae Mon Sep 17 00:00:00 2001 From: Luca Succi Date: Mon, 27 Oct 2025 10:22:47 +0100 Subject: [PATCH 2/3] Optionally react to grisp_netman network events to determin if there is connectivity. --- src/grisp_connect_app.erl | 6 ++++++ src/grisp_connect_client.erl | 38 ++++++++++++++++++++++++++++++---- src/grisp_connect_netman.erl | 40 ++++++++++++++++++++++++++++++++++++ src/grisp_connect_utils.erl | 5 +++++ 4 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/grisp_connect_netman.erl diff --git a/src/grisp_connect_app.erl b/src/grisp_connect_app.erl index 21f2558..4835cd9 100644 --- a/src/grisp_connect_app.erl +++ b/src/grisp_connect_app.erl @@ -18,6 +18,12 @@ start(_StartType, _StartArgs) -> logger:add_handlers(grisp_connect), + case grisp_connect_utils:using_grisp_netman() of + true -> + grisp_connect_netman:add_handler(); + false -> + ok + end, grisp_connect_sup:start_link(). stop(_State) -> diff --git a/src/grisp_connect_client.erl b/src/grisp_connect_client.erl index 97d706a..5939a61 100644 --- a/src/grisp_connect_client.erl +++ b/src/grisp_connect_client.erl @@ -19,6 +19,8 @@ % Internal API -export([reboot/0]). +-export([lan_connected/0]). +-export([lan_disconnected/0]). % Behaviour gen_statem callback functions -export([init/1, terminate/3, code_change/4, callback_mode/0]). @@ -106,6 +108,11 @@ notify(Method, Type, Params) -> reboot() -> erlang:send_after(1000, ?MODULE, reboot). +lan_connected() -> + gen_statem:cast(?MODULE, ?FUNCTION_NAME). + +lan_disconnected() -> + gen_statem:cast(?MODULE, ?FUNCTION_NAME). %--- Behaviour gen_statem Callback Functions ----------------------------------- @@ -134,8 +141,7 @@ init([]) -> terminate(Reason, _State, Data) -> conn_close(Data, Reason), - persistent_term:erase({?MODULE, self()}), - ok. + persistent_term:erase({?MODULE, self()}). code_change(_Vsn, State, Data, _Extra) -> {ok, State, Data}. @@ -160,8 +166,23 @@ idle(cast, connect, Data) -> % If the device do not have an IP address, it will wait a fixed amount of time % and check again, without incrementing the retry counter. waiting_network(enter, _OldState, _Data) -> - % First IP check do not have any delay - {keep_state_and_data, [{state_timeout, 0, check_ip}]}; + Actions = case grisp_connect_utils:using_grisp_netman() of + true -> + case grisp_netman:connection_status() of + S when S =:= lan orelse S =:= internet -> + ?MODULE:lan_connected(); + disconnected -> + ok + end, + []; + _ -> + [{state_timeout, 0, check_ip}] + end, + {keep_state_and_data, Actions}; +waiting_network(cast, lan_connected, Data) -> + ?LOG_DEBUG(#{description => <<"Connected to LAN">>, + event => lan_connected}), + {next_state, connecting, Data}; waiting_network(state_timeout, check_ip, Data) -> case grisp_connect_utils:check_inet_ipv4() of {ok, IP} -> @@ -225,6 +246,15 @@ connected(cast, {notify, Method, Type, Params}, Data) -> % Common event handling appended as last match case to each state_function handle_common(cast, connect, State, _Data) when State =/= idle -> keep_state_and_data; +handle_common(cast, lan_connected, State, _Data) +when State =/= waiting_network -> + keep_state_and_data; +handle_common(cast, lan_disconnected, State, Data) +when State =/= idle, State =/= waiting_network -> + Reason = lan_disconnected, + ?LOG_WARNING(#{description => <<"LAN connection lost">>, + event => lan_disconnected}), + reconnect(conn_close(Data, Reason), Reason); handle_common({call, From}, is_connected, State, _) when State =/= connected -> {keep_state_and_data, [{reply, From, false}]}; handle_common({call, From}, wait_connected, _State, diff --git a/src/grisp_connect_netman.erl b/src/grisp_connect_netman.erl new file mode 100644 index 0000000..cc4e525 --- /dev/null +++ b/src/grisp_connect_netman.erl @@ -0,0 +1,40 @@ +-module(grisp_connect_netman). + +-behaviour(gen_event). + +-export([add_handler/0, remove_handler/0]). +-export([init/1, handle_event/2, handle_call/2]). + +-include_lib("kernel/include/logger.hrl"). + +add_handler() -> + gen_event:add_handler(grisp_netman_event, ?MODULE, []). + +remove_handler() -> + gen_event:delete_handler(grisp_netman_event, ?MODULE, []). + +% Behaviour gen_event callbacks ------------------------------------------------ + +init([]) -> + {ok, #{}}. + +handle_event({connection_status, _IfName, Status}, State) -> + case Status of + S when S =:= lan orelse S =:= internet -> + grisp_connect_client:lan_connected(); + disconnected -> + % If one interface went down, + % check if the global connectivity is still maintained + % by any other interface + case grisp_netman:connection_status() of + S when S =:= lan orelse S =:= internet -> + ok; + disconnected -> + grisp_connect_client:lan_disconnected() + end + end, + {ok, State}. + +handle_call(Request, State) -> + ?LOG_WARNING("Unexpected grisp_netman_event call: ~p", [Request]), + {reply, unexpected_call, State}. diff --git a/src/grisp_connect_utils.erl b/src/grisp_connect_utils.erl index ed2ba84..f5e98f4 100644 --- a/src/grisp_connect_utils.erl +++ b/src/grisp_connect_utils.erl @@ -4,12 +4,17 @@ %--- Exports ------------------------------------------------------------------- % API functions +-export([using_grisp_netman/0]). -export([retry_delay/1]). -export([check_inet_ipv4/0]). %--- API Functions ------------------------------------------------------------- +using_grisp_netman() -> + RunningApps = application:which_applications(), + lists:keymember(grisp_netman, 1, RunningApps). + check_inet_ipv4() -> case get_ip_of_valid_interfaces() of {ok, {IP1, _, _, _} = IP} when IP1 =/= 127 -> {ok, IP}; From 2b641bf9195877dfd948a8ea2b84f44d7f740c54 Mon Sep 17 00:00:00 2001 From: Luca Succi Date: Wed, 12 Nov 2025 13:23:02 +0100 Subject: [PATCH 3/3] Clenup event handler --- src/grisp_connect_app.erl | 9 +++++++-- ...tman.erl => grisp_connect_netman_handler.erl} | 16 ++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) rename src/{grisp_connect_netman.erl => grisp_connect_netman_handler.erl} (73%) diff --git a/src/grisp_connect_app.erl b/src/grisp_connect_app.erl index 4835cd9..49d87cb 100644 --- a/src/grisp_connect_app.erl +++ b/src/grisp_connect_app.erl @@ -20,13 +20,18 @@ start(_StartType, _StartArgs) -> logger:add_handlers(grisp_connect), case grisp_connect_utils:using_grisp_netman() of true -> - grisp_connect_netman:add_handler(); + grisp_netman:add_event_handler(grisp_connect_netman_handler, []); false -> ok end, grisp_connect_sup:start_link(). stop(_State) -> - ok. + case grisp_connect_utils:using_grisp_netman() of + true -> + grisp_netman:delete_event_handler(grisp_connect_netman_handler, []); + false -> + ok + end. %--- Internal Functions -------------------------------------------------------- diff --git a/src/grisp_connect_netman.erl b/src/grisp_connect_netman_handler.erl similarity index 73% rename from src/grisp_connect_netman.erl rename to src/grisp_connect_netman_handler.erl index cc4e525..7099756 100644 --- a/src/grisp_connect_netman.erl +++ b/src/grisp_connect_netman_handler.erl @@ -1,21 +1,17 @@ --module(grisp_connect_netman). +-module(grisp_connect_netman_handler). +-moduledoc """ +Handler for grisp_netman events. +""". -behaviour(gen_event). --export([add_handler/0, remove_handler/0]). -export([init/1, handle_event/2, handle_call/2]). -include_lib("kernel/include/logger.hrl"). -add_handler() -> - gen_event:add_handler(grisp_netman_event, ?MODULE, []). - -remove_handler() -> - gen_event:delete_handler(grisp_netman_event, ?MODULE, []). - % Behaviour gen_event callbacks ------------------------------------------------ -init([]) -> +init(_) -> {ok, #{}}. handle_event({connection_status, _IfName, Status}, State) -> @@ -36,5 +32,5 @@ handle_event({connection_status, _IfName, Status}, State) -> {ok, State}. handle_call(Request, State) -> - ?LOG_WARNING("Unexpected grisp_netman_event call: ~p", [Request]), + ?LOG_WARNING("Unexpected ~p call: ~p", [?MODULE, Request]), {reply, unexpected_call, State}.