Packages
macula
3.10.2
7.0.0
6.0.0
5.2.2
5.2.1
5.2.0
5.1.0
5.0.0
4.8.0
4.7.1
4.7.0
4.6.0
4.5.0
4.4.10
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.1
4.3.0
4.2.9
4.2.8
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.1
4.1.0
4.0.0
3.16.0
3.15.3
3.15.2
3.15.1
3.14.0
3.13.0
3.12.1
3.12.0
3.11.1
3.11.0
3.10.3
3.10.2
3.10.1
3.9.0
3.8.0
3.7.0
3.5.0
3.4.0
3.3.0
3.2.0
3.1.0
3.0.0
2.1.1
2.1.0
2.0.0
1.5.2
1.5.1
1.4.30
1.4.29
1.4.28
1.4.27
1.4.26
1.4.25
1.4.24
1.4.23
1.4.22
1.4.21
1.4.20
1.4.19
1.4.18
1.4.17
1.4.16
1.4.15
1.4.14
1.4.13
1.4.11
1.4.10
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.1
1.3.0
1.2.0
1.1.0
1.0.10
1.0.9
1.0.8
1.0.7
1.0.6
1.0.5
1.0.4
1.0.3
1.0.2
1.0.1
1.0.0
0.48.6
0.48.5
0.48.4
0.48.3
0.48.2
0.48.1
0.48.0
0.47.1
0.47.0
0.46.3
0.46.1
0.46.0
0.45.3
0.45.2
0.45.1
0.45.0
0.44.2
0.44.1
0.44.0
0.43.3
0.43.2
0.43.1
0.43.0
0.42.9
0.42.8
0.42.7
0.42.6
0.42.5
0.42.4
0.42.3
0.42.2
0.42.1
0.42.0
0.41.1
0.41.0
0.40.1
0.40.0
0.39.9
0.39.8
0.39.7
0.39.6
0.39.5
0.39.4
0.39.3
0.39.2
0.39.1
0.39.0
0.38.8
0.38.7
0.38.6
0.38.5
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.7
0.37.6
0.37.5
0.37.4
0.37.3
0.37.2
0.37.1
0.37.0
0.36.6
0.36.5
0.36.4
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.1
0.34.0
0.33.1
0.33.0
0.32.5
0.32.4
0.32.3
0.32.2
0.32.1
0.32.0
0.31.9
0.31.8
0.31.7
0.31.6
0.31.5
0.31.4
0.31.3
0.31.2
0.31.1
0.31.0
0.30.10
0.30.9
0.30.8
0.30.7
0.30.6
0.30.5
0.30.4
0.30.3
0.30.2
0.30.1
0.30.0
0.29.0
0.28.3
0.28.2
0.28.1
0.28.0
0.27.1
0.27.0
0.26.1
0.26.0
0.25.6
0.25.5
0.25.4
0.25.3
0.25.2
0.25.1
0.25.0
0.24.6
0.24.5
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.3
0.23.2
0.23.1
0.23.0
0.22.12
0.22.11
0.22.10
0.22.9
0.22.8
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.7
0.21.6
0.21.5
0.21.4
0.21.2
0.21.1
0.21.0
0.20.25
0.20.24
0.20.23
0.20.22
0.20.21
0.20.20
0.20.19
0.20.18
0.20.17
0.20.16
0.20.15
0.20.14
0.20.13
0.20.12
0.20.11
0.20.10
0.20.9
0.20.8
0.20.7
0.20.6
0.20.5
0.20.3
0.20.2
0.20.1
0.20.0
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.12.6
0.12.5
0.12.3
0.11.3
0.10.2
0.10.1
0.10.0
0.9.2
0.9.1
0.9.0
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.10
0.8.9
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.30
0.7.29
0.7.28
0.7.27
0.7.26
0.7.25
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.4
0.7.3
0.7.2
0.7.1
0.7.0
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.4
0.3.3
0.3.2
0.3.1
Macula HTTP/3 Mesh SDK — connect, subscribe, publish, call, advertise
Current section
Files
Jump to
Current section
Files
src/macula_console.erl
%%%-------------------------------------------------------------------
%%% @doc Macula Console Formatter - Beautiful colored terminal output.
%%%
%%% Provides ANSI-colored output for ping-pong demo and general logging.
%%% Makes demo output visually appealing and easy to read.
%%%
%%% Example output:
%%% <pre>
%%% --> fc01 -> rc05 [full_cone -> restricted]
%%% <-- fc01 <- rc05 42ms [restricted]
%%% [!!] fc01 -> sy07 TIMEOUT [symmetric]
%%% </pre>
%%%
%%% @end
%%%-------------------------------------------------------------------
-module(macula_console).
-export([
%% Ping-pong demo output
ping/4,
pong/4,
timeout/3,
%% PubSub demo output
pubsub_send/3,
pubsub_recv/5,
%% General output
error/2,
info/2,
success/2,
warning/2,
%% Stats display
stats/2,
%% Mesh events
node_connected/2,
node_disconnected/2,
%% Banner
banner/1
]).
%%%===================================================================
%%% ANSI Color Codes
%%%===================================================================
-define(RESET, "\e[0m").
-define(BOLD, "\e[1m").
-define(DIM, "\e[2m").
-define(ITALIC, "\e[3m").
-define(UNDERLINE, "\e[4m").
%% Colors
-define(BLACK, "\e[30m").
-define(RED, "\e[31m").
-define(GREEN, "\e[32m").
-define(YELLOW, "\e[33m").
-define(BLUE, "\e[34m").
-define(MAGENTA, "\e[35m").
-define(CYAN, "\e[36m").
-define(WHITE, "\e[37m").
%% Bright colors
-define(BRIGHT_RED, "\e[91m").
-define(BRIGHT_GREEN, "\e[92m").
-define(BRIGHT_YELLOW, "\e[93m").
-define(BRIGHT_BLUE, "\e[94m").
-define(BRIGHT_MAGENTA, "\e[95m").
-define(BRIGHT_CYAN, "\e[96m").
%% Background colors
-define(BG_RED, "\e[41m").
-define(BG_GREEN, "\e[42m").
-define(BG_YELLOW, "\e[43m").
%%%===================================================================
%%% Icons
%%%===================================================================
-define(ARROW_RIGHT, "-->").
-define(ARROW_LEFT, "<--").
-define(CHECK, "[ok]").
-define(CROSS, "[!!]").
-define(INFO, "[i]").
-define(WARN, "[!]").
-define(STAR, "[*]").
%%%===================================================================
%%% Ping-Pong Demo Output
%%%===================================================================
%% @doc Output a PING message showing source to target with NAT types
-spec ping(binary() | string(), binary() | string(),
binary() | string() | atom(), binary() | string() | atom()) -> ok.
ping(FromNode, ToNode, FromNat, ToNat) ->
io:format("~s~s~s ~s~s~s -> ~s~s~s ~s[~s -> ~s]~s~n",
[?CYAN, ?ARROW_RIGHT, ?RESET,
?BOLD, format_node(FromNode), ?RESET,
?BOLD, format_node(ToNode), ?RESET,
?DIM, format_nat(FromNat), format_nat(ToNat), ?RESET]).
%% @doc Output a PONG message with RTT
-spec pong(binary() | string(), binary() | string(),
integer(), binary() | string() | atom()) -> ok.
pong(FromNode, ToNode, RTT, NatType) ->
RttColor = rtt_color(RTT),
io:format("~s~s~s ~s~s~s <- ~s~s~s ~s~pms~s ~s[~s]~s~n",
[?GREEN, ?ARROW_LEFT, ?RESET,
?BOLD, format_node(FromNode), ?RESET,
?BOLD, format_node(ToNode), ?RESET,
RttColor, RTT, ?RESET,
?DIM, format_nat(NatType), ?RESET]).
%% @doc Output a TIMEOUT message for a failed ping
-spec timeout(binary() | string(), binary() | string(),
binary() | string() | atom()) -> ok.
timeout(FromNode, ToNode, NatInfo) ->
io:format("~s~s~s ~s~s~s -> ~s~s~s ~sTIMEOUT~s ~s[~s]~s~n",
[?RED, ?CROSS, ?RESET,
?BOLD, format_node(FromNode), ?RESET,
?BOLD, format_node(ToNode), ?RESET,
?RED, ?RESET,
?DIM, format_nat(NatInfo), ?RESET]).
%%%===================================================================
%%% PubSub Demo Output
%%%===================================================================
%% @doc Output a PubSub broadcast message with sequence number
-spec pubsub_send(binary() | string(), integer(), binary() | string() | atom()) -> ok.
pubsub_send(FromNode, SeqNum, NatType) ->
io:format("~s[>>]~s ~s~s~s ~s#~p~s ~s[~s]~s~n",
[?MAGENTA, ?RESET,
?BOLD, format_node(FromNode), ?RESET,
?MAGENTA, SeqNum, ?RESET,
?DIM, format_nat(NatType), ?RESET]).
%% @doc Output a PubSub receive message with delivery rate
-spec pubsub_recv(binary() | string(), binary() | string(), integer(),
binary() | string() | atom(), float()) -> ok.
pubsub_recv(ToNode, FromNode, SeqNum, SenderNat, DeliveryRate) ->
RateColor = delivery_color(DeliveryRate),
io:format("~s[<<]~s ~s~s~s <- ~s~s~s ~s#~p~s ~s[~s]~s ~s~.1f%%~s~n",
[?BRIGHT_BLUE, ?RESET,
?BOLD, format_node(ToNode), ?RESET,
?BOLD, format_node(FromNode), ?RESET,
?BRIGHT_BLUE, SeqNum, ?RESET,
?DIM, format_nat(SenderNat), ?RESET,
RateColor, DeliveryRate, ?RESET]).
%%%===================================================================
%%% General Output
%%%===================================================================
%% @doc Output an error message: [!!] Error message
-spec error(binary() | string(), binary() | string()) -> ok.
error(_Prefix, Msg) ->
io:format("~s~s~s ~s~s~s~n",
[?RED, ?CROSS, ?RESET, ?RED, format_msg(Msg), ?RESET]).
%% @doc Output an info message: [i] Info message
-spec info(binary() | string(), binary() | string()) -> ok.
info(_Prefix, Msg) ->
io:format("~s~s~s ~s~n",
[?CYAN, ?INFO, ?RESET, format_msg(Msg)]).
%% @doc Output a success message: [ok] Success message
-spec success(binary() | string(), binary() | string()) -> ok.
success(_Prefix, Msg) ->
io:format("~s~s~s ~s~s~s~n",
[?GREEN, ?CHECK, ?RESET, ?GREEN, format_msg(Msg), ?RESET]).
%% @doc Output a warning message: [!] Warning message
-spec warning(binary() | string(), binary() | string()) -> ok.
warning(_Prefix, Msg) ->
io:format("~s~s~s ~s~s~s~n",
[?YELLOW, ?WARN, ?RESET, ?YELLOW, format_msg(Msg), ?RESET]).
%%%===================================================================
%%% Stats Display
%%%===================================================================
%% @doc Output statistics for a node
%% StatsMap should contain: pings_sent, pongs_received, timeouts, avg_rtt_ms
-spec stats(binary() | string(), map()) -> ok.
stats(NodeId, StatsMap) ->
PingsSent = maps:get(pings_sent, StatsMap, 0),
PongsReceived = maps:get(pongs_received, StatsMap, 0),
Timeouts = maps:get(timeouts, StatsMap, 0),
AvgRtt = maps:get(avg_rtt_ms, StatsMap, 0),
SuccessRate = calc_success_rate(PingsSent, PongsReceived),
io:format("~n~s=== ~s Statistics ===~s~n", [?BOLD, format_node(NodeId), ?RESET]),
io:format(" ~sSent:~s ~p~n", [?DIM, ?RESET, PingsSent]),
io:format(" ~sReceived:~s ~s~p~s~n", [?DIM, ?RESET, ?GREEN, PongsReceived, ?RESET]),
io:format(" ~sTimeouts:~s ~s~p~s~n", [?DIM, ?RESET, timeout_color(Timeouts), Timeouts, ?RESET]),
io:format(" ~sAvg RTT:~s ~s~pms~s~n", [?DIM, ?RESET, rtt_color(AvgRtt), AvgRtt, ?RESET]),
io:format(" ~sSuccess:~s ~s~.1f%~s~n", [?DIM, ?RESET, success_color(SuccessRate), SuccessRate, ?RESET]).
%%%===================================================================
%%% Mesh Events
%%%===================================================================
%% @doc Output a node connected event: [*] Node fc01 connected (full_cone)
-spec node_connected(binary() | string(), binary() | string() | atom()) -> ok.
node_connected(NodeId, NatType) ->
io:format("~s~s~s ~sNode ~s~s~s connected~s ~s(~s)~s~n",
[?GREEN, ?STAR, ?RESET,
?GREEN, ?BOLD, format_node(NodeId), ?RESET,
?RESET, ?DIM, format_nat(NatType), ?RESET]).
%% @doc Output a node disconnected event: [*] Node fc01 disconnected
-spec node_disconnected(binary() | string(), binary() | string()) -> ok.
node_disconnected(NodeId, Reason) ->
io:format("~s~s~s ~sNode ~s~s~s disconnected~s ~s(~s)~s~n",
[?YELLOW, ?STAR, ?RESET,
?YELLOW, ?BOLD, format_node(NodeId), ?RESET,
?RESET, ?DIM, format_msg(Reason), ?RESET]).
%%%===================================================================
%%% Banner
%%%===================================================================
%% @doc Display a startup banner for the node
-spec banner(map()) -> ok.
banner(Config) ->
NodeId = maps:get(node_id, Config, <<"unknown">>),
Realm = maps:get(realm, Config, <<"default">>),
Mode = maps:get(mode, Config, <<"peer">>),
io:format("~n"),
io:format("~s╔══════════════════════════════════════════════╗~s~n", [?CYAN, ?RESET]),
io:format("~s║~s ~s~sMAC~s~sU~s~sLA~s ~sMesh Network~s ~s║~s~n",
[?CYAN, ?RESET, ?BOLD, ?RED, ?RESET, ?BOLD, ?RESET, ?BOLD, ?RED, ?RESET, ?DIM, ?CYAN, ?RESET]),
io:format("~s╠══════════════════════════════════════════════╣~s~n", [?CYAN, ?RESET]),
io:format("~s║~s Node: ~s~s~s ~s║~s~n",
[?CYAN, ?RESET, ?BOLD, format_padded(NodeId, 20), ?RESET, ?CYAN, ?RESET]),
io:format("~s║~s Realm: ~s~s ~s║~s~n",
[?CYAN, ?RESET, format_padded(Realm, 20), ?RESET, ?CYAN, ?RESET]),
io:format("~s║~s Mode: ~s~s ~s║~s~n",
[?CYAN, ?RESET, format_padded(Mode, 20), ?RESET, ?CYAN, ?RESET]),
io:format("~s╚══════════════════════════════════════════════╝~s~n", [?CYAN, ?RESET]),
io:format("~n").
%%%===================================================================
%%% Internal Functions
%%%===================================================================
%% @private Format a node ID for display
format_node(Node) when is_binary(Node) -> binary_to_list(Node);
format_node(Node) when is_atom(Node) -> atom_to_list(Node);
format_node(Node) when is_list(Node) -> Node.
%% @private Format a NAT type for display
format_nat(Nat) when is_atom(Nat) -> atom_to_list(Nat);
format_nat(Nat) when is_binary(Nat) -> binary_to_list(Nat);
format_nat(Nat) when is_list(Nat) -> Nat.
%% @private Format a message for display
format_msg(Msg) when is_binary(Msg) -> binary_to_list(Msg);
format_msg(Msg) when is_atom(Msg) -> atom_to_list(Msg);
format_msg(Msg) when is_list(Msg) -> Msg.
%% @private Format with padding
format_padded(Value, Width) ->
Str = format_msg(Value),
Len = length(Str),
case Len >= Width of
true -> string:slice(Str, 0, Width);
false -> Str ++ lists:duplicate(Width - Len, $ )
end.
%% @private Get color based on RTT
rtt_color(RTT) when RTT < 50 -> ?BRIGHT_GREEN;
rtt_color(RTT) when RTT < 100 -> ?GREEN;
rtt_color(RTT) when RTT < 200 -> ?YELLOW;
rtt_color(RTT) when RTT < 500 -> ?BRIGHT_YELLOW;
rtt_color(_) -> ?RED.
%% @private Get color based on timeout count
timeout_color(0) -> ?GREEN;
timeout_color(T) when T < 5 -> ?YELLOW;
timeout_color(_) -> ?RED.
%% @private Get color based on success rate
success_color(Rate) when Rate >= 90.0 -> ?BRIGHT_GREEN;
success_color(Rate) when Rate >= 70.0 -> ?GREEN;
success_color(Rate) when Rate >= 50.0 -> ?YELLOW;
success_color(_) -> ?RED.
%% @private Get color based on delivery rate (for pubsub)
delivery_color(Rate) when Rate >= 95.0 -> ?BRIGHT_GREEN;
delivery_color(Rate) when Rate >= 80.0 -> ?GREEN;
delivery_color(Rate) when Rate >= 60.0 -> ?YELLOW;
delivery_color(_) -> ?RED.
%% @private Calculate success rate
calc_success_rate(0, _) -> 0.0;
calc_success_rate(Sent, Received) ->
(Received / Sent) * 100.0.