Skip to content

Commit c32a5d4

Browse files
committed
Channels and refactoring
1 parent 0538efb commit c32a5d4

28 files changed

+745
-217
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.CommandLine.Binding;
2+
using Meshtastic.Cli.Enums;
3+
using Meshtastic.Protobufs;
4+
5+
namespace Meshtastic.Cli.Binders;
6+
7+
public record ChannelOperationSettings(ChannelOperation Operation, int? Index, string? Name, Channel.Types.Role? Role, string? PSK, bool? UplinkEnabled, bool? DownlinkEnabled);
8+
9+
public class ChannelBinder : BinderBase<ChannelOperationSettings>
10+
{
11+
private readonly Argument<ChannelOperation> operation;
12+
private readonly Option<int?> indexOption;
13+
private readonly Option<string?> nameOption;
14+
private readonly Option<Channel.Types.Role?> roleOption;
15+
private readonly Option<string?> pskOption;
16+
private readonly Option<bool?> uplinkOption;
17+
private readonly Option<bool?> downlinkOption;
18+
19+
public ChannelBinder(Argument<ChannelOperation> operation,
20+
Option<int?> indexOption,
21+
Option<string?> nameOption,
22+
Option<Channel.Types.Role?> roleOption,
23+
Option<string?> pskOption,
24+
Option<bool?> uplinkOption,
25+
Option<bool?> downlinkOption)
26+
{
27+
this.operation = operation;
28+
this.indexOption = indexOption;
29+
this.nameOption = nameOption;
30+
this.roleOption = roleOption;
31+
this.pskOption = pskOption;
32+
this.uplinkOption = uplinkOption;
33+
this.downlinkOption = downlinkOption;
34+
}
35+
36+
protected override ChannelOperationSettings GetBoundValue(BindingContext bindingContext) =>
37+
new(bindingContext.ParseResult.GetValueForArgument(operation),
38+
bindingContext.ParseResult?.GetValueForOption(indexOption),
39+
bindingContext.ParseResult?.GetValueForOption(nameOption),
40+
bindingContext.ParseResult?.GetValueForOption(roleOption),
41+
bindingContext.ParseResult?.GetValueForOption(pskOption),
42+
bindingContext.ParseResult?.GetValueForOption(uplinkOption),
43+
bindingContext.ParseResult?.GetValueForOption(downlinkOption))
44+
{
45+
};
46+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
using Google.Protobuf;
2+
using Microsoft.Extensions.Logging;
3+
using Meshtastic.Data;
4+
using Meshtastic.Cli.Enums;
5+
using Meshtastic.Cli.Binders;
6+
using Meshtastic.Protobufs;
7+
using System.Security.Cryptography;
8+
using System.Text;
9+
10+
namespace Meshtastic.Cli.Commands;
11+
12+
public class ChannelCommand : Command
13+
{
14+
public ChannelCommand(string name, string description, Option<string> port, Option<string> host) : base(name, description)
15+
{
16+
var operationArgument = new Argument<ChannelOperation>("operation", "The type of channel operation");
17+
operationArgument.AddCompletions(ctx => Enum.GetNames(typeof(ChannelOperation)));
18+
19+
var indexOption = new Option<int?>("--index", description: "Channel index");
20+
indexOption.AddAlias("-i");
21+
var nameOption = new Option<string?>("--name", description: "Channel name");
22+
nameOption.AddAlias("-n");
23+
var roleOption = new Option<Channel.Types.Role?>("--role", description: "Channel role");
24+
roleOption.AddAlias("-r");
25+
var pskOption = new Option<string?>("--psk", description: "Channel pre-shared key");
26+
pskOption.AddAlias("-p");
27+
var uplinkOption = new Option<bool?>("--uplink-enabled", description: "Channel uplink enabled");
28+
uplinkOption.AddAlias("-u");
29+
var downlinkOption = new Option<bool?>("--downlink-enabled", description: "Channel downlink enabled");
30+
downlinkOption.AddAlias("-d");
31+
var channelBinder = new ChannelBinder(operationArgument, indexOption, nameOption, roleOption, pskOption, uplinkOption, downlinkOption);
32+
33+
var channelCommandHandler = new ChannelCommandHandler();
34+
this.SetHandler(channelCommandHandler.Handle,
35+
channelBinder,
36+
new ConnectionBinder(port, host),
37+
new LoggingBinder());
38+
39+
AddArgument(operationArgument);
40+
AddOption(indexOption);
41+
AddOption(nameOption);
42+
AddOption(roleOption);
43+
AddOption(pskOption);
44+
AddOption(uplinkOption);
45+
AddOption(downlinkOption);
46+
}
47+
}
48+
49+
public class ChannelCommandHandler : DeviceCommandHandler
50+
{
51+
private ChannelOperationSettings? _settings;
52+
public async Task Handle(ChannelOperationSettings settings, DeviceConnectionContext context, ILogger logger)
53+
{
54+
_settings = settings;
55+
if (settings.Index.HasValue && (settings.Index.Value > 8 || settings.Index.Value < 0))
56+
{
57+
AnsiConsole.WriteLine("[red]Channel index is out of range[/]");
58+
return;
59+
}
60+
if (settings.Operation != ChannelOperation.Add && !settings.Index.HasValue)
61+
{
62+
AnsiConsole.WriteLine($"[red]Must specify an index for this {settings.Operation} operation[/]");
63+
return;
64+
}
65+
if ((settings.Operation == ChannelOperation.Disable || settings.Operation == ChannelOperation.Enable) &&
66+
settings.Index == 0)
67+
{
68+
AnsiConsole.WriteLine($"[red]Cannot enable / disable PRIMARY channel[/]");
69+
return;
70+
}
71+
72+
await OnConnection(context, async () =>
73+
{
74+
connection = context.GetDeviceConnection();
75+
var wantConfig = ToRadioMessageFactory.CreateWantConfigMessage();
76+
77+
await connection.WriteToRadio(wantConfig.ToByteArray(), DefaultIsCompleteAsync);
78+
});
79+
}
80+
81+
public override async Task OnCompleted(FromDeviceMessage packet, DeviceStateContainer container)
82+
{
83+
if (_settings == null)
84+
throw new InvalidOperationException("Cannot complete ChannelCommandHandler without ChannelOperationSettings");
85+
86+
var adminMessageFactory = new AdminMessageFactory(container);
87+
await BeginEditSettings(adminMessageFactory);
88+
89+
var channel = container.Channels.Find(c => c.Index == _settings.Index);
90+
91+
AnsiConsole.MarkupLine("Writing channel");
92+
93+
switch (_settings.Operation)
94+
{
95+
case ChannelOperation.Add:
96+
container.Channels.Find(c => c.Role == Channel.Types.Role.Disabled);
97+
SetChannelSettings(_settings, channel);
98+
break;
99+
case ChannelOperation.Disable:
100+
if (channel != null) channel.Role = Channel.Types.Role.Disabled;
101+
break;
102+
case ChannelOperation.Save:
103+
SetChannelSettings(_settings, channel);
104+
break;
105+
case ChannelOperation.Enable:
106+
if (channel != null) channel.Role = Channel.Types.Role.Primary;
107+
break;
108+
default:
109+
throw new InvalidOperationException("Cannot complete ChannelCommandHandler without ChannelOperation");
110+
}
111+
var adminMessage = adminMessageFactory.CreateSetChannelMessage(channel!);
112+
await connection!.WriteToRadio(ToRadioMessageFactory.CreateMeshPacketMessage(adminMessage).ToByteArray(),
113+
AlwaysComplete);
114+
await CommitEditSettings(adminMessageFactory);
115+
}
116+
117+
private static void SetChannelSettings(ChannelOperationSettings settings, Channel? channel)
118+
{
119+
if (channel != null)
120+
{
121+
if (channel.Index > 0)
122+
channel.Role = settings.Role ?? Channel.Types.Role.Secondary;
123+
if (settings.Name != null)
124+
channel.Settings.Name = settings.Name;
125+
if (settings.DownlinkEnabled.HasValue)
126+
channel.Settings.DownlinkEnabled = settings.DownlinkEnabled.Value;
127+
if (settings.UplinkEnabled.HasValue)
128+
channel.Settings.UplinkEnabled = settings.UplinkEnabled.Value;
129+
if (settings.PSK != null)
130+
{
131+
if (settings.PSK == "none")
132+
channel.Settings.Psk = ByteString.Empty;
133+
else if (settings.PSK == "random")
134+
{
135+
using var random = RandomNumberGenerator.Create();
136+
byte[] data = new byte[32];
137+
random.GetBytes(data);
138+
channel.Settings.Psk = ByteString.CopyFrom(data);
139+
}
140+
else
141+
{
142+
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(settings.PSK));
143+
channel.Settings.Psk = ByteString.CopyFrom(hash);
144+
}
145+
}
146+
}
147+
else
148+
{
149+
AnsiConsole.MarkupLine("[red]Could not find available channel[/]");
150+
}
151+
}
152+
}

Meshtastic.Cli/Handlers/DeviceCommandHandler.cs renamed to Meshtastic.Cli/Commands/DeviceCommandHandler.cs

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
using Meshtastic.Cli.Parsers;
1+
using Google.Protobuf;
2+
using Meshtastic.Cli.Parsers;
23
using Meshtastic.Connections;
34
using Meshtastic.Data;
45
using Meshtastic.Protobufs;
56

6-
namespace Meshtastic.Cli.Handlers;
7+
namespace Meshtastic.Cli.Commands;
78

89
public class DeviceCommandHandler
910
{
@@ -12,15 +13,8 @@ public class DeviceCommandHandler
1213

1314
protected async Task OnConnection(DeviceConnectionContext context, Func<Task> operation)
1415
{
15-
await AnsiConsole.Status()
16-
.StartAsync("Connecting...", async ctx =>
17-
{
18-
ctx.Status($"Connecting {context.DisplayName}...");
19-
ctx.Spinner(Spinner.Known.Dots);
20-
ctx.SpinnerStyle(new Style(StyleResources.MESHTASTIC_GREEN));
21-
22-
await operation();
23-
});
16+
AnsiConsole.MarkupLine($"Connecting {context.DisplayName}...");
17+
await operation();
2418
}
2519

2620
protected static (SettingParserResult? result, bool isValid) ParseSettingOptions(IEnumerable<string> settings, bool isGetOnly)
@@ -68,4 +62,21 @@ public virtual async Task OnCompleted(FromDeviceMessage packet, DeviceStateConta
6862
{
6963
await Task.CompletedTask;
7064
}
65+
66+
protected async Task BeginEditSettings(AdminMessageFactory adminMessageFactory)
67+
{
68+
var message = adminMessageFactory.CreateBeginEditSettingsMessage();
69+
await connection!.WriteToRadio(ToRadioMessageFactory.CreateMeshPacketMessage(message).ToByteArray(),
70+
AlwaysComplete);
71+
AnsiConsole.MarkupLine($"[olive]Starting edit transaction for settings...[/]");
72+
}
73+
74+
protected async Task CommitEditSettings(AdminMessageFactory adminMessageFactory)
75+
{
76+
var message = adminMessageFactory.CreateCommitEditSettingsMessage();
77+
await connection!.WriteToRadio(ToRadioMessageFactory.CreateMeshPacketMessage(message).ToByteArray(),
78+
AlwaysComplete);
79+
AnsiConsole.MarkupLine($"[green]Commit edit transaction for settings...[/]");
80+
}
81+
7182
}

Meshtastic.Cli/Handlers/GetCommandHandler.cs renamed to Meshtastic.Cli/Commands/GetCommandHandler.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
1-
using Meshtastic.Connections;
21
using Meshtastic.Display;
32
using Google.Protobuf;
43
using Microsoft.Extensions.Logging;
54
using Meshtastic.Data;
65
using Meshtastic.Cli.Parsers;
6+
using Meshtastic.Cli.Binders;
77

8-
namespace Meshtastic.Cli.Handlers;
8+
namespace Meshtastic.Cli.Commands;
99

10+
public class GetCommand : Command
11+
{
12+
public GetCommand(string name, string description, Option<string> port, Option<string> host, Option<IEnumerable<string>> settings) :
13+
base(name, description)
14+
{
15+
var getCommandHandler = new GetCommandHandler();
16+
this.SetHandler(getCommandHandler.Handle,
17+
settings,
18+
new ConnectionBinder(port, host),
19+
new LoggingBinder());
20+
this.AddOption(settings);
21+
}
22+
}
1023
public class GetCommandHandler : DeviceCommandHandler
1124
{
1225
private IEnumerable<ParsedSetting>? parsedSettings;
@@ -20,7 +33,7 @@ public async Task Handle(IEnumerable<string> settings, DeviceConnectionContext c
2033

2134
await OnConnection(context, async () =>
2235
{
23-
var connection = context.GetDeviceConnection();
36+
connection = context.GetDeviceConnection();
2437
var wantConfig = new ToRadioMessageFactory().CreateWantConfigMessage();
2538

2639
await connection.WriteToRadio(wantConfig.ToByteArray(), DefaultIsCompleteAsync);

Meshtastic.Cli/Handlers/InfoCommandHandler.cs renamed to Meshtastic.Cli/Commands/InfoCommandHandler.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,28 @@
11
using Meshtastic.Connections;
2-
using Meshtastic.Display;
32
using Google.Protobuf;
43
using Microsoft.Extensions.Logging;
54
using Meshtastic.Data;
5+
using Meshtastic.Display;
6+
using Meshtastic.Cli.Binders;
67

7-
namespace Meshtastic.Cli.Handlers;
8-
8+
namespace Meshtastic.Cli.Commands;
9+
public class InfoCommand : Command
10+
{
11+
public InfoCommand(string name, string description, Option<string> port, Option<string> host) : base(name, description)
12+
{
13+
var infoCommandHandler = new InfoCommandHandler();
14+
this.SetHandler(infoCommandHandler.Handle,
15+
new ConnectionBinder(port, host),
16+
new LoggingBinder());
17+
}
18+
}
919
public class InfoCommandHandler : DeviceCommandHandler
1020
{
1121
public async Task Handle(DeviceConnectionContext context, ILogger logger)
1222
{
1323
await OnConnection(context, async () =>
1424
{
15-
var connection = context.GetDeviceConnection();
25+
connection = context.GetDeviceConnection();
1626
var wantConfig = new ToRadioMessageFactory().CreateWantConfigMessage();
1727

1828
await connection.WriteToRadio(wantConfig.ToByteArray(), DefaultIsCompleteAsync);
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Meshtastic.Cli.Binders;
2+
using Meshtastic.Connections;
3+
using Microsoft.Extensions.Logging;
4+
5+
namespace Meshtastic.Cli.Commands;
6+
7+
public class ListCommand : Command
8+
{
9+
public ListCommand(string name, string description, Option<string> port, Option<string> host) : base(name, description)
10+
{
11+
var listCommandHandler = new ListCommandHandler();
12+
this.SetHandler(listCommandHandler.Handle, new LoggingBinder());
13+
}
14+
}
15+
public class ListCommandHandler
16+
{
17+
public async Task Handle(ILogger logger)
18+
{
19+
AnsiConsole.WriteLine("Found the following serial ports:");
20+
foreach (var port in SerialConnection.ListPorts())
21+
AnsiConsole.WriteLine(port);
22+
await Task.CompletedTask;
23+
}
24+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
using Meshtastic.Cli.Binders;
2+
using Microsoft.Extensions.Logging;
3+
4+
namespace Meshtastic.Cli.Commands;
5+
6+
public class MonitorCommand : Command
7+
{
8+
public MonitorCommand(string name, string description, Option<string> port, Option<string> host) : base(name, description)
9+
{
10+
var monitorCommandHandler = new MonitorCommandHandler();
11+
this.SetHandler(monitorCommandHandler.Handle,
12+
new ConnectionBinder(port, host),
13+
new LoggingBinder());
14+
}
15+
}
16+
17+
public class MonitorCommandHandler
18+
{
19+
public async Task Handle(DeviceConnectionContext context, ILogger logger)
20+
{
21+
var connection = context.GetDeviceConnection();
22+
await connection.Monitor();
23+
}
24+
}

0 commit comments

Comments
 (0)