From cff47be336b0bfe12047a616ffcfb2873ab7a330 Mon Sep 17 00:00:00 2001
From: IciaC
Date: Mon, 9 Sep 2024 15:34:56 +0200
Subject: [PATCH 01/18] WIP
---
src/grisp_connect_api.erl | 29 +++++++++++
src/grisp_connect_updater_progress.erl | 70 ++++++++++++++++++++++++++
2 files changed, 99 insertions(+)
create mode 100644 src/grisp_connect_updater_progress.erl
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 33dcb69..3101567 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -39,6 +39,20 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
handle_rpc_messages(Batch,
[grisp_connect_jsonrpc:format_error(E)| Replies]).
+handle_request(<<"post">>, #{type := <<"start_update">>} = Params, ID) ->
+ URL = maps:get(url, Params, 1),
+ case start_update(URL) of
+ % TODO: start_update error cast
+ {error, update_unavailable} -> {error,
+ -564095940,
+ <<"grisp_updater unavailable">>,
+ undefined,
+ ID};
+ {error, Reason} -> {error,
+ -32603,
+ iolist_to_binary(io_lib:format("~p", [Reason])), ID};
+ ok -> {result, undefined, ID}
+ end;
handle_request(<<"post">>, #{type := <<"flash">>} = Params, ID) ->
Led = maps:get(led, Params, 1),
Color = maps:get(color, Params, red),
@@ -64,6 +78,21 @@ flash(Led, Color) ->
end),
ok.
+start_update(URL) ->
+ case is_running(grisp_updater) of
+ true -> grisp_updater:start_update(URL,
+ grisp_conntect_updater_progress,
+ #{client => self()}, #{});
+ false -> {error, update_unavailable}
+ end.
+
+is_running(AppName) ->
+ Apps = application:which_applications(),
+ case [App || {App, _Desc, _VSN} <- Apps, App =:= AppName] of
+ [] -> false;
+ [_] -> true
+ end.
+
error_atom(-1) -> device_not_linked;
error_atom(-2) -> token_expired;
error_atom(-3) -> device_already_linked;
diff --git a/src/grisp_connect_updater_progress.erl b/src/grisp_connect_updater_progress.erl
new file mode 100644
index 0000000..4ff0f65
--- /dev/null
+++ b/src/grisp_connect_updater_progress.erl
@@ -0,0 +1,70 @@
+-module(grisp_connect_updater_progress).
+-behaviour(grisp_updater_progress).
+
+%--- Includes ------------------------------------------------------------------
+
+-include_lib("kernel/include/logger.hrl").
+
+
+%--- Types ---------------------------------------------------------------------
+
+
+%--- Exports -------------------------------------------------------------------
+
+
+% Behaviour grisp_updater_progress callbacks
+-export([progress_init/1]).
+-export([progress_update/2]).
+-export([progress_warning/3]).
+-export([progress_error/3]).
+-export([progress_done/2]).
+
+
+%--- records -------------------------------------------------------------------
+
+-record(state, {
+ client :: pid(),
+ last_notification :: undefined | integer()
+}).
+
+
+%--- API Functions -------------------------------------------------------------
+
+
+%--- Behavior grisp_updater_progress Callback ----------------------------------
+
+progress_init(#{client := PID} = _Opts) ->
+ {ok, #state{
+ last_notification = erlang:system_time(millisecond),
+ client = PID
+ }}.
+
+progress_update(#state{last_notification = LastLog} = State, Stats) ->
+ case (erlang:system_time(millisecond) - LastLog) > 1000 of
+ false -> {ok, State};
+ true ->
+ % Recheck log level when there is another way to check the progress update
+ ?LOG_NOTICE("Update progress: ~b%", [progress_percent(Stats)]),
+ {ok, State#state{last_notification = erlang:system_time(millisecond)}}
+ end.
+
+progress_warning(State, Msg, Reason) ->
+ ?LOG_WARNING("Update warning; ~s: ~p", [Msg, Reason]),
+ {ok, State}.
+
+progress_error(#state{}, Stats, Reason) ->
+ ?LOG_ERROR("Update failed after ~b% : ~p",
+ [progress_percent(Stats), Reason]),
+ ok.
+
+progress_done(#state{}, _Stats) ->
+ ?LOG_NOTICE("Update done", []),
+ ok.
+
+
+%--- Internal Functions --------------------------------------------------------
+
+progress_percent(Stats) ->
+ #{data_total := Total, data_checked := Checked,
+ data_skipped := Skipped, data_written := Written} = Stats,
+ (Checked + Skipped + Written) * 100 div (Total * 2).
From c50d52abc8346c4a30b6e08a9ffc660443e85081 Mon Sep 17 00:00:00 2001
From: Luca Succi
Date: Mon, 23 Sep 2024 18:19:47 +0200
Subject: [PATCH 02/18] Send the REason to the peer in inteernal_error error
types
---
src/grisp_connect_jsonrpc.erl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/grisp_connect_jsonrpc.erl b/src/grisp_connect_jsonrpc.erl
index 8060bd7..3e83b09 100644
--- a/src/grisp_connect_jsonrpc.erl
+++ b/src/grisp_connect_jsonrpc.erl
@@ -47,8 +47,8 @@ format_error({internal_error, method_not_found, ID}) ->
{error, -32601, <<"Method not found">>, undefined, ID};
format_error({internal_error, invalid_params, ID}) ->
{error, -32602, <<"Invalid params">>, undefined, ID};
-format_error({internal_error, internal_error, ID}) ->
- {error, -32603, <<"Internal error">>, undefined, ID}.
+format_error({internal_error, Reason, ID}) ->
+ {error, -32603, <<"Internal error">>, Reason, ID}.
%--- Internal -----------------------------------------------------------------
From dc3cf2f1c36daa65f20e5ff19ba557d3a11c0ef1 Mon Sep 17 00:00:00 2001
From: Luca Succi
Date: Mon, 23 Sep 2024 18:21:49 +0200
Subject: [PATCH 03/18] Cleanup doc and spec, fix communication errors in
jsonrpc handling
---
src/grisp_connect_api.erl | 41 ++++++++++++++++++++++++++-------------
1 file changed, 27 insertions(+), 14 deletions(-)
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 3101567..622d3e0 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -8,12 +8,23 @@
%--- API -----------------------------------------------------------------------
+% #doc Assembles a jsonrpc request and its uuid
+-spec request(Method :: atom() | binary(),
+ Type :: atom() | binary(),
+ Params :: map()) -> {ID :: binary(), Encoded :: binary()}.
request(Method, Type, Params) ->
ID = id(),
Rpc = {request, Method, maps:put(type, Type, Params), ID},
Encoded = grisp_connect_jsonrpc:encode(Rpc),
{ID, Encoded}.
+% @doc Indentifies if the message is a request or a reply to a previous request.
+% In case it was a request, returns the reply to be sent to the peer.
+% In case it was a response, returns the parsed ID and content to be handled by
+% the caller.
+-spec handle_msg(JSON :: binary()) ->
+ {request, Response :: binary()} |
+ {response, ID :: binary(), {ok, Result :: map()} | {error, atom()}}.
handle_msg(JSON) ->
JSON_RPC = grisp_connect_jsonrpc:decode(JSON),
handle_jsonrpc(JSON_RPC).
@@ -41,24 +52,26 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
handle_request(<<"post">>, #{type := <<"start_update">>} = Params, ID) ->
URL = maps:get(url, Params, 1),
- case start_update(URL) of
+ Reply = case start_update(URL) of
% TODO: start_update error cast
- {error, update_unavailable} -> {error,
- -564095940,
- <<"grisp_updater unavailable">>,
- undefined,
- ID};
- {error, Reason} -> {error,
- -32603,
- iolist_to_binary(io_lib:format("~p", [Reason])), ID};
- ok -> {result, undefined, ID}
- end;
+ {error, update_unavailable} ->
+ {error, -564095940, <<"grisp_updater unavailable">>, undefined, ID};
+ {error, Reason} ->
+ ReasonText = iolist_to_binary(io_lib:format("~p", [Reason])),
+ grisp_connect_jsonrpc:format_error({internal_error, Reason, ID});
+ ok ->
+ {result, ok, ID}
+ end,
+ {request, grisp_connect_jsonrpc:encode(Reply)};
handle_request(<<"post">>, #{type := <<"flash">>} = Params, ID) ->
Led = maps:get(led, Params, 1),
Color = maps:get(color, Params, red),
- {result, flash(Led, Color), ID};
+ Reply = {result, flash(Led, Color), ID},
+ {request, grisp_connect_jsonrpc:encode(Reply)};
handle_request(_, _, ID) ->
- grisp_connect_jsonrpc:format_error({internal_error, method_not_found, ID}).
+ Error = {internal_error, method_not_found, ID},
+ FormattedError = grisp_connect_jsonrpc:format_error(),
+ grisp_connect_jsonrpc:encode(FormattedError).
handle_response(Response) ->
{ID, Reply} = case Response of
@@ -80,7 +93,7 @@ flash(Led, Color) ->
start_update(URL) ->
case is_running(grisp_updater) of
- true -> grisp_updater:start_update(URL,
+ true -> grisp_updater:start_update(URL,
grisp_conntect_updater_progress,
#{client => self()}, #{});
false -> {error, update_unavailable}
From 995fc0b76a03b1e65451e3d3d3c4334e98e49777 Mon Sep 17 00:00:00 2001
From: Luca Succi
Date: Tue, 24 Sep 2024 15:54:47 +0200
Subject: [PATCH 04/18] Improve code readability in handle_msg return atoms
---
src/grisp_connect_api.erl | 10 +++++-----
src/grisp_connect_client.erl | 8 ++++----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 622d3e0..0bf795d 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -23,8 +23,8 @@ request(Method, Type, Params) ->
% In case it was a response, returns the parsed ID and content to be handled by
% the caller.
-spec handle_msg(JSON :: binary()) ->
- {request, Response :: binary()} |
- {response, ID :: binary(), {ok, Result :: map()} | {error, atom()}}.
+ {send_response, Response :: binary()} |
+ {handle_response, ID :: binary(), {ok, Result :: map()} | {error, atom()}}.
handle_msg(JSON) ->
JSON_RPC = grisp_connect_jsonrpc:decode(JSON),
handle_jsonrpc(JSON_RPC).
@@ -62,12 +62,12 @@ handle_request(<<"post">>, #{type := <<"start_update">>} = Params, ID) ->
ok ->
{result, ok, ID}
end,
- {request, grisp_connect_jsonrpc:encode(Reply)};
+ {send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(<<"post">>, #{type := <<"flash">>} = Params, ID) ->
Led = maps:get(led, Params, 1),
Color = maps:get(color, Params, red),
Reply = {result, flash(Led, Color), ID},
- {request, grisp_connect_jsonrpc:encode(Reply)};
+ {send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(_, _, ID) ->
Error = {internal_error, method_not_found, ID},
FormattedError = grisp_connect_jsonrpc:format_error(),
@@ -80,7 +80,7 @@ handle_response(Response) ->
{error, Code, _Message, _Data, ID0} ->
{ID0, {error, error_atom(Code)}}
end,
- {response, ID, Reply}.
+ {handle_response, ID, Reply}.
flash(Led, Color) ->
spawn(fun() ->
diff --git a/src/grisp_connect_client.erl b/src/grisp_connect_client.erl
index 2fc299d..40deedb 100644
--- a/src/grisp_connect_client.erl
+++ b/src/grisp_connect_client.erl
@@ -121,15 +121,15 @@ connected(cast, disconnected, Data) ->
grisp_connect_log_server:stop(),
{next_state, waiting_ip, Data};
connected(cast, {handle_message, Payload}, #data{requests = Requests} = Data) ->
- Replies = grisp_connect_api:handle_msg(Payload),
+ Responses = grisp_connect_api:handle_msg(Payload),
% A reduce operation is needed to support jsonrpc batch comunications
- case Replies of
+ case Responses of
[] ->
keep_state_and_data;
- [{request, Response}] -> % Response for a GRiSP.io request
+ [{send_response, Response}] -> % Response for a GRiSP.io request
grisp_connect_ws:send(Response),
keep_state_and_data;
- [{response, ID, Response}] -> % GRiSP.io response
+ [{handle_response, ID, Response}] -> % handle a GRiSP.io response
{OtherRequests, Actions} = dispatch_response(ID, Response, Requests),
{keep_state, Data#data{requests = OtherRequests}, Actions}
end;
From 8c7e01842391a879bbf9e65dbd00cc86a92d3c34 Mon Sep 17 00:00:00 2001
From: IciaC
Date: Wed, 25 Sep 2024 15:43:45 +0200
Subject: [PATCH 05/18] WIP
---
docs/grisp_connect_api.md | 72 +++++++++++++++++++++++++++++++++++++++
src/grisp_connect_api.erl | 52 +++++++++++++++-------------
2 files changed, 101 insertions(+), 23 deletions(-)
create mode 100644 docs/grisp_connect_api.md
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
new file mode 100644
index 0000000..a00bddc
--- /dev/null
+++ b/docs/grisp_connect_api.md
@@ -0,0 +1,72 @@
+# UI Websocket API
+
+**Table Of Contents**
+- [UI Websocket API](#ui-websocket-api)
+ - [Backend API](#backend-api)
+ - [Requests](#requests)
+ - [Error Codes](#error-codes)
+ - [Default error codes](#default-error-codes)
+ - [Custom error codes](#custom-error-codes)
+
+We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
+
+## Backend API
+
+### Requests
+
+Post - Start an update
+
+
+**`params`:**
+| key (required *) | value | description |
+| ----------------- | -------- | -------------------------- |
+| `"type"` * | string | `"start_update"` |
+| `"url"` * | [string] | URL to the code repository |
+
+**`result`**: `"ok"`
+
+**`error`**:
+
+| Error Content | When it Happens |
+| -------------------------------------------------------- | ------------------------------------------------- |
+| `{code: -32001, message: "grisp_updater unavailable"}` | When the grisp_updater application is not running |
+
+
+
+
+Post - Flash
+
+
+**`params`:**
+| key (required *) | value | description |
+| ------------------- | -------- | --------------------------------- |
+| `"led"` * | integer | Number that identifies the LED, to obtain more information about the options, you can visit [grisp_led:color/2](https://hexdocs.pm/grisp/) |
+| `"color"` | string | Color of the LED, by default: red. To obtain more information about the options, you can visit [grisp_led:color/2](https://hexdocs.pm/grisp/) |
+
+**`result`**: `"ok"`
+
+
+
+
+## Error Codes
+
+### Default error codes
+
+| code | message | meaning |
+|---------|------------------|--------------------------------------------------|
+|-32700 | Parse error | Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. |
+|-32600 | Invalid Request | The JSON sent is not a valid Request object. |
+|-32601 | Method not found | The method does not exist / is not available.|
+|-32602 | Invalid params | Invalid method parameter(s). |
+|-32603 | Internal error | Internal JSON-RPC error. |
+
+### Custom error codes
+
+Additionally to the default jsonrpc error codes the following codes will be returned.
+
+|code | message | meaning |
+|---|---|---|
+| -1 | `"device not linked"` | device can't be used without being linked to a registered user |
+| -2 | `"token expired"` | token is expired |
+| -3 | `"device already linked"` | device needs to be unlinked first via UI |
+| -4 | `"invalid token"` | token is e.g. not orderly encoded |
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 0bf795d..5f0222c 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -1,4 +1,4 @@
-%% @doc Librabry module containing the jsonrpc API logic
+%% @doc Library module containing the jsonrpc API logic
-module(grisp_connect_api).
-export([request/3]).
@@ -51,18 +51,23 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
[grisp_connect_jsonrpc:format_error(E)| Replies]).
handle_request(<<"post">>, #{type := <<"start_update">>} = Params, ID) ->
- URL = maps:get(url, Params, 1),
- Reply = case start_update(URL) of
- % TODO: start_update error cast
- {error, update_unavailable} ->
- {error, -564095940, <<"grisp_updater unavailable">>, undefined, ID};
- {error, Reason} ->
- ReasonText = iolist_to_binary(io_lib:format("~p", [Reason])),
- grisp_connect_jsonrpc:format_error({internal_error, Reason, ID});
- ok ->
- {result, ok, ID}
- end,
- {send_response, grisp_connect_jsonrpc:encode(Reply)};
+ try
+ URL = maps:get(url, Params, undefined),
+ Reply = case start_update(URL) of
+ {error, grisp_updater_unavailable} ->
+ {error, -10, grisp_updater_unavailable, undefined, ID};
+ {error, Reason} ->
+ ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
+ grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID});
+ ok ->
+ {result, ok, ID}
+ end,
+ {request, grisp_connect_jsonrpc:encode(Reply)}
+ catch
+ throw:invalid_params ->
+ {request,
+ grisp_connect_jsonrpc:format_error({internal_error, invalid_params, ID})}
+ end;
handle_request(<<"post">>, #{type := <<"flash">>} = Params, ID) ->
Led = maps:get(led, Params, 1),
Color = maps:get(color, Params, red),
@@ -70,7 +75,7 @@ handle_request(<<"post">>, #{type := <<"flash">>} = Params, ID) ->
{send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(_, _, ID) ->
Error = {internal_error, method_not_found, ID},
- FormattedError = grisp_connect_jsonrpc:format_error(),
+ FormattedError = grisp_connect_jsonrpc:format_error(Error),
grisp_connect_jsonrpc:encode(FormattedError).
handle_response(Response) ->
@@ -93,10 +98,10 @@ flash(Led, Color) ->
start_update(URL) ->
case is_running(grisp_updater) of
- true -> grisp_updater:start_update(URL,
- grisp_conntect_updater_progress,
- #{client => self()}, #{});
- false -> {error, update_unavailable}
+ true -> grisp_updater:start(URL,
+ grisp_conntect_updater_progress,
+ #{client => self()}, #{});
+ false -> {error, grisp_updater_unavailable}
end.
is_running(AppName) ->
@@ -106,11 +111,12 @@ is_running(AppName) ->
[_] -> true
end.
-error_atom(-1) -> device_not_linked;
-error_atom(-2) -> token_expired;
-error_atom(-3) -> device_already_linked;
-error_atom(-4) -> invalid_token;
-error_atom(_) -> jsonrpc_error.
+error_atom(-1) -> device_not_linked;
+error_atom(-2) -> token_expired;
+error_atom(-3) -> device_already_linked;
+error_atom(-4) -> invalid_token;
+error_atom(-10) -> grisp_updater_unavailable;
+error_atom(_) -> jsonrpc_error.
id() ->
list_to_binary(integer_to_list(erlang:unique_integer())).
From 8a0bda4678557f89c6755335a2f4a5f0ab0ee7f5 Mon Sep 17 00:00:00 2001
From: IciaC
Date: Thu, 26 Sep 2024 17:20:00 +0200
Subject: [PATCH 06/18] doc & fix grisp_updater call
---
docs/grisp_connect_api.md | 3 ++-
src/grisp_connect_api.erl | 22 ++++++++++++++++------
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
index a00bddc..bb248c1 100644
--- a/docs/grisp_connect_api.md
+++ b/docs/grisp_connect_api.md
@@ -29,7 +29,8 @@ We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
| Error Content | When it Happens |
| -------------------------------------------------------- | ------------------------------------------------- |
-| `{code: -32001, message: "grisp_updater unavailable"}` | When the grisp_updater application is not running |
+| `{code: -10, message: "grisp_updater unavailable"}` | Grisp updater app is not running |
+| `{code: -11, message: "already updating "}` | An update is already happening |
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 5f0222c..8ca1081 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -6,6 +6,12 @@
-include_lib("kernel/include/logger.hrl").
+%--- Macros --------------------------------------------------------------------
+-define(method_get, <<"get">>).
+-define(method_post, <<"post">>).
+-define(method_patch, <<"patch">>).
+-define(method_delete, <<"delete">>).
+
%--- API -----------------------------------------------------------------------
% #doc Assembles a jsonrpc request and its uuid
@@ -38,7 +44,7 @@ handle_jsonrpc({single, Rpc}) ->
handle_rpc_messages([], Replies) -> lists:reverse(Replies);
handle_rpc_messages([{request, M, Params, ID} | Batch], Replies)
-when M == <<"post">> ->
+ when M == ?method_post ->
handle_rpc_messages(Batch, [handle_request(M, Params, ID) | Replies]);
handle_rpc_messages([{result, _, _} = Res| Batch], Replies) ->
handle_rpc_messages(Batch, [handle_response(Res)| Replies]);
@@ -50,12 +56,14 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
handle_rpc_messages(Batch,
[grisp_connect_jsonrpc:format_error(E)| Replies]).
-handle_request(<<"post">>, #{type := <<"start_update">>} = Params, ID) ->
+handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
try
- URL = maps:get(url, Params, undefined),
+ URL = maps:get(url, Params),
Reply = case start_update(URL) of
{error, grisp_updater_unavailable} ->
{error, -10, grisp_updater_unavailable, undefined, ID};
+ {error, already_updating} ->
+ {error, -11, already_updating, undefined, ID};
{error, Reason} ->
ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID});
@@ -64,11 +72,12 @@ handle_request(<<"post">>, #{type := <<"start_update">>} = Params, ID) ->
end,
{request, grisp_connect_jsonrpc:encode(Reply)}
catch
- throw:invalid_params ->
+ throw:bad_key ->
{request,
- grisp_connect_jsonrpc:format_error({internal_error, invalid_params, ID})}
+ grisp_connect_jsonrpc:format_error(
+ {internal_error, invalid_params, ID})}
end;
-handle_request(<<"post">>, #{type := <<"flash">>} = Params, ID) ->
+handle_request(?method_post, #{type := <<"flash">>} = Params, ID) ->
Led = maps:get(led, Params, 1),
Color = maps:get(color, Params, red),
Reply = {result, flash(Led, Color), ID},
@@ -116,6 +125,7 @@ error_atom(-2) -> token_expired;
error_atom(-3) -> device_already_linked;
error_atom(-4) -> invalid_token;
error_atom(-10) -> grisp_updater_unavailable;
+error_atom(-11) -> already_updating;
error_atom(_) -> jsonrpc_error.
id() ->
From 52eaf211018eceba87874cd93e3bd11315879bcf Mon Sep 17 00:00:00 2001
From: IciaC
Date: Fri, 27 Sep 2024 16:55:19 +0200
Subject: [PATCH 07/18] Typo in callback module
---
src/grisp_connect_api.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 8ca1081..b90a6f8 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -108,7 +108,7 @@ flash(Led, Color) ->
start_update(URL) ->
case is_running(grisp_updater) of
true -> grisp_updater:start(URL,
- grisp_conntect_updater_progress,
+ grisp_connect_updater_progress,
#{client => self()}, #{});
false -> {error, grisp_updater_unavailable}
end.
From 411080358129942f5ffcc33dd71160c75b82b7ae Mon Sep 17 00:00:00 2001
From: IciaC
Date: Fri, 27 Sep 2024 17:05:31 +0200
Subject: [PATCH 08/18] update readme
---
README.md | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 3428e81..e4495e2 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
# grisp_connect
-GRiSP.io Client Library for GRiSP
+GRiSP.io Client Library for GRiSP. This library enables secure communication
+ between your GRiSP2 board and the [GRiSP.io](https://grisp.io) services using
+ Mutual TLS (mTLS). To get started, add this application as a dependency in your
+ GRiSP2 project.
+
+⚠️ **Note:** If you plan to use the API calls related to `grisp_updater`, make
+ sure to add `grisp_updater` as a dependency in your project as well.
## Table of content
@@ -28,10 +34,6 @@ GRiSP.io Client Library for GRiSP
- [Development on GRiSP Hardware](#development-on-grisp-hardware)
- [Production on GRiSP Hardware](#production-on-grisp-hardware)
-
-Add this application as a dependency in your GRiSP2 project.
-Your board will connect securely using mTLS to the [GRiSP.io](https://grisp.io) services.
-
## Usage
### Option 1. Use the `rebar3_grisp` plugin
From e7575006f1a574bc6d70f4c9177487f283335200 Mon Sep 17 00:00:00 2001
From: Luca Succi
Date: Wed, 2 Oct 2024 10:08:32 +0200
Subject: [PATCH 09/18] Fix api atoms
---
src/grisp_connect_api.erl | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index b90a6f8..c6e75f9 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -57,7 +57,7 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
[grisp_connect_jsonrpc:format_error(E)| Replies]).
handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
- try
+ try
URL = maps:get(url, Params),
Reply = case start_update(URL) of
{error, grisp_updater_unavailable} ->
@@ -70,10 +70,10 @@ handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
ok ->
{result, ok, ID}
end,
- {request, grisp_connect_jsonrpc:encode(Reply)}
+ {send_response, grisp_connect_jsonrpc:encode(Reply)}
catch
throw:bad_key ->
- {request,
+ {send_response,
grisp_connect_jsonrpc:format_error(
{internal_error, invalid_params, ID})}
end;
@@ -85,7 +85,7 @@ handle_request(?method_post, #{type := <<"flash">>} = Params, ID) ->
handle_request(_, _, ID) ->
Error = {internal_error, method_not_found, ID},
FormattedError = grisp_connect_jsonrpc:format_error(Error),
- grisp_connect_jsonrpc:encode(FormattedError).
+ {send_response, grisp_connect_jsonrpc:encode(FormattedError)}.
handle_response(Response) ->
{ID, Reply} = case Response of
From da340378b59c510e81e18efbd88851a1403e8406 Mon Sep 17 00:00:00 2001
From: IciaC
Date: Fri, 4 Oct 2024 01:25:51 +0200
Subject: [PATCH 10/18] Notify grisp_manager of update progress
---
docs/grisp_connect_api.md | 12 ++++++++++++
src/grisp_connect_api.erl | 20 ++++++++++++++++----
src/grisp_connect_updater_progress.erl | 12 +++++++++---
3 files changed, 37 insertions(+), 7 deletions(-)
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
index bb248c1..484d3ad 100644
--- a/docs/grisp_connect_api.md
+++ b/docs/grisp_connect_api.md
@@ -7,6 +7,7 @@
- [Error Codes](#error-codes)
- [Default error codes](#default-error-codes)
- [Custom error codes](#custom-error-codes)
+ - [Notifications](#notifications)
We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
@@ -71,3 +72,14 @@ Additionally to the default jsonrpc error codes the following codes will be retu
| -2 | `"token expired"` | token is expired |
| -3 | `"device already linked"` | device needs to be unlinked first via UI |
| -4 | `"invalid token"` | token is e.g. not orderly encoded |
+
+## Notifications
+
+Notify - Status Update
+
+**`result`**: `JSON Object`
+
+| key | value | description |
+| -------------- | ----------------- | --------------------------------- |
+| `"type"` | `"status_update"` | Type of notification |
+| `"percentage"` | integer | Progress percentage of the update |
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index c6e75f9..e6664ec 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -11,6 +11,7 @@
-define(method_post, <<"post">>).
-define(method_patch, <<"patch">>).
-define(method_delete, <<"delete">>).
+-define(method_notify, <<"notify">>).
%--- API -----------------------------------------------------------------------
@@ -56,6 +57,11 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
handle_rpc_messages(Batch,
[grisp_connect_jsonrpc:format_error(E)| Replies]).
+handle_request(?method_post, #{type := <<"flash">>} = Params, ID) ->
+ Led = maps:get(led, Params, 1),
+ Color = maps:get(color, Params, red),
+ Reply = {result, flash(Led, Color), ID},
+ {send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
try
URL = maps:get(url, Params),
@@ -77,10 +83,16 @@ handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
grisp_connect_jsonrpc:format_error(
{internal_error, invalid_params, ID})}
end;
-handle_request(?method_post, #{type := <<"flash">>} = Params, ID) ->
- Led = maps:get(led, Params, 1),
- Color = maps:get(color, Params, red),
- Reply = {result, flash(Led, Color), ID},
+handle_request(?method_notify, #{type := <<"status_update">>} = Params, ID) ->
+ Percentage = maps:get(percentage, Params, undefined),
+ Reply = case Percentage of
+ Per when is_integer(Per) ->
+ {result, #{percentage => Percentage}, ID};
+ undefined ->
+ Reason = update_percentage_not_retrieved,
+ ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
+ grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID})
+ end,
{send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(_, _, ID) ->
Error = {internal_error, method_not_found, ID},
diff --git a/src/grisp_connect_updater_progress.erl b/src/grisp_connect_updater_progress.erl
index 4ff0f65..9d16144 100644
--- a/src/grisp_connect_updater_progress.erl
+++ b/src/grisp_connect_updater_progress.erl
@@ -41,10 +41,16 @@ progress_init(#{client := PID} = _Opts) ->
progress_update(#state{last_notification = LastLog} = State, Stats) ->
case (erlang:system_time(millisecond) - LastLog) > 1000 of
- false -> {ok, State};
- true ->
+ false ->
+ {ok, State};
+ true ->
+ UpdatePercentage = progress_percent(Stats),
% Recheck log level when there is another way to check the progress update
- ?LOG_NOTICE("Update progress: ~b%", [progress_percent(Stats)]),
+ ?LOG_NOTICE("Update progress: ~b%", [UpdatePercentage]),
+ grisp_connect_client:request(
+ <<"notify">>,
+ <<"status_update">>,
+ #{percentage => UpdatePercentage}),
{ok, State#state{last_notification = erlang:system_time(millisecond)}}
end.
From 7f7a24c40dc4277431bb08ac519d918d14b079ef Mon Sep 17 00:00:00 2001
From: IciaC
Date: Fri, 4 Oct 2024 02:10:42 +0200
Subject: [PATCH 11/18] Fix PR comments
---
README.md | 4 ++--
docs/grisp_connect_api.md | 22 ++++------------------
src/grisp_connect_api.erl | 14 --------------
3 files changed, 6 insertions(+), 34 deletions(-)
diff --git a/README.md b/README.md
index e4495e2..461fd9c 100644
--- a/README.md
+++ b/README.md
@@ -5,8 +5,8 @@ GRiSP.io Client Library for GRiSP. This library enables secure communication
Mutual TLS (mTLS). To get started, add this application as a dependency in your
GRiSP2 project.
-⚠️ **Note:** If you plan to use the API calls related to `grisp_updater`, make
- sure to add `grisp_updater` as a dependency in your project as well.
+⚠️ **Note:** If you plan to use the API calls related to `grisp_updater_grisp2`,
+make sure to add `grisp_updater_grisp2` as a dependency in your project as well.
## Table of content
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
index 484d3ad..9c0e103 100644
--- a/docs/grisp_connect_api.md
+++ b/docs/grisp_connect_api.md
@@ -28,24 +28,10 @@ We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
**`error`**:
-| Error Content | When it Happens |
-| -------------------------------------------------------- | ------------------------------------------------- |
-| `{code: -10, message: "grisp_updater unavailable"}` | Grisp updater app is not running |
-| `{code: -11, message: "already updating "}` | An update is already happening |
-
-
-
-
-Post - Flash
-
-
-**`params`:**
-| key (required *) | value | description |
-| ------------------- | -------- | --------------------------------- |
-| `"led"` * | integer | Number that identifies the LED, to obtain more information about the options, you can visit [grisp_led:color/2](https://hexdocs.pm/grisp/) |
-| `"color"` | string | Color of the LED, by default: red. To obtain more information about the options, you can visit [grisp_led:color/2](https://hexdocs.pm/grisp/) |
-
-**`result`**: `"ok"`
+| Error Content | When it Happens |
+| ----------------------------------------------------| -------------------------------- |
+| `{code: -10, message: "grisp_updater unavailable"}` | Grisp updater app is not running |
+| `{code: -11, message: "already updating "}` | An update is already happening |
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index e6664ec..46011d4 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -57,11 +57,6 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
handle_rpc_messages(Batch,
[grisp_connect_jsonrpc:format_error(E)| Replies]).
-handle_request(?method_post, #{type := <<"flash">>} = Params, ID) ->
- Led = maps:get(led, Params, 1),
- Color = maps:get(color, Params, red),
- Reply = {result, flash(Led, Color), ID},
- {send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
try
URL = maps:get(url, Params),
@@ -108,15 +103,6 @@ handle_response(Response) ->
end,
{handle_response, ID, Reply}.
-flash(Led, Color) ->
- spawn(fun() ->
- ?LOG_NOTICE("Flash from Seawater!~n"),
- grisp_led:color(Led, Color),
- timer:sleep(100),
- grisp_led:off(Led)
- end),
- ok.
-
start_update(URL) ->
case is_running(grisp_updater) of
true -> grisp_updater:start(URL,
From c476dcd9323817258d46e533618797aa96be5168 Mon Sep 17 00:00:00 2001
From: Luca Succi
Date: Fri, 4 Oct 2024 12:31:23 +0000
Subject: [PATCH 12/18] Send progress notifications to the backend (#47)
* Do not loop on sync calls to WS while waiting for a connection
This allows to instantly detect a connection
* Support sending of jsonrpc notifications
* Use new notification to report download progress
---
src/grisp_connect_api.erl | 13 +++++++++++--
src/grisp_connect_client.erl | 25 +++++++++++++++----------
src/grisp_connect_updater_progress.erl | 17 +++++++++++------
src/grisp_connect_ws.erl | 9 +++------
4 files changed, 40 insertions(+), 24 deletions(-)
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 46011d4..8d56580 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -2,6 +2,7 @@
-module(grisp_connect_api).
-export([request/3]).
+-export([notify/3]).
-export([handle_msg/1]).
-include_lib("kernel/include/logger.hrl").
@@ -25,6 +26,14 @@ request(Method, Type, Params) ->
Encoded = grisp_connect_jsonrpc:encode(Rpc),
{ID, Encoded}.
+% #doc Assembles a jsonrpc notification
+-spec notify(Method :: atom() | binary(),
+ Type :: atom() | binary(),
+ Params :: map()) -> Encoded :: binary().
+notify(Method, Type, Params) ->
+ Rpc = {notification, Method, maps:put(type, Type, Params)},
+ grisp_connect_jsonrpc:encode(Rpc).
+
% @doc Indentifies if the message is a request or a reply to a previous request.
% In case it was a request, returns the reply to be sent to the peer.
% In case it was a response, returns the parsed ID and content to be handled by
@@ -81,9 +90,9 @@ handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
handle_request(?method_notify, #{type := <<"status_update">>} = Params, ID) ->
Percentage = maps:get(percentage, Params, undefined),
Reply = case Percentage of
- Per when is_integer(Per) ->
+ Per when is_integer(Per) ->
{result, #{percentage => Percentage}, ID};
- undefined ->
+ undefined ->
Reason = update_percentage_not_retrieved,
ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID})
diff --git a/src/grisp_connect_client.erl b/src/grisp_connect_client.erl
index 40deedb..9a258ef 100644
--- a/src/grisp_connect_client.erl
+++ b/src/grisp_connect_client.erl
@@ -10,8 +10,10 @@
-export([connect/0]).
-export([is_connected/0]).
-export([request/3]).
+-export([notify/3]).
% Internal API
+-export([connected/0]).
-export([disconnected/0]).
-export([handle_message/1]).
@@ -49,6 +51,12 @@ is_connected() ->
request(Method, Type, Params) ->
gen_statem:call(?MODULE, {?FUNCTION_NAME, Method, Type, Params}).
+notify(Method, Type, Params) ->
+ gen_statem:cast(?MODULE, {?FUNCTION_NAME, Method, Type, Params}).
+
+connected() ->
+ gen_statem:cast(?MODULE, ?FUNCTION_NAME).
+
disconnected() ->
gen_statem:cast(?MODULE, ?FUNCTION_NAME).
@@ -97,16 +105,9 @@ connecting(enter, _OldState, _Data) ->
{ok, Port} = application:get_env(grisp_connect, port),
?LOG_NOTICE(#{event => connecting, domain => Domain, port => Port}),
grisp_connect_ws:connect(Domain, Port),
- {keep_state_and_data, [{state_timeout, 0, wait}]};
-connecting(state_timeout, wait, Data) ->
- case grisp_connect_ws:is_connected() of
- true ->
- ?LOG_NOTICE(#{event => connected}),
- {next_state, connected, Data};
- false ->
- ?LOG_DEBUG(#{event => waiting_ws_connection}),
- {keep_state_and_data, [{state_timeout, ?STD_TIMEOUT, wait}]}
- end;
+ keep_state_and_data;
+connecting(cast, connected, Data) ->
+ {next_state, connected, Data};
connecting(cast, disconnected, _Data) ->
repeat_state_and_data;
?HANDLE_COMMON.
@@ -141,6 +142,10 @@ connected({call, From}, {request, Method, Type, Params},
{keep_state,
Data#data{requests = NewRequests},
[{{timeout, ID}, request_timeout(), request}]};
+connected(cast, {notify, Method, Type, Params}, _Data) ->
+ Payload = grisp_connect_api:notify(Method, Type, Params),
+ grisp_connect_ws:send(Payload),
+ keep_state_and_data;
?HANDLE_COMMON.
% Common event handling appended as last match case to each state_function
diff --git a/src/grisp_connect_updater_progress.erl b/src/grisp_connect_updater_progress.erl
index 9d16144..2bb5db1 100644
--- a/src/grisp_connect_updater_progress.erl
+++ b/src/grisp_connect_updater_progress.erl
@@ -41,16 +41,17 @@ progress_init(#{client := PID} = _Opts) ->
progress_update(#state{last_notification = LastLog} = State, Stats) ->
case (erlang:system_time(millisecond) - LastLog) > 1000 of
- false ->
+ false ->
{ok, State};
- true ->
+ true ->
UpdatePercentage = progress_percent(Stats),
% Recheck log level when there is another way to check the progress update
?LOG_NOTICE("Update progress: ~b%", [UpdatePercentage]),
- grisp_connect_client:request(
- <<"notify">>,
- <<"status_update">>,
- #{percentage => UpdatePercentage}),
+ grisp_connect_client:notify(
+ <<"update">>,
+ <<"software_update_event">>,
+ #{event => progress,
+ percentage => UpdatePercentage}),
{ok, State#state{last_notification = erlang:system_time(millisecond)}}
end.
@@ -65,6 +66,10 @@ progress_error(#state{}, Stats, Reason) ->
progress_done(#state{}, _Stats) ->
?LOG_NOTICE("Update done", []),
+ grisp_connect_client:notify(
+ <<"update">>,
+ <<"software_update_event">>,
+ #{event => done}),
ok.
diff --git a/src/grisp_connect_ws.erl b/src/grisp_connect_ws.erl
index 88a90b5..074ed46 100644
--- a/src/grisp_connect_ws.erl
+++ b/src/grisp_connect_ws.erl
@@ -4,7 +4,6 @@
-export([start_link/0]).
-export([connect/0]).
-export([connect/2]).
--export([is_connected/0]).
-export([send/1]).
-behaviour(gen_server).
@@ -37,9 +36,6 @@ connect() ->
connect(Server, Port) ->
gen_server:cast(?MODULE, {?FUNCTION_NAME, Server, Port}).
-is_connected() ->
- gen_server:call(?MODULE, ?FUNCTION_NAME).
-
send(Payload) ->
gen_server:cast(?MODULE, {?FUNCTION_NAME, Payload}).
@@ -47,8 +43,8 @@ send(Payload) ->
init([]) -> {ok, #state{}}.
-handle_call(is_connected, _, #state{ws_up = Up} = S) ->
- {reply, Up, S}.
+handle_call(Call, _, _) ->
+ error({unexpected_call, Call}).
handle_cast({connect, Server, Port}, #state{gun_pid = undefined} = S) ->
GunOpts = #{
@@ -87,6 +83,7 @@ handle_info({gun_up, Pid, http}, #state{gun_pid = GunPid} = S) ->
handle_info({gun_upgrade, Pid, Stream, [<<"websocket">>], _},
#state{gun_pid = Pid, ws_stream = Stream} = S) ->
?LOG_INFO(#{event => ws_upgrade}),
+ grisp_connect_client:connected(),
{noreply, S#state{ws_up = true, ping_timer = start_ping_timer()}};
handle_info({gun_response, Pid, Stream, _, Status, _Headers},
#state{gun_pid = Pid, ws_stream = Stream} = S) ->
From 6caf750b3ea83f74bc82eb4cb9073bbbad8b4609 Mon Sep 17 00:00:00 2001
From: Luca Succi
Date: Fri, 4 Oct 2024 15:22:23 +0200
Subject: [PATCH 13/18] Fix test suites timings to adapt to faster reconnection
detections
---
test/grisp_connect_test_client.erl | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/test/grisp_connect_test_client.erl b/test/grisp_connect_test_client.erl
index 587e26d..b76fefe 100644
--- a/test/grisp_connect_test_client.erl
+++ b/test/grisp_connect_test_client.erl
@@ -17,7 +17,7 @@ cert_dir() -> filename:join(code:lib_dir(grisp_connect, test), "certs").
serial_number() -> <<"0000">>.
wait_connection() ->
- wait_connection(20).
+ wait_connection(2000).
wait_connection(0) ->
ct:pal("grisp_connect_ws state:~n~p~n", [sys:get_state(grisp_connect_ws)]),
@@ -26,12 +26,12 @@ wait_connection(N) ->
case grisp_connect:is_connected() of
true -> ok;
false ->
- ct:sleep(100),
+ ct:sleep(1),
wait_connection(N - 1)
end.
wait_disconnection() ->
- wait_disconnection(20).
+ wait_disconnection(2000).
wait_disconnection(0) ->
ct:pal("grisp_connect_ws state:~n~p~n", [sys:get_state(grisp_connect_ws)]),
@@ -39,7 +39,7 @@ wait_disconnection(0) ->
wait_disconnection(N) ->
case grisp_connect:is_connected() of
true ->
- ct:sleep(100),
+ ct:sleep(1),
wait_disconnection(N - 1);
false -> ok
end.
From b0c9435583e0972d4268446b5983b173ff1c23be Mon Sep 17 00:00:00 2001
From: IciaC
Date: Mon, 7 Oct 2024 15:32:20 +0200
Subject: [PATCH 14/18] Remove API's notify request and add warning/error
software_update_event notifications.
---
docs/grisp_connect_api.md | 33 ++++++++++++++++----------
src/grisp_connect_api.erl | 12 ----------
src/grisp_connect_updater_progress.erl | 18 +++++++++++---
3 files changed, 36 insertions(+), 27 deletions(-)
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
index 9c0e103..3db5a7d 100644
--- a/docs/grisp_connect_api.md
+++ b/docs/grisp_connect_api.md
@@ -4,13 +4,16 @@
- [UI Websocket API](#ui-websocket-api)
- [Backend API](#backend-api)
- [Requests](#requests)
+ - [Notifications](#notifications)
- [Error Codes](#error-codes)
- [Default error codes](#default-error-codes)
- [Custom error codes](#custom-error-codes)
- - [Notifications](#notifications)
We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
+⚠️ **Note:** If you plan to use the API calls related to `grisp_updater`, make
+ sure to add `grisp_updater_grisp2` as a dependency in your project as well.
+
## Backend API
### Requests
@@ -36,6 +39,23 @@ We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
+### Notifications
+
+update
{"type":"software_update_event"}
- notify the current progess of grisp_updater
+
+
+**`params`:**
+| key | value | type | description |
+|---------------|---------------------------------------------|----------|--------------------------------------|
+|`"type"` | `"software_update_event"` | required | |
+|`"event_type"` | `"progress"` `"warning"` `"error"` `"done"` | required | |
+|`"message"` | integer | optional | expected in case of warning |
+|`"reason"` | integer | optional | expected in case of warning or error |
+|`"percentage"` | integer | optional | expected in case of progress or error|
+
+
+
+
## Error Codes
### Default error codes
@@ -58,14 +78,3 @@ Additionally to the default jsonrpc error codes the following codes will be retu
| -2 | `"token expired"` | token is expired |
| -3 | `"device already linked"` | device needs to be unlinked first via UI |
| -4 | `"invalid token"` | token is e.g. not orderly encoded |
-
-## Notifications
-
-Notify - Status Update
-
-**`result`**: `JSON Object`
-
-| key | value | description |
-| -------------- | ----------------- | --------------------------------- |
-| `"type"` | `"status_update"` | Type of notification |
-| `"percentage"` | integer | Progress percentage of the update |
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 8d56580..9d820eb 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -12,7 +12,6 @@
-define(method_post, <<"post">>).
-define(method_patch, <<"patch">>).
-define(method_delete, <<"delete">>).
--define(method_notify, <<"notify">>).
%--- API -----------------------------------------------------------------------
@@ -87,17 +86,6 @@ handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
grisp_connect_jsonrpc:format_error(
{internal_error, invalid_params, ID})}
end;
-handle_request(?method_notify, #{type := <<"status_update">>} = Params, ID) ->
- Percentage = maps:get(percentage, Params, undefined),
- Reply = case Percentage of
- Per when is_integer(Per) ->
- {result, #{percentage => Percentage}, ID};
- undefined ->
- Reason = update_percentage_not_retrieved,
- ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
- grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID})
- end,
- {send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(_, _, ID) ->
Error = {internal_error, method_not_found, ID},
FormattedError = grisp_connect_jsonrpc:format_error(Error),
diff --git a/src/grisp_connect_updater_progress.erl b/src/grisp_connect_updater_progress.erl
index 2bb5db1..ca7b564 100644
--- a/src/grisp_connect_updater_progress.erl
+++ b/src/grisp_connect_updater_progress.erl
@@ -50,18 +50,30 @@ progress_update(#state{last_notification = LastLog} = State, Stats) ->
grisp_connect_client:notify(
<<"update">>,
<<"software_update_event">>,
- #{event => progress,
+ #{event => progress,
percentage => UpdatePercentage}),
{ok, State#state{last_notification = erlang:system_time(millisecond)}}
end.
progress_warning(State, Msg, Reason) ->
?LOG_WARNING("Update warning; ~s: ~p", [Msg, Reason]),
+ grisp_connect_client:notify(
+ <<"update">>,
+ <<"software_update_event">>,
+ #{event => warning,
+ reason => Reason,
+ message => Msg}),
{ok, State}.
progress_error(#state{}, Stats, Reason) ->
- ?LOG_ERROR("Update failed after ~b% : ~p",
- [progress_percent(Stats), Reason]),
+ UpdatePercentage = progress_percent(Stats),
+ ?LOG_ERROR("Update failed after ~b% : ~p", [UpdatePercentage, Reason]),
+ grisp_connect_client:notify(
+ <<"update">>,
+ <<"software_update_event">>,
+ #{event => error,
+ reason => Reason,
+ percentage => UpdatePercentage}),
ok.
progress_done(#state{}, _Stats) ->
From 41ed68481b8dd72e14a22b9f9b77697c593a2884 Mon Sep 17 00:00:00 2001
From: Luca Succi
Date: Mon, 7 Oct 2024 16:24:59 +0000
Subject: [PATCH 15/18] Add validate method (#48)
* Handle boot_system_not_validated
* Add validate method
---
docs/grisp_connect_api.md | 31 +++++++++++++++++++++++++++++--
src/grisp_connect_api.erl | 13 +++++++++++++
2 files changed, 42 insertions(+), 2 deletions(-)
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
index 3db5a7d..05c84fa 100644
--- a/docs/grisp_connect_api.md
+++ b/docs/grisp_connect_api.md
@@ -21,6 +21,8 @@ We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
Post - Start an update
+Triggers grisp_updater to install an update from the given URL.
+
**`params`:**
| key (required *) | value | description |
| ----------------- | -------- | -------------------------- |
@@ -33,8 +35,33 @@ We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
| Error Content | When it Happens |
| ----------------------------------------------------| -------------------------------- |
-| `{code: -10, message: "grisp_updater unavailable"}` | Grisp updater app is not running |
-| `{code: -11, message: "already updating "}` | An update is already happening |
+| `{code: -10, message: "grisp_updater_unavailable"}` | Grisp updater app is not running |
+| `{code: -11, message: "already_updating"}` | An update is already happening |
+| `{code: -12, message: "boot_system_not_validated"}` | The board rebooted after an update and needs validation |
+
+
+
+
+Post - Validate an update
+
+
+Validates the current booted partition. This can only be done after an update was installed and a reboot occurred.
+This request sets the current partition as permanent in the bootloader if it is not.
+If the new partition is not validated, from the next reboot, the bootloader will load the previous one.
+This should only be called if the new software is functioning as expected.
+
+**`params`:**
+| key (required *) | value | description |
+| ----------------- | -------- | -------------------------- |
+| `"type"` * | string | `"validate"` |
+
+**`result`**: `"ok"`
+
+**`error`**:
+
+| Error Content | When it Happens |
+| ----------------------------------------------------| -------------------------------- |
+| `{code: -13, message: "validate_from_unbooted", data: 0}` | The current partition N cannot be validated |
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 9d820eb..9b9520c 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -73,6 +73,8 @@ handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
{error, -10, grisp_updater_unavailable, undefined, ID};
{error, already_updating} ->
{error, -11, already_updating, undefined, ID};
+ {error, boot_system_not_validated} ->
+ {error, -12, boot_system_not_validated, undefined, ID};
{error, Reason} ->
ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID});
@@ -86,6 +88,17 @@ handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
grisp_connect_jsonrpc:format_error(
{internal_error, invalid_params, ID})}
end;
+handle_request(?method_post, #{type := <<"validate">>}, ID) ->
+ Reply = case grisp_updater:validate() of
+ {error, {validate_from_unbooted, PartitionIndex}} ->
+ {error, -13, validate_from_unbooted, PartitionIndex, ID};
+ {error, Reason} ->
+ ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
+ grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID});
+ ok ->
+ {result, ok, ID}
+ end,
+ {send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(_, _, ID) ->
Error = {internal_error, method_not_found, ID},
FormattedError = grisp_connect_jsonrpc:format_error(Error),
From 432af67cc29b5f94265dd97df1ff4adbfb7bcc59 Mon Sep 17 00:00:00 2001
From: IciaC
Date: Tue, 8 Oct 2024 14:49:25 +0200
Subject: [PATCH 16/18] Fix callback module errors
---
docs/grisp_connect_api.md | 4 ++--
src/grisp_connect_updater_progress.erl | 7 ++++---
2 files changed, 6 insertions(+), 5 deletions(-)
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
index 05c84fa..695f4c2 100644
--- a/docs/grisp_connect_api.md
+++ b/docs/grisp_connect_api.md
@@ -36,7 +36,7 @@ Triggers grisp_updater to install an update from the given URL.
| Error Content | When it Happens |
| ----------------------------------------------------| -------------------------------- |
| `{code: -10, message: "grisp_updater_unavailable"}` | Grisp updater app is not running |
-| `{code: -11, message: "already_updating"}` | An update is already happening |
+| `{code: -11, message: "already_updating"}` | An update is already happening |
| `{code: -12, message: "boot_system_not_validated"}` | The board rebooted after an update and needs validation |
@@ -76,7 +76,7 @@ This should only be called if the new software is functioning as expected.
|---------------|---------------------------------------------|----------|--------------------------------------|
|`"type"` | `"software_update_event"` | required | |
|`"event_type"` | `"progress"` `"warning"` `"error"` `"done"` | required | |
-|`"message"` | integer | optional | expected in case of warning |
+|`"message"` | integer | optional | expected in case of warning or error |
|`"reason"` | integer | optional | expected in case of warning or error |
|`"percentage"` | integer | optional | expected in case of progress or error|
diff --git a/src/grisp_connect_updater_progress.erl b/src/grisp_connect_updater_progress.erl
index ca7b564..6c68274 100644
--- a/src/grisp_connect_updater_progress.erl
+++ b/src/grisp_connect_updater_progress.erl
@@ -16,7 +16,7 @@
-export([progress_init/1]).
-export([progress_update/2]).
-export([progress_warning/3]).
--export([progress_error/3]).
+-export([progress_error/4]).
-export([progress_done/2]).
@@ -55,7 +55,7 @@ progress_update(#state{last_notification = LastLog} = State, Stats) ->
{ok, State#state{last_notification = erlang:system_time(millisecond)}}
end.
-progress_warning(State, Msg, Reason) ->
+progress_warning(State, Reason, Msg) ->
?LOG_WARNING("Update warning; ~s: ~p", [Msg, Reason]),
grisp_connect_client:notify(
<<"update">>,
@@ -65,7 +65,7 @@ progress_warning(State, Msg, Reason) ->
message => Msg}),
{ok, State}.
-progress_error(#state{}, Stats, Reason) ->
+progress_error(#state{}, Stats, Reason, Msg) ->
UpdatePercentage = progress_percent(Stats),
?LOG_ERROR("Update failed after ~b% : ~p", [UpdatePercentage, Reason]),
grisp_connect_client:notify(
@@ -73,6 +73,7 @@ progress_error(#state{}, Stats, Reason) ->
<<"software_update_event">>,
#{event => error,
reason => Reason,
+ message => Msg,
percentage => UpdatePercentage}),
ok.
From f1edf28e43f15d5668cf20c2f1c2c1d5a5962641 Mon Sep 17 00:00:00 2001
From: IciaC
Date: Wed, 9 Oct 2024 17:46:38 +0200
Subject: [PATCH 17/18] get partition_state endpoint
---
docs/grisp_connect_api.md | 38 ++++++++++++++++++++
src/grisp_connect_api.erl | 75 ++++++++++++++++++++++++++++++++++++++-
2 files changed, 112 insertions(+), 1 deletion(-)
diff --git a/docs/grisp_connect_api.md b/docs/grisp_connect_api.md
index 695f4c2..2ab9506 100644
--- a/docs/grisp_connect_api.md
+++ b/docs/grisp_connect_api.md
@@ -18,6 +18,44 @@ We use [jsonrpc](https://www.jsonrpc.org) 2.0 between frontend and backend.
### Requests
+
+
+Get - partition_state
+
+
+Retrieves the current state of the system’s partition, indicating whether the
+system requires a reboot, needs validation, or is running an old partition
+with no updates pending. This can be used to check if the system is running a
+ valid system, or has an update pending.
+
+**`params`:**
+| key (required *) | value | description |
+| ----------------- | -------- | ------------------- |
+| `"type"` * | string | `"partition_state"` |
+
+**`result`**: JSON Object
+
+| key | value | type | description |
+|-----------------|-----------|----------|----------------------------------------------------|
+| state | string | required | `"old"`, `"old_no_update"`, `"new"`, `"unknown"` |
+| message | string | required | Message describing the current state of the system |
+| action_required | boolean | required | Indicates whether any action is required (e.g., reboot, validation). |
+
+Meaning of the state:
+
+| key | description |
+|-------------------|--------------------------------------------------------------------------------------------|
+| `"new"` | The system has booted into a new partition. Validation is required to finalize the update. |
+| `"old"` | Current partition is old. A reboot is required to load the new partition. |
+| `"old_no_update"` | There is no update pending. The system is running the old partition. |
+| `"unknown"` | The current partition state does not match any of the previous described states. |
+
+**`error`**:
+
+| Error Content | When it Happens |
+| ----------------------------------------------------| -------------------------------------- |
+| `{code: -10, message: "grisp_updater_unavailable"}` | Grisp updater app is not running |
+
Post - Start an update
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 9b9520c..7442aa4 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -53,7 +53,8 @@ handle_jsonrpc({single, Rpc}) ->
handle_rpc_messages([], Replies) -> lists:reverse(Replies);
handle_rpc_messages([{request, M, Params, ID} | Batch], Replies)
- when M == ?method_post ->
+ when M == ?method_post;
+ M == ?method_get ->
handle_rpc_messages(Batch, [handle_request(M, Params, ID) | Replies]);
handle_rpc_messages([{result, _, _} = Res| Batch], Replies) ->
handle_rpc_messages(Batch, [handle_response(Res)| Replies]);
@@ -65,6 +66,18 @@ handle_rpc_messages([{internal_error, _, _} = E | Batch], Replies) ->
handle_rpc_messages(Batch,
[grisp_connect_jsonrpc:format_error(E)| Replies]).
+handle_request(?method_get, #{type := <<"partition_state">>} = _Params, ID) ->
+ Info = get_partition_info(),
+ Reply = case Info of
+ #{state := _State,
+ message := _Msg,
+ action_required := _ActionRequired} = Response ->
+ {result, Response, ID};
+ {error, Reason} ->
+ ReasonBinary = iolist_to_binary(io_lib:format("~p", [Reason])),
+ grisp_connect_jsonrpc:format_error({internal_error, ReasonBinary, ID})
+ end,
+ {send_response, grisp_connect_jsonrpc:encode(Reply)};
handle_request(?method_post, #{type := <<"start_update">>} = Params, ID) ->
try
URL = maps:get(url, Params),
@@ -121,6 +134,64 @@ start_update(URL) ->
false -> {error, grisp_updater_unavailable}
end.
+get_partition_info() ->
+ case is_running(grisp_updater) of
+ true ->
+ Info = grisp_updater:info(),
+ #{boot := Boot, valid := Valid, next := Next} = Info,
+ ActionRequired = maps:get(action_required, Info, false),
+ case evaluate_partition_state(Boot, Valid, Next) of
+ new_boot ->
+ #{state => <<"new">>,
+ message => <<"New partition booted, validation required">>,
+ action_required => ActionRequired};
+ update_pending ->
+ #{state => <<"old">>,
+ message => <<"Reboot required to load new partition">>,
+ action_required => ActionRequired};
+ no_update_pending ->
+ #{state => <<"old_no_update">>,
+ message => <<"No update pending, running old partition">>,
+ action_required => ActionRequired};
+ _ ->
+ #{state => <<"unknown">>,
+ message => <<"Unknown partition state">>,
+ action_required => ActionRequired}
+ end;
+ false -> {error, grisp_updater_unavailable}
+ end.
+
+evaluate_partition_state(BootPartition, ValidPartition, NextPartition) ->
+ case {BootPartition, ValidPartition, NextPartition} of
+ % Case 1: Booting from removable media, but system has a pending update
+ {#{type := removable},
+ #{type := system, id := ValidId},
+ #{type := system, id := NextId}}
+ when ValidId =/= NextId -> update_pending;
+ % Case 2: Booted from system partition, but a different system partition is pending update
+ {#{type := system, id := BootId},
+ #{type := system, id := ValidId},
+ #{type := system, id := NextId}}
+ when BootId == ValidId, ValidId =/= NextId -> update_pending;
+ % Case 3: Booted from a new partition, validation required
+ {#{type := system, id := BootId},
+ #{type := system, id := ValidId},
+ _}
+ when BootId =/= ValidId -> new_boot;
+ % Case 4: Booted from removable media, no update pending
+ {#{type := removable},
+ #{type := system, id := ValidId},
+ #{type := system, id := NextId}}
+ when ValidId == NextId -> no_update_pending;
+ % Case 5: Booted from system partition, no update pending
+ {#{type := system, id := BootId},
+ _,
+ #{type := system, id := NextId}}
+ when NextId == BootId -> no_update_pending;
+ % Default case: Unknown partition state
+ _ -> unknown_state
+ end.
+
is_running(AppName) ->
Apps = application:which_applications(),
case [App || {App, _Desc, _VSN} <- Apps, App =:= AppName] of
@@ -134,6 +205,8 @@ error_atom(-3) -> device_already_linked;
error_atom(-4) -> invalid_token;
error_atom(-10) -> grisp_updater_unavailable;
error_atom(-11) -> already_updating;
+error_atom(-12) -> boot_system_not_validated;
+error_atom(-13) -> validate_from_unbooted;
error_atom(_) -> jsonrpc_error.
id() ->
From 279942a5505e25491086f4c81c22b06f54c1998c Mon Sep 17 00:00:00 2001
From: IciaC
Date: Wed, 9 Oct 2024 19:04:19 +0200
Subject: [PATCH 18/18] Delete error_atom not coming from grisp_manager
---
src/grisp_connect_api.erl | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/grisp_connect_api.erl b/src/grisp_connect_api.erl
index 7442aa4..ecb5f4c 100644
--- a/src/grisp_connect_api.erl
+++ b/src/grisp_connect_api.erl
@@ -203,10 +203,6 @@ error_atom(-1) -> device_not_linked;
error_atom(-2) -> token_expired;
error_atom(-3) -> device_already_linked;
error_atom(-4) -> invalid_token;
-error_atom(-10) -> grisp_updater_unavailable;
-error_atom(-11) -> already_updating;
-error_atom(-12) -> boot_system_not_validated;
-error_atom(-13) -> validate_from_unbooted;
error_atom(_) -> jsonrpc_error.
id() ->