Skip to content

Commit 7494cb1

Browse files
authored
Merge pull request #26 from meshtastic/wip
Live dashboard wip
2 parents 1f72045 + faaa387 commit 7494cb1

File tree

7 files changed

+310
-229
lines changed

7 files changed

+310
-229
lines changed

Meshtastic.Cli/CommandHandlers/LiveCommandHandler.cs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
using Meshtastic.Data;
22
using Meshtastic.Data.MessageFactories;
33
using Meshtastic.Display;
4-
using Meshtastic.Extensions;
54
using Meshtastic.Protobufs;
6-
using Microsoft.Extensions.Logging;
7-
using Spectre.Console;
85

96
namespace Meshtastic.Cli.CommandHandlers;
107

@@ -46,11 +43,12 @@ await Connection.ReadFromRadio((fromRadio, container) =>
4643
});
4744
});
4845
}
46+
4947

5048
private static void UpdateDashboard(Layout layout, ProtobufPrinter printer)
5149
{
52-
layout["Nodes"].Update(printer.PrintNodesTable(compactTable: true));
53-
layout["Traffic"].Update(printer.PrintTrafficChart());
50+
layout["Nodes"].Update(printer.PrintNodesPanel());
51+
layout["Traffic"].Update(printer.PrintTrafficCharts());
5452
layout["Messages"].Update(printer.PrintMessagesPanel());
5553
}
5654
}

Meshtastic.Cli/Display/ProtobufPrinter.cs

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -55,24 +55,25 @@ public Table PrintNodesTable(bool compactTable = false)
5555
{
5656
var table = new Table();
5757
table.Expand();
58-
table.Border(TableBorder.Rounded);
58+
table.Border(compactTable ? TableBorder.Minimal : TableBorder.Rounded);
5959
table.BorderColor(StyleResources.MESHTASTIC_GREEN);
6060
table.Centered();
6161
if (compactTable)
6262
table.AddColumns("Name", "Latitude", "Longitude", "Battery", "SNR", "Last Heard");
63-
else
64-
table.AddColumns("ID#", "Name", "Latitude", "Longitude","Battery", "Air Util", "Ch. Util", "SNR", "Last Heard");
63+
else
64+
table.AddColumns("ID#", "Name", "Latitude", "Longitude", "Battery", "Air Util", "Ch. Util", "SNR", "Last Heard");
6565

6666
foreach (var node in container.Nodes)
6767
{
6868
if (compactTable)
6969
{
70-
table.AddRow(container.GetNodeDisplayName(node.Num, hideNodeNum: true),
71-
(node.Position?.LatitudeI * 1e-7 ?? 0).ToString("N6") ?? String.Empty,
72-
(node.Position?.LongitudeI * 1e-7 ?? 0).ToString("N6") ?? String.Empty,
73-
$"{node.DeviceMetrics?.BatteryLevel}%",
74-
node.Snr.ToString(),
75-
DisplayRelativeTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(node.LastHeard)));
70+
table.AddRow(
71+
new Text(container.GetNodeDisplayName(node.Num, hideNodeNum: true), new Style(GetColorFromNum(node.Num))),
72+
new Text((node.Position?.LatitudeI * 1e-7 ?? 0).ToString("N6") ?? String.Empty),
73+
new Text((node.Position?.LongitudeI * 1e-7 ?? 0).ToString("N6") ?? String.Empty),
74+
new Text($"{node.DeviceMetrics?.BatteryLevel}%"),
75+
new Text(node.Snr.ToString()),
76+
new Text(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(node.LastHeard).AsTimeAgo()));
7677
}
7778
else
7879
{
@@ -84,44 +85,20 @@ public Table PrintNodesTable(bool compactTable = false)
8485
$"{node.DeviceMetrics?.AirUtilTx.ToString("N2")}%" ?? String.Empty,
8586
$"{node.DeviceMetrics?.ChannelUtilization.ToString("N2")}%" ?? String.Empty,
8687
node.Snr.ToString(),
87-
DisplayRelativeTime(new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(node.LastHeard)));
88+
new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(node.LastHeard).AsTimeAgo());
8889
}
8990
}
9091
return table;
9192
}
9293

93-
private static string DisplayRelativeTime(DateTime dateTime)
94+
public Panel PrintNodesPanel()
9495
{
95-
TimeSpan span = DateTime.Now.Subtract(dateTime);
96-
97-
int dayDiff = (int)span.TotalDays;
98-
99-
int secDiff = (int)span.TotalSeconds;
100-
101-
if (dayDiff >= 31)
102-
return "A long time ago";
103-
104-
if (dayDiff == 0)
96+
return new Panel(PrintNodesTable(true))
10597
{
106-
if (secDiff < 60)
107-
return "just now";
108-
if (secDiff < 120)
109-
return "1 minute ago";
110-
if (secDiff < 3600)
111-
return $"{Math.Floor((double)secDiff / 60)} minutes ago";
112-
if (secDiff < 7200)
113-
return "1 hour ago";
114-
if (secDiff < 86400)
115-
return $"{Math.Floor((double)secDiff / 3600)} hours ago";
116-
}
117-
if (dayDiff == 1)
118-
return "yesterday";
119-
if (dayDiff < 7)
120-
return $"{dayDiff} days ago";
121-
if (dayDiff < 31)
122-
return $"{Math.Ceiling((double)dayDiff / 7)} weeks ago";
123-
124-
return String.Empty;
98+
Header = new PanelHeader("Nodes"),
99+
Expand = true,
100+
BorderStyle = new Style(StyleResources.MESHTASTIC_GREEN)
101+
};
125102
}
126103

127104

@@ -303,20 +280,43 @@ public void PrintRoute(RepeatedField<uint> route)
303280
AnsiConsole.Write(root);
304281
}
305282
}
306-
public BarChart PrintTrafficChart()
283+
284+
public Panel PrintTrafficCharts()
307285
{
308286
var myInfo = container.Nodes.FirstOrDefault(n => n.Num == container.MyNodeInfo.MyNodeNum);
309-
var airTimeStats = myInfo != null ? $"(Ch. Util {myInfo.DeviceMetrics.ChannelUtilization:N2}% / Airtime {myInfo.DeviceMetrics.AirUtilTx:N2}%)" : String.Empty;
310-
311-
return new BarChart()
312-
.Label($"[green bold underline]Mesh traffic by Port {airTimeStats} [/]")
313-
.CenterLabel()
314-
.AddItem("Admin", GetMessageCountByPortNum(PortNum.AdminApp), Color.Red)
315-
.AddItem("Text", GetMessageCountByPortNum(PortNum.TextMessageApp) + GetMessageCountByPortNum(PortNum.TextMessageCompressedApp), Color.Yellow)
316-
.AddItem("Position", GetMessageCountByPortNum(PortNum.PositionApp), Color.Green)
317-
.AddItem("Waypoint", GetMessageCountByPortNum(PortNum.WaypointApp), Color.Pink1)
318-
.AddItem("NodeInfo", GetMessageCountByPortNum(PortNum.NodeinfoApp), Color.White)
319-
.AddItem("Telemetry", GetMessageCountByPortNum(PortNum.TelemetryApp, ignoreLocal: true), Color.Blue);
287+
var airTimeStats = myInfo != null ? $"Ch. Util {myInfo.DeviceMetrics.ChannelUtilization:N2}% / Airtime {myInfo.DeviceMetrics.AirUtilTx:N2}%" : String.Empty;
288+
289+
var byNodeChart = new BreakdownChart()
290+
.FullSize();
291+
292+
foreach(var node in container.Nodes.Select(n => new { n.Num, Name = container.GetNodeDisplayName(n.Num, hideNodeNum: true), Count = container.FromRadioMessageLog.Count(fr => fr.Packet?.From == n.Num) }))
293+
{
294+
byNodeChart.AddItem(node.Name, node.Count, GetColorFromNum(node.Num));
295+
}
296+
297+
var charts = new Rows(new Text(String.Empty),
298+
new Text(airTimeStats).Centered(),
299+
new Text(String.Empty),
300+
new Text("By Port:").Centered(),
301+
new BarChart()
302+
.AddItem("Admin", GetMessageCountByPortNum(PortNum.AdminApp), Color.Red)
303+
.AddItem("Text", GetMessageCountByPortNum(PortNum.TextMessageApp) + GetMessageCountByPortNum(PortNum.TextMessageCompressedApp), Color.Yellow)
304+
.AddItem("Routing", GetMessageCountByPortNum(PortNum.RoutingApp), Color.Grey)
305+
.AddItem("Position", GetMessageCountByPortNum(PortNum.PositionApp), Color.Green)
306+
.AddItem("Waypoint", GetMessageCountByPortNum(PortNum.WaypointApp), Color.Pink1)
307+
.AddItem("NodeInfo", GetMessageCountByPortNum(PortNum.NodeinfoApp), Color.White)
308+
.AddItem("Telemetry", GetMessageCountByPortNum(PortNum.TelemetryApp, ignoreLocal: true), Color.Blue),
309+
new Text(String.Empty),
310+
new Text("By Node:").Centered(),
311+
byNodeChart);
312+
313+
var panel = new Panel(charts)
314+
{
315+
Header = new PanelHeader($"Mesh traffic"),
316+
Expand = true,
317+
BorderStyle = new Style(StyleResources.MESHTASTIC_GREEN)
318+
};
319+
return panel;
320320
}
321321

322322
public Panel PrintMessagesPanel()
@@ -325,18 +325,22 @@ public Panel PrintMessagesPanel()
325325
.Where(fr => fr.Packet?.Decoded?.Portnum == PortNum.TextMessageApp || fr.Packet?.Decoded?.Portnum == PortNum.TextMessageCompressedApp)
326326
.Select(fr => new
327327
{
328-
from = $"[green bold underline]{container.GetNodeDisplayName(fr.Packet.From, hideNodeNum: true)}[/]",
328+
time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(fr.Packet.RxTime).AsTimeAgo(),
329+
num = fr.Packet.From,
330+
from = $"[bold]{container.GetNodeDisplayName(fr.Packet.From, hideNodeNum: true)}[/]",
329331
message = fr.Packet?.Decoded?.Payload.ToStringUtf8()
330332
})
331333
.SelectMany(text =>
332334
{
333335
return new List<IRenderable>() {
334-
new Rule(text.from),
336+
new Rule($"{text.from} - {text.time}")
337+
{
338+
Style = new Style(GetColorFromNum(text.num))
339+
},
335340
new Text(text.message!)
336341
};
337342
});
338343

339-
340344
if (!texts.Any())
341345
{
342346
return new Panel("Messages")
@@ -356,8 +360,13 @@ public Panel PrintMessagesPanel()
356360
return panel;
357361
}
358362

363+
private Color GetColorFromNum(uint num)
364+
{
365+
return new Color(BitConverter.GetBytes(num)[0], BitConverter.GetBytes(num)[1], BitConverter.GetBytes(num)[2]);
366+
}
367+
359368
private int GetMessageCountByPortNum(PortNum portNum, bool ignoreLocal = false)
360369
{
361-
return container.FromRadioMessageLog.Count(fr => fr.Packet?.Decoded?.Portnum == portNum && (!ignoreLocal || fr.Packet?.From == container.MyNodeInfo.MyNodeNum));
370+
return container.FromRadioMessageLog.Count(fr => fr.Packet?.Decoded?.Portnum == portNum && (!ignoreLocal || fr.Packet?.From != container.MyNodeInfo.MyNodeNum));
362371
}
363372
}

Meshtastic/Data/DeviceStateContainer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public void AddFromRadio(FromRadio fromRadio)
6060
if (fromRadio.PayloadVariantCase == FromRadio.PayloadVariantOneofCase.Metadata)
6161
this.Metadata = fromRadio.Metadata;
6262

63-
this.FromRadioMessageLog.Add(fromRadio);
63+
this.FromRadioMessageLog.Insert(0, fromRadio);
6464
}
6565

6666
public void AddToRadio(ToRadio toRadio)

Meshtastic/Extensions/DateTimeExtensions.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,36 @@ public static uint GetUnixTimestamp(this DateTime dateTime)
66
{
77
return Convert.ToUInt32(dateTime.Subtract(new DateTime(1970, 1, 1)).TotalSeconds);
88
}
9+
10+
public static string AsTimeAgo(this DateTime dateTime)
11+
{
12+
TimeSpan timeSpan = DateTime.UtcNow.Subtract(dateTime);
13+
14+
return timeSpan.TotalSeconds switch
15+
{
16+
<= 60 => $"{timeSpan.Seconds} seconds ago",
17+
18+
_ => timeSpan.TotalMinutes switch
19+
{
20+
<= 1 => "A minute ago",
21+
< 60 => $"{timeSpan.Minutes} minutes ago",
22+
_ => timeSpan.TotalHours switch
23+
{
24+
<= 1 => "A hour ago",
25+
< 24 => $"{timeSpan.Hours} hours ago",
26+
_ => timeSpan.TotalDays switch
27+
{
28+
<= 1 => "yesterday",
29+
<= 30 => $"{timeSpan.Days} days ago",
30+
31+
<= 60 => "A month ago",
32+
< 365 => $"{timeSpan.Days / 30} months ago",
33+
34+
<= 365 * 2 => "A year ago",
35+
_ => $"{timeSpan.Days / 365} years ago"
36+
}
37+
}
38+
}
39+
};
40+
}
941
}

0 commit comments

Comments
 (0)