Skip to content

Commit 802905e

Browse files
authored
Merge pull request #38 from meshtastic/wip
MQTT Client Proxy handling and v2.1.18 protos
2 parents cad7745 + 40b531f commit 802905e

File tree

15 files changed

+1406
-270
lines changed

15 files changed

+1406
-270
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dotnet.defaultSolution": "Meshtastic.sln"
3+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"dotnet.defaultSolution": "Meshtastic.Cli.sln"
3+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using Meshtastic.Data;
2+
using Meshtastic.Data.MessageFactories;
3+
using MQTTnet;
4+
using Meshtastic.Protobufs;
5+
using MQTTnet.Client;
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Meshtastic.Cli.CommandHandlers;
9+
10+
public class MqttProxyCommandHandler : DeviceCommandHandler
11+
{
12+
public MqttProxyCommandHandler(DeviceConnectionContext context, CommandContext commandContext) : base(context, commandContext) { }
13+
14+
public async Task<DeviceStateContainer> Handle()
15+
{
16+
var wantConfig = new ToRadioMessageFactory().CreateWantConfigMessage();
17+
var container = await Connection.WriteToRadio(wantConfig, CompleteOnConfigReceived);
18+
Connection.Disconnect();
19+
return container;
20+
}
21+
22+
public override async Task OnCompleted(FromRadio packet, DeviceStateContainer container)
23+
{
24+
// connect to mqtt server with mqttnet
25+
var factory = new MqttFactory();
26+
using var mqttClient = factory.CreateMqttClient();
27+
MqttClientOptions options = GetMqttClientOptions(container);
28+
await mqttClient.ConnectAsync(options, CancellationToken.None);
29+
30+
var root = String.IsNullOrWhiteSpace(container.LocalModuleConfig.Mqtt.Root) ? "msh" : container.LocalModuleConfig.Mqtt.Root;
31+
var prefix = $"{root}/{container.Metadata.FirmwareVersion.First()}";
32+
var subscriptionTopic = $"{prefix}/#";
33+
34+
Logger.LogInformation($"Subscribing to topic: {subscriptionTopic}");
35+
await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder()
36+
.WithTopic(subscriptionTopic)
37+
.Build());
38+
39+
mqttClient.ApplicationMessageReceivedAsync += async e =>
40+
{
41+
if (e.ApplicationMessage.Topic.StartsWith($"{prefix}/stat/"))
42+
return;
43+
44+
Logger.LogInformation($"Received MQTT from host on topic: {e.ApplicationMessage.Topic}");
45+
46+
// Get bytes from utf8 string
47+
var toRadio = new ToRadioMessageFactory()
48+
.CreateMqttClientProxyMessage(e.ApplicationMessage.Topic, e.ApplicationMessage.PayloadSegment.ToArray(), e.ApplicationMessage.Retain);
49+
Logger.LogDebug(toRadio.ToString());
50+
await Connection.WriteToRadio(toRadio);
51+
};
52+
53+
await Connection.ReadFromRadio(async (fromRadio, container) =>
54+
{
55+
if (fromRadio?.PayloadVariantCase == FromRadio.PayloadVariantOneofCase.MqttClientProxyMessage &&
56+
fromRadio.MqttClientProxyMessage is not null)
57+
{
58+
var message = fromRadio.MqttClientProxyMessage;
59+
Logger.LogInformation($"Received MQTT message from device to proxy on topic: {message.Topic}");
60+
if (message.PayloadVariantCase == MqttClientProxyMessage.PayloadVariantOneofCase.Data)
61+
{
62+
Logger.LogDebug(ServiceEnvelope.Parser.ParseFrom(message.Data).ToString());
63+
await mqttClient.PublishAsync(new MqttApplicationMessageBuilder()
64+
.WithTopic(message.Topic)
65+
.WithPayload(message.Data.ToByteArray())
66+
.WithRetainFlag(message.Retained)
67+
.Build());
68+
}
69+
else if (message.PayloadVariantCase == MqttClientProxyMessage.PayloadVariantOneofCase.Text)
70+
{
71+
Logger.LogDebug(message.Text);
72+
await mqttClient.PublishAsync(new MqttApplicationMessageBuilder()
73+
.WithTopic(message.Topic)
74+
.WithPayload(message.Text)
75+
.WithRetainFlag(message.Retained)
76+
.Build());
77+
}
78+
}
79+
return false;
80+
});
81+
}
82+
83+
private MqttClientOptions GetMqttClientOptions(DeviceStateContainer container)
84+
{
85+
var builder = new MqttClientOptionsBuilder()
86+
.WithClientId(container.GetDeviceNodeInfo()?.User?.Id ?? container.MyNodeInfo.MyNodeNum.ToString());
87+
88+
var address = container.LocalModuleConfig.Mqtt.Address;
89+
var host = address.Split(':').FirstOrDefault() ?? container.LocalModuleConfig.Mqtt.Address;
90+
var port = address.Contains(":") ? address.Split(':').LastOrDefault() : null;
91+
92+
if (container.LocalModuleConfig.Mqtt.TlsEnabled)
93+
{
94+
builder = builder.WithTls()
95+
.WithTcpServer(host, Int32.Parse(port ?? "8883"));
96+
}
97+
else {
98+
builder = builder.WithTcpServer(host, Int32.Parse(port ?? "1883"));
99+
}
100+
101+
if (container.LocalModuleConfig.Mqtt.Username is not null)
102+
builder = builder.WithCredentials(container.LocalModuleConfig.Mqtt.Username, container.LocalModuleConfig.Mqtt.Password);
103+
104+
return builder.Build();
105+
}
106+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using Meshtastic.Cli.Binders;
2+
using Meshtastic.Cli.CommandHandlers;
3+
using Meshtastic.Cli.Enums;
4+
using Microsoft.Extensions.Logging;
5+
6+
namespace Meshtastic.Cli.Commands;
7+
public class MqttProxyCommand : Command
8+
{
9+
public MqttProxyCommand(string name, string description, Option<string> port, Option<string> host,
10+
Option<OutputFormat> output, Option<LogLevel> log) : base(name, description)
11+
{
12+
this.SetHandler(async (context, commandContext) =>
13+
{
14+
var handler = new MqttProxyCommandHandler(context, commandContext);
15+
await handler.Handle();
16+
},
17+
new DeviceConnectionBinder(port, host),
18+
new CommandContextBinder(log, output, new Option<uint?>("dest") { }, new Option<bool>("select-dest") { }));
19+
}
20+
}

Meshtastic.Cli/Meshtastic.Cli.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
4141
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="7.0.0" />
4242
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="7.0.0" />
43+
<PackageReference Include="MQTTnet" Version="4.2.1.781" />
4344
<PackageReference Include="QRCoder" Version="1.4.3" />
4445
<PackageReference Include="SimpleExec" Version="11.0.0" />
4546
<PackageReference Include="Spectre.Console" Version="0.46.0" />

Meshtastic.Cli/Meshtastic.Cli.sln

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
2+
Microsoft Visual Studio Solution File, Format Version 12.00
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.5.001.0
5+
MinimumVisualStudioVersion = 10.0.40219.1
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Meshtastic.Cli", "Meshtastic.Cli.csproj", "{A3FB32D2-954E-4D1D-B789-1E14AA5B2CB6}"
7+
EndProject
8+
Global
9+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
11+
Release|Any CPU = Release|Any CPU
12+
EndGlobalSection
13+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
14+
{A3FB32D2-954E-4D1D-B789-1E14AA5B2CB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15+
{A3FB32D2-954E-4D1D-B789-1E14AA5B2CB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
16+
{A3FB32D2-954E-4D1D-B789-1E14AA5B2CB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
17+
{A3FB32D2-954E-4D1D-B789-1E14AA5B2CB6}.Release|Any CPU.Build.0 = Release|Any CPU
18+
EndGlobalSection
19+
GlobalSection(SolutionProperties) = preSolution
20+
HideSolutionNode = FALSE
21+
EndGlobalSection
22+
GlobalSection(ExtensibilityGlobals) = postSolution
23+
SolutionGuid = {CF056994-0472-486E-8D07-0D470536C40D}
24+
EndGlobalSection
25+
EndGlobal

Meshtastic.Cli/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
root.AddCommand(new UpdateCommand("update", "Update the firmware of the serial connected device", port, host, output, log));
6161
root.AddCommand(new ExportCommand("export", "Export the profile of the connected device as yaml", port, host, output, log));
6262
root.AddCommand(new ImportCommand("import", "Import the profile export from a yaml file and set the connected device", port, host, output, log));
63+
root.AddCommand(new MqttProxyCommand("mqtt-proxy", "Proxy to the MQTT server referenced in the MQTT module config of the connected device", port, host, output, log));
6364

6465
var parser = new CommandLineBuilder(root)
6566
.UseExceptionHandler((ex, context) =>

Meshtastic.Cli/Properties/launchSettings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@
139139
"import": {
140140
"commandName": "Project",
141141
"commandLineArgs": "import"
142+
},
143+
"mqtt": {
144+
"commandName": "Project",
145+
"commandLineArgs": "mqtt-proxy"
142146
}
143147
}
144148
}

Meshtastic/Data/MessageFactories/ToRadioMessageFactory.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using Google.Protobuf;
12
using Meshtastic.Protobufs;
23
using static Meshtastic.Protobufs.XModem.Types;
34

@@ -29,4 +30,15 @@ public ToRadio CreateXmodemPacketMessage(Control control = XModem.Types.Control.
2930
Control = control
3031
}
3132
};
33+
34+
public ToRadio CreateMqttClientProxyMessage(string topic, byte[] payload, bool retain = false) =>
35+
new()
36+
{
37+
MqttClientProxyMessage = new MqttClientProxyMessage()
38+
{
39+
Topic = topic,
40+
Data = ByteString.CopyFrom(payload),
41+
Retained = retain,
42+
}
43+
};
3244
}

Meshtastic/Generated/Deviceonly.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ public bool DidGpsReset {
335335
private readonly pbc::RepeatedField<global::Meshtastic.Protobufs.NodeInfoLite> nodeDbLite_ = new pbc::RepeatedField<global::Meshtastic.Protobufs.NodeInfoLite>();
336336
/// <summary>
337337
///
338-
/// New lite version of NodeDB to decrease
338+
/// New lite version of NodeDB to decrease memory footprint
339339
/// </summary>
340340
[global::System.Diagnostics.DebuggerNonUserCodeAttribute]
341341
[global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]

0 commit comments

Comments
 (0)