diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f0a4597..fbe82ba 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,16 +10,16 @@ jobs:
- name: Setup .NET
uses: actions/setup-dotnet@v1
with:
- dotnet-version: '7.0.x'
+ dotnet-version: '8.0.x'
- name: Clone Plugins
uses: actions/checkout@v2
- name: Build Plugin
- run: dotnet publish -c Release src/Artemis.Plugins.Mqtt.sln
+ run: dotnet publish -c Release src
- name: Upload
uses: actions/upload-artifact@v2
with:
name: Artemis.Plugins.Mqtt
- path: src/Artemis.Plugins.Mqtt/bin/x64/Release/net7.0/publish
+ path: src/Artemis.Plugins.Mqtt/bin/x64/Release/net8.0/publish
diff --git a/src/Artemis.Plugins.Mqtt.sln b/src/Artemis.Plugins.Mqtt.sln
index 4fa874a..49215b6 100644
--- a/src/Artemis.Plugins.Mqtt.sln
+++ b/src/Artemis.Plugins.Mqtt.sln
@@ -2,6 +2,11 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Artemis.Plugins.Mqtt", "Artemis.Plugins.Mqtt\Artemis.Plugins.Mqtt.csproj", "{C49EFF75-D1D3-486D-BA37-DF65481EA6EB}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BC333B86-D4D6-4D67-B713-293CAAAD0A1F}"
+ ProjectSection(SolutionItems) = preProject
+ ..\.github\workflows\build.yml = ..\.github\workflows\build.yml
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
diff --git a/src/Artemis.Plugins.Mqtt/Artemis.Plugins.Mqtt.csproj b/src/Artemis.Plugins.Mqtt/Artemis.Plugins.Mqtt.csproj
index 6074806..0b1c24c 100644
--- a/src/Artemis.Plugins.Mqtt/Artemis.Plugins.Mqtt.csproj
+++ b/src/Artemis.Plugins.Mqtt/Artemis.Plugins.Mqtt.csproj
@@ -3,10 +3,11 @@
net8.0
x64
true
+ enable
-
+
diff --git a/src/Artemis.Plugins.Mqtt/DataModels/MqttDataModel.cs b/src/Artemis.Plugins.Mqtt/DataModels/MqttDataModel.cs
index d5d32e3..e881cf0 100644
--- a/src/Artemis.Plugins.Mqtt/DataModels/MqttDataModel.cs
+++ b/src/Artemis.Plugins.Mqtt/DataModels/MqttDataModel.cs
@@ -8,4 +8,6 @@ public class MqttDataModel : DataModel
public StatusesDataModel Statuses { get; } = new();
public NodeDataModel Root { get; } = new(new());
+
+ public MqttServersDataModel Servers { get; } = new();
}
\ No newline at end of file
diff --git a/src/Artemis.Plugins.Mqtt/DataModels/MqttNodeDataModel.cs b/src/Artemis.Plugins.Mqtt/DataModels/MqttNodeDataModel.cs
new file mode 100644
index 0000000..c625eac
--- /dev/null
+++ b/src/Artemis.Plugins.Mqtt/DataModels/MqttNodeDataModel.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using Artemis.Core.Modules;
+using Swan;
+
+namespace Artemis.Plugins.Mqtt.DataModels;
+
+public class MqttNodeDataModel : DataModel
+{
+ public string Data { get; set; }
+
+ public MqttNodeDataModel()
+ {
+ Data = "";
+ }
+
+ public void PropagateValue(string[] topics, object data)
+ {
+ if (topics.Length == 0)
+ {
+ Data = data.ToString() ?? "";
+ return;
+ }
+
+ var key = topics[0];
+ var remainingPartialTopics = topics[1..];
+ if (!TryGetDynamicChild(key, out var child))
+ child = AddDynamicChild(key, new());
+
+ child.Value.PropagateValue(remainingPartialTopics, data);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Plugins.Mqtt/DataModels/MqttServersDataModel.cs b/src/Artemis.Plugins.Mqtt/DataModels/MqttServersDataModel.cs
new file mode 100644
index 0000000..201400a
--- /dev/null
+++ b/src/Artemis.Plugins.Mqtt/DataModels/MqttServersDataModel.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using Artemis.Core.Modules;
+
+namespace Artemis.Plugins.Mqtt.DataModels;
+
+public class MqttServersDataModel : DataModel
+{
+ private readonly Dictionary> _servers = new();
+
+ internal void CreateServers(IEnumerable servers)
+ {
+ ClearDynamicChildren();
+ _servers.Clear();
+
+ foreach (var server in servers)
+ {
+ var id = server.ServerId.ToString();
+ _servers.Add(
+ server.ServerId,
+ AddDynamicChild(id, new MqttNodeDataModel(), server.DisplayName)
+ );
+ }
+ }
+
+ public void PropagateValue(Guid sourceServer, string topic, object data)
+ {
+ if (!_servers.TryGetValue(sourceServer, out var server))
+ return;
+
+ var parts = topic.Split('/');
+ server.Value.PropagateValue(parts, data);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Plugins.Mqtt/DataModels/NodeDataModel.cs b/src/Artemis.Plugins.Mqtt/DataModels/NodeDataModel.cs
index 6c894ee..13fe6dd 100644
--- a/src/Artemis.Plugins.Mqtt/DataModels/NodeDataModel.cs
+++ b/src/Artemis.Plugins.Mqtt/DataModels/NodeDataModel.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using Artemis.Core.Modules;
-using Artemis.Plugins.Mqtt.DataModels.Dynamic;
namespace Artemis.Plugins.Mqtt.DataModels;
@@ -18,6 +17,9 @@ internal void CreateStructure(StructureDefinitionNode dataModelStructure)
ClearDynamicChildren();
_allDynamicChildren.Clear();
+ if (dataModelStructure.Children == null)
+ return;
+
foreach (var childDefinition in dataModelStructure.Children)
{
var id = GetNodeId(childDefinition.Server ?? Guid.NewGuid(), childDefinition.Topic);
@@ -67,7 +69,7 @@ public void PropagateValue(Guid sourceServer, string topic, object data)
boolChild.Value = string.Compare(data.ToString(), "true", StringComparison.OrdinalIgnoreCase) == 0;
return;
case DynamicChild stringChild:
- stringChild.Value = data.ToString();
+ stringChild.Value = data.ToString()!;
return;
}
}
diff --git a/src/Artemis.Plugins.Mqtt/DataModels/StructureDefinitionNode.cs b/src/Artemis.Plugins.Mqtt/DataModels/StructureDefinitionNode.cs
index 8e668a4..277651e 100644
--- a/src/Artemis.Plugins.Mqtt/DataModels/StructureDefinitionNode.cs
+++ b/src/Artemis.Plugins.Mqtt/DataModels/StructureDefinitionNode.cs
@@ -1,13 +1,23 @@
using System;
using System.Collections.Generic;
+using System.Text.Json.Serialization;
-namespace Artemis.Plugins.Mqtt.DataModels.Dynamic;
+namespace Artemis.Plugins.Mqtt.DataModels;
///
/// Class that defines a single node of the DataModel structure.
///
public class StructureDefinitionNode
{
+ public StructureDefinitionNode(string label, Guid? server, string topic, Type? type, bool isGroup)
+ {
+ Label = label;
+ Server = server;
+ Topic = topic;
+ AssemblyQualifiedTypeName = type?.AssemblyQualifiedName ?? "";
+ Children = isGroup ? new List() : null;
+ }
+
///
/// The display name this node will appear as in the Artemis Data Model.
///
@@ -22,23 +32,30 @@ public class StructureDefinitionNode
/// The topic that can be used to set the value of this node.
///
public string Topic { get; set; }
+
+ ///
+ /// The type of value stored in this node.
+ ///
+ public string AssemblyQualifiedTypeName { get; set; }
///
/// The type of value stored in this node.
///
- public Type Type { get; set; }
+ [JsonIgnore]
+ public Type? Type => Type.GetType(AssemblyQualifiedTypeName);
+
+ ///
+ /// Whether this node is a group or not.
+ ///
+ public bool IsGroup => Children != null;
///
/// Any children this node has.
///
- public List Children { get; set; }
+ public List? Children { get; set; }
///
/// Returns a default root .
///
- public static StructureDefinitionNode RootDefault => new()
- {
- Label = "Root",
- Children = new List()
- };
+ public static StructureDefinitionNode RootDefault => new("Root", null, "", typeof(object), true);
}
\ No newline at end of file
diff --git a/src/Artemis.Plugins.Mqtt/MqttConnector.cs b/src/Artemis.Plugins.Mqtt/MqttConnector.cs
index 0b3eeef..414d886 100644
--- a/src/Artemis.Plugins.Mqtt/MqttConnector.cs
+++ b/src/Artemis.Plugins.Mqtt/MqttConnector.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -13,41 +13,42 @@ namespace Artemis.Plugins.Mqtt;
///
public sealed class MqttConnector : IDisposable
{
- private static readonly MqttFactory clientFactory = new();
- private readonly IManagedMqttClient client;
+ private static readonly MqttFactory ClientFactory = new();
+ private readonly IManagedMqttClient _client;
public MqttConnector()
{
- client = clientFactory.CreateManagedMqttClient();
- client.ApplicationMessageReceivedAsync += OnClientMessageReceived;
- client.ConnectedAsync += OnClientConnected;
- client.DisconnectedAsync += OnClientDisconnected;
+ _client = ClientFactory.CreateManagedMqttClient();
+ _client.ApplicationMessageReceivedAsync += OnClientMessageReceived;
+ _client.ConnectedAsync += OnClientConnected;
+ _client.DisconnectedAsync += OnClientDisconnected;
}
///
/// The ID of the server this connector is connected to.
///
- /// Based on the 'ServerId' property from the settings object passed to .
+ /// Based on the 'ServerId' property from the settings object passed to .
///
public Guid ServerId { get; private set; }
///
/// Whether or not this connector is currently connected to a server.
///
+ // ReSharper disable once UnusedAutoPropertyAccessor.Global
public bool IsConnected { get; private set; }
public void Dispose()
{
- client.Dispose();
+ _client.Dispose();
}
///
/// Event that fires when this connector receives a message from the server it is connected to.
///
- public event EventHandler MessageReceived;
+ public event EventHandler? MessageReceived;
- public event EventHandler Connected;
- public event EventHandler Disconnected;
+ public event EventHandler? Connected;
+ public event EventHandler? Disconnected;
///
/// Sets up and starts listening with the MQTT client behind this connector.
@@ -75,11 +76,12 @@ public async Task Start(MqttConnectionSettings settings, IEnumerable top
.WithClientOptions(clientOptions.Build())
.Build();
- await client.StopAsync();
- await client.StartAsync(managedClientOptions);
+ await _client.StopAsync();
+ await _client.StartAsync(managedClientOptions);
await Task.WhenAll(
- topics.Select(topic => client.SubscribeAsync(topic))
+ topics.Where(t => !string.IsNullOrWhiteSpace(t)).Select(topic => _client.SubscribeAsync(topic))
);
+ await _client.SubscribeAsync("#");
}
///
@@ -87,7 +89,7 @@ await Task.WhenAll(
///
public Task Stop()
{
- return client.StopAsync();
+ return _client.StopAsync();
}
private Task OnClientMessageReceived(MqttApplicationMessageReceivedEventArgs e)
diff --git a/src/Artemis.Plugins.Mqtt/MqttModule.cs b/src/Artemis.Plugins.Mqtt/MqttModule.cs
index acc9df4..c5b8ce6 100644
--- a/src/Artemis.Plugins.Mqtt/MqttModule.cs
+++ b/src/Artemis.Plugins.Mqtt/MqttModule.cs
@@ -6,7 +6,6 @@
using Artemis.Core;
using Artemis.Core.Modules;
using Artemis.Plugins.Mqtt.DataModels;
-using Artemis.Plugins.Mqtt.DataModels.Dynamic;
using MQTTnet;
using MQTTnet.Client;
using Serilog;
@@ -15,28 +14,28 @@ namespace Artemis.Plugins.Mqtt;
public class MqttModule : Module
{
- private readonly List connectors = new();
- private readonly PluginSetting dynamicDataModelStructureSetting;
-
- private readonly PluginSetting> serverConnectionsSetting;
+ private readonly List _connectors = new();
+ private readonly PluginSetting _dynamicDataModelStructureSetting;
+ private readonly PluginSetting> _serverConnectionsSetting;
private readonly ILogger _logger;
public MqttModule(PluginSettings settings, ILogger logger)
{
_logger = logger;
- serverConnectionsSetting = settings.GetSetting("ServerConnections", new List());
- serverConnectionsSetting.PropertyChanged += OnSeverConnectionListChanged;
+ _serverConnectionsSetting = settings.GetSetting("ServerConnections", new List());
+ _serverConnectionsSetting.PropertyChanged += OnSeverConnectionListChanged;
- dynamicDataModelStructureSetting = settings.GetSetting("DynamicDataModelStructure", StructureDefinitionNode.RootDefault);
- dynamicDataModelStructureSetting.PropertyChanged += OnDataModelStructureChanged;
+ _dynamicDataModelStructureSetting = settings.GetSetting("DynamicDataModelStructure", StructureDefinitionNode.RootDefault);
+ _dynamicDataModelStructureSetting.PropertyChanged += OnDataModelStructureChanged;
}
public override List ActivationRequirements { get; } = new();
public override async void Enable()
{
- DataModel.Root.CreateStructure(dynamicDataModelStructureSetting.Value);
- DataModel.Statuses.UpdateConnectorList(serverConnectionsSetting.Value);
+ DataModel.Root.CreateStructure(_dynamicDataModelStructureSetting.Value!);
+ DataModel.Statuses.UpdateConnectorList(_serverConnectionsSetting.Value!);
+ DataModel.Servers.CreateServers(_serverConnectionsSetting.Value!);
await RestartConnectors();
}
@@ -54,10 +53,10 @@ private Task RestartConnectors()
{
// Resize connectors to match number of setup servers
// - Remove extraneous connectors if there are more connectors than there are server connections
- if (connectors.Count > serverConnectionsSetting.Value.Count)
+ if (_connectors.Count > _serverConnectionsSetting.Value!.Count)
{
- var amountToRemove = connectors.Count - serverConnectionsSetting.Value.Count;
- foreach (var connector in connectors.Take(amountToRemove))
+ var amountToRemove = _connectors.Count - _serverConnectionsSetting.Value.Count;
+ foreach (var connector in _connectors.Take(amountToRemove))
{
connector.MessageReceived -= OnMqttClientMessageReceived;
connector.Connected -= OnMqttClientConnected;
@@ -65,28 +64,28 @@ private Task RestartConnectors()
connector.Dispose();
}
- connectors.RemoveRange(0, amountToRemove);
+ _connectors.RemoveRange(0, amountToRemove);
}
// - Add new connectors if there are less connectors than there are server connections
- else if (connectors.Count < serverConnectionsSetting.Value.Count)
+ else if (_connectors.Count < _serverConnectionsSetting.Value.Count)
{
- for (var i = connectors.Count; i < serverConnectionsSetting.Value.Count; i++)
+ for (var i = _connectors.Count; i < _serverConnectionsSetting.Value.Count; i++)
{
var connector = new MqttConnector();
connector.MessageReceived += OnMqttClientMessageReceived;
connector.Connected += OnMqttClientConnected;
connector.Disconnected += OnMqttClientDisconnected;
- connectors.Add(connector);
+ _connectors.Add(connector);
}
}
// Calculate which topics should be listened to by which servers
- var serverTopicMap = new Dictionary>();
+ var serverTopicMap = new Dictionary>();
var nodesToSearch = new Queue();
- nodesToSearch.Enqueue(dynamicDataModelStructureSetting.Value);
+ nodesToSearch.Enqueue(_dynamicDataModelStructureSetting.Value!);
- foreach (var server in serverConnectionsSetting.Value)
+ foreach (var server in _serverConnectionsSetting.Value)
serverTopicMap.Add(server.ServerId, new HashSet());
// - Not implemented as a recursive function because it then becomes a lot of hassle to merge dictionaries.
@@ -94,10 +93,14 @@ private Task RestartConnectors()
if (current.Children == null)
{
if (current.Server == null) // If null, listens to any server
- foreach (var server in serverConnectionsSetting.Value)
+ {
+ foreach (var server in _serverConnectionsSetting.Value)
serverTopicMap[server.ServerId].Add(current.Topic);
+ }
else
- serverTopicMap[current.Server].Add(current.Topic);
+ {
+ serverTopicMap[(Guid)current.Server].Add(current.Topic);
+ }
}
else
{
@@ -108,28 +111,29 @@ private Task RestartConnectors()
// Start each connector with relevant settings
return Task.WhenAll(
- connectors.Select((connector, i) =>
- connector.Start(serverConnectionsSetting.Value[i], serverTopicMap[serverConnectionsSetting.Value[i].ServerId]))
+ _connectors.Select((connector, i) =>
+ connector.Start(_serverConnectionsSetting.Value[i], serverTopicMap[_serverConnectionsSetting.Value[i].ServerId]))
);
}
private Task StopConnectors()
{
return Task.WhenAll(
- connectors.Select(connector => connector.Stop())
+ _connectors.Select(connector => connector.Stop())
);
}
- private void OnMqttClientMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
+ private void OnMqttClientMessageReceived(object? sender, MqttApplicationMessageReceivedEventArgs e)
{
if (sender is not MqttConnector connector)
return;
_logger.Debug("Received message on connector {Connector} for topic {Topic}: {Message}", connector.ServerId,e.ApplicationMessage.Topic, e.ApplicationMessage.ConvertPayloadToString());
DataModel.Root.PropagateValue(connector.ServerId, e.ApplicationMessage.Topic, e.ApplicationMessage.ConvertPayloadToString());
+ DataModel.Servers.PropagateValue(connector.ServerId, e.ApplicationMessage.Topic, e.ApplicationMessage.ConvertPayloadToString());
}
- private void OnMqttClientConnected(object sender, MqttClientConnectedEventArgs e)
+ private void OnMqttClientConnected(object? sender, MqttClientConnectedEventArgs e)
{
if (sender is not MqttConnector connector)
return;
@@ -137,7 +141,7 @@ private void OnMqttClientConnected(object sender, MqttClientConnectedEventArgs e
DataModel.Statuses[connector.ServerId].IsConnected = true;
}
- private void OnMqttClientDisconnected(object sender, MqttClientDisconnectedEventArgs e)
+ private void OnMqttClientDisconnected(object? sender, MqttClientDisconnectedEventArgs e)
{
if (sender is not MqttConnector connector)
return;
@@ -145,16 +149,17 @@ private void OnMqttClientDisconnected(object sender, MqttClientDisconnectedEvent
DataModel.Statuses[connector.ServerId].IsConnected = false;
}
- private void OnSeverConnectionListChanged(object sender, PropertyChangedEventArgs e)
+ private void OnSeverConnectionListChanged(object? sender, PropertyChangedEventArgs e)
{
RestartConnectors();
- DataModel.Statuses.UpdateConnectorList(serverConnectionsSetting.Value);
+ DataModel.Statuses.UpdateConnectorList(_serverConnectionsSetting.Value!);
+ DataModel.Servers.CreateServers(_serverConnectionsSetting.Value!);
}
- private async void OnDataModelStructureChanged(object sender, PropertyChangedEventArgs e)
+ private async void OnDataModelStructureChanged(object? sender, PropertyChangedEventArgs e)
{
// Rebuild the Artemis Data Model with the new structure
- DataModel.Root.CreateStructure(dynamicDataModelStructureSetting.Value);
+ DataModel.Root.CreateStructure(_dynamicDataModelStructureSetting.Value!);
// Restart the Mqtt client in case it needs to change which topics it's subscribed to
await RestartConnectors();
@@ -162,7 +167,7 @@ private async void OnDataModelStructureChanged(object sender, PropertyChangedEve
protected override void Dispose(bool disposing)
{
- foreach (var connector in connectors)
+ foreach (var connector in _connectors)
{
connector.MessageReceived -= OnMqttClientMessageReceived;
connector.Connected -= OnMqttClientConnected;
@@ -170,6 +175,6 @@ protected override void Dispose(bool disposing)
connector.Dispose();
}
- connectors.Clear();
+ _connectors.Clear();
}
}
\ No newline at end of file
diff --git a/src/Artemis.Plugins.Mqtt/MqttPluginBootstrapper.cs b/src/Artemis.Plugins.Mqtt/MqttPluginBootstrapper.cs
index 4bbb3f4..9d6b5d5 100644
--- a/src/Artemis.Plugins.Mqtt/MqttPluginBootstrapper.cs
+++ b/src/Artemis.Plugins.Mqtt/MqttPluginBootstrapper.cs
@@ -1,5 +1,5 @@
using Artemis.Core;
-using Artemis.Plugins.Mqtt.Screens;
+using Artemis.Plugins.Mqtt.ViewModels;
using Artemis.UI.Shared;
namespace Artemis.Plugins.Mqtt;
diff --git a/src/Artemis.Plugins.Mqtt/ViewModels/MqttPluginConfigurationViewModel.cs b/src/Artemis.Plugins.Mqtt/ViewModels/MqttPluginConfigurationViewModel.cs
index bc430b7..69b46f9 100644
--- a/src/Artemis.Plugins.Mqtt/ViewModels/MqttPluginConfigurationViewModel.cs
+++ b/src/Artemis.Plugins.Mqtt/ViewModels/MqttPluginConfigurationViewModel.cs
@@ -1,15 +1,15 @@
-using System.Collections.Generic;
+using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Artemis.Core;
-using Artemis.Plugins.Mqtt.DataModels.Dynamic;
+using Artemis.Plugins.Mqtt.DataModels;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.Builders;
using ReactiveUI;
-namespace Artemis.Plugins.Mqtt.Screens;
+namespace Artemis.Plugins.Mqtt.ViewModels;
///
/// ViewModel for the main MQTT plugin configuration view.
@@ -26,10 +26,10 @@ public MqttPluginConfigurationViewModel(Plugin plugin, PluginSettings settings,
_windowService = windowService;
_serverConnectionsSetting = settings.GetSetting("ServerConnections", new List());
- ServerConnections = new ObservableCollection(_serverConnectionsSetting.Value);
+ ServerConnections = new ObservableCollection(_serverConnectionsSetting.Value!);
_dynamicDataModelStructureSetting = settings.GetSetting("DynamicDataModelStructure", StructureDefinitionNode.RootDefault);
- DynamicDataModelStructureRoot = new StructureNodeViewModel(windowService, null, _dynamicDataModelStructureSetting.Value);
+ DynamicDataModelStructureRoot = new StructureNodeViewModel(windowService, null, _dynamicDataModelStructureSetting.Value!);
AddServerConnection = ReactiveCommand.Create(ExecuteAddServerConnection);
EditServerConnection = ReactiveCommand.Create(ExecuteEditServerConnection);
diff --git a/src/Artemis.Plugins.Mqtt/ViewModels/ServerConnectionDialogViewModel.cs b/src/Artemis.Plugins.Mqtt/ViewModels/ServerConnectionDialogViewModel.cs
index 01b1dec..c8368f4 100644
--- a/src/Artemis.Plugins.Mqtt/ViewModels/ServerConnectionDialogViewModel.cs
+++ b/src/Artemis.Plugins.Mqtt/ViewModels/ServerConnectionDialogViewModel.cs
@@ -3,7 +3,7 @@
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
-namespace Artemis.Plugins.Mqtt.Screens;
+namespace Artemis.Plugins.Mqtt.ViewModels;
public class ServerConnectionDialogViewModel : DialogViewModelBase
{
diff --git a/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeConfigurationDialogViewModel.cs b/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeConfigurationDialogViewModel.cs
index 24b8a33..6a3a345 100644
--- a/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeConfigurationDialogViewModel.cs
+++ b/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeConfigurationDialogViewModel.cs
@@ -1,53 +1,39 @@
using System;
using System.Collections.Generic;
using System.Reactive;
-using System.Threading.Tasks;
using Artemis.Core;
+using Artemis.Plugins.Mqtt.DataModels;
using Artemis.UI.Shared;
using ReactiveUI;
using ReactiveUI.Validation.Extensions;
-namespace Artemis.Plugins.Mqtt.Screens;
+namespace Artemis.Plugins.Mqtt.ViewModels;
///
/// ViewModel for single node edit dialog.
///
-public class StructureNodeConfigurationDialogViewModel : DialogViewModelBase
+public class StructureNodeConfigurationDialogViewModel : DialogViewModelBase
{
- private static readonly Type[] supportedTypes = { typeof(string), typeof(bool), typeof(int), typeof(double) };
+ private string _label;
+ private Guid? _server;
+ private string _topic;
+ private Type _type;
- private string label;
- private Guid? server;
- private string topic;
- private Type type;
-
- public StructureNodeConfigurationDialogViewModel(PluginSettings settingsService, bool isGroup): this()
+ public StructureNodeConfigurationDialogViewModel(PluginSettings pluginSettings, StructureNodeViewModel poco)
{
- label = "";
- server = Guid.Empty;
- topic = isGroup ? null : "";
- type = isGroup ? null : supportedTypes[0];
- IsGroup = isGroup;
- ServerConnectionsSetting = settingsService.GetSetting>("ServerConnections");
- }
+ _label = poco.Label;
+ _server = poco.Server;
+ _topic = poco.Topic;
+ _type = poco.Type;
+ IsGroup = poco.IsGroup;
+ ServerConnectionsSetting = pluginSettings.GetSetting>("ServerConnections");
- public StructureNodeConfigurationDialogViewModel(PluginSettings settingsService, StructureNodeViewModel target) : this()
- {
- label = target.Label;
- server = target.Server;
- topic = target.Topic;
- type = target.Type;
- IsGroup = target.IsGroup;
- ServerConnectionsSetting = settingsService.GetSetting>("ServerConnections");
- }
-
- private StructureNodeConfigurationDialogViewModel()
- {
Save = ReactiveCommand.Create(ExecuteSave);
+ Cancel = ReactiveCommand.Create(ExecuteCancel);
this.ValidationRule(vm => vm.Label, label => !string.IsNullOrWhiteSpace(label), "Label cannot be empty");
this.ValidationRule(vm => vm.Server, server => server != null && server != Guid.Empty, "Server cannot be empty");
this.ValidationRule(vm => vm.Topic, topic => !string.IsNullOrWhiteSpace(topic), "Topic cannot be empty");
-
+
if (!IsGroup)
{
this.ValidationRule(vm => vm.Type, type => type != null, "Type cannot be empty");
@@ -55,47 +41,48 @@ private StructureNodeConfigurationDialogViewModel()
}
public ReactiveCommand Save { get; }
-
+
public ReactiveCommand Cancel { get; }
public string Label
{
- get => label;
- set => RaiseAndSetIfChanged(ref label, value);
+ get => _label;
+ set => RaiseAndSetIfChanged(ref _label, value);
}
public Guid? Server
{
- get => server;
- set => RaiseAndSetIfChanged(ref server, value);
+ get => _server;
+ set => RaiseAndSetIfChanged(ref _server, value);
}
public string Topic
{
- get => topic;
- set => RaiseAndSetIfChanged(ref topic, value);
+ get => _topic;
+ set => RaiseAndSetIfChanged(ref _topic, value);
}
public Type Type
{
- get => type;
- set => RaiseAndSetIfChanged(ref type, value);
+ get => _type;
+ set => RaiseAndSetIfChanged(ref _type, value);
}
-
+
public bool IsGroup { get; }
public bool IsValue => !IsGroup;
- public IEnumerable SupportedValueTypes => supportedTypes;
+ public IEnumerable SupportedValueTypes { get; } = new[] { typeof(string), typeof(bool), typeof(int), typeof(double) };
+
public PluginSetting> ServerConnectionsSetting { get; }
public void ExecuteSave()
{
if (!HasErrors)
- Close(new StructureNodeConfigurationDialogResult(Label, Server, Topic, Type));
+ Close(new(Label, Server, Topic, Type, IsGroup));
}
-}
-///
-/// POCO that contains the result of a successful MqttNodeConfiguration dialog.
-///
-public record StructureNodeConfigurationDialogResult(string Label, Guid? Server, string Topic, Type Type);
\ No newline at end of file
+ public void ExecuteCancel()
+ {
+ Close(null);
+ }
+}
\ No newline at end of file
diff --git a/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeViewModel.cs b/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeViewModel.cs
index 826f936..9d1c86f 100644
--- a/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeViewModel.cs
+++ b/src/Artemis.Plugins.Mqtt/ViewModels/StructureNodeViewModel.cs
@@ -3,96 +3,77 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
-using Artemis.Plugins.Mqtt.DataModels.Dynamic;
+using Artemis.Plugins.Mqtt.DataModels;
using Artemis.UI.Shared;
using Artemis.UI.Shared.Services;
-namespace Artemis.Plugins.Mqtt.Screens;
+namespace Artemis.Plugins.Mqtt.ViewModels;
-///
-/// ViewModel representing a model.
-///
public class StructureNodeViewModel : ViewModelBase
{
private readonly IWindowService _windowService;
- private readonly StructureNodeViewModel parent;
-
- private string label;
- private Guid? server;
- private string topic;
- private Type type;
-
- ///
- /// Creates a new, blank ViewModel that represents a non-materialized .
- ///
- private StructureNodeViewModel(IWindowService windowService, StructureNodeViewModel parent)
+ private readonly StructureNodeViewModel? _parent;
+
+ private string _label;
+ private Guid? _server;
+ private string _topic;
+ private Type _type;
+
+ public StructureNodeViewModel(IWindowService windowService, StructureNodeViewModel? parent, StructureDefinitionNode model)
{
- this._windowService = windowService;
- this.parent = parent;
+ _windowService = windowService;
+ _parent = parent;
+
+ _label = model.Label;
+ _server = model.Server;
+ _topic = model.Topic;
+ _type = model.Type;
+
+ Children = model.IsGroup
+ ? new ObservableCollection(model.Children!.Select(c => new StructureNodeViewModel(windowService, this, c)))
+ : new ObservableCollection();
}
- ///
- /// Creates a new ViewModel that represents the given .
- ///
- public StructureNodeViewModel(IWindowService windowService, StructureNodeViewModel parent, StructureDefinitionNode model) : this(windowService,
- parent)
- {
- label = model.Label;
- server = model.Server;
- topic = model.Topic;
- type = model.Type;
- if (model.Children != null)
- Children = new ObservableCollection(
- model.Children.Select(c => new StructureNodeViewModel(windowService, this, c))
- );
- }
-
- ///
- /// Converts this ViewModel into a model that can be saved, and used
- /// by the .
- ///
public StructureDefinitionNode ViewModelToModel()
{
- return new StructureDefinitionNode()
- {
- Label = label,
- Server = server,
- Topic = topic,
- Type = type,
- Children = IsGroup ? new List(Children.Select(c => c.ViewModelToModel())) : null
- };
+ var node = new StructureDefinitionNode(Label, Server, Topic, Type, IsGroup);
+
+ if (Children.Any())
+ node.Children!.AddRange(Children.Select(c => c.ViewModelToModel()));
+
+ return node;
}
#region Properties
public string Label
{
- get => label;
- set => RaiseAndSetIfChanged(ref label, value);
+ get => _label;
+ set => RaiseAndSetIfChanged(ref _label, value);
}
public Guid? Server
{
- get => server;
- set => RaiseAndSetIfChanged(ref server, value);
+ get => _server;
+ set => RaiseAndSetIfChanged(ref _server, value);
}
public string Topic
{
- get => topic;
- set => RaiseAndSetIfChanged(ref topic, value);
+ get => _topic;
+ set => RaiseAndSetIfChanged(ref _topic, value);
}
public Type Type
{
- get => type;
- set => RaiseAndSetIfChanged(ref type, value);
+ get => _type;
+ set => RaiseAndSetIfChanged(ref _type, value);
}
public ObservableCollection Children { get; init; }
- public bool IsGroup => Children != null;
- public bool IsValue => Children == null;
+ public bool IsGroup => Children.Any();
+ public bool IsValue => !IsGroup;
#endregion
@@ -103,7 +84,10 @@ public Type Type
///
public async Task EditNode()
{
- var r = await _windowService.ShowDialogAsync(this);
+ var r = await _windowService.ShowDialogAsync(this);
+ if (r == null)
+ return;
+
Label = r.Label;
Server = r.Server;
Topic = r.Topic;
@@ -115,9 +99,14 @@ public async Task EditNode()
///
public async Task DeleteNode()
{
- // If Children is null or does not have this child, throw an error
- if (parent.Children?.Contains(this) != true)
- throw new InvalidOperationException("This node does not support child or child does not exist in this node.");
+ if(_parent == null)
+ throw new InvalidOperationException("Cannot delete root node.");
+
+ if (!_parent.IsGroup)
+ throw new InvalidOperationException("Cannot delete child node from node that does not support children.");
+
+ if (!_parent.Children.Contains(this))
+ throw new InvalidOperationException("Cannot delete node that is not a child of this node.");
var result = await _windowService.ShowConfirmContentDialog(
$"Delete {(IsGroup ? "Group" : "Value")}",
@@ -128,7 +117,7 @@ public async Task DeleteNode()
"Don't delete"
);
if (result)
- parent.Children.Remove(this);
+ _parent.Children.Remove(this);
}
///
@@ -138,18 +127,18 @@ public async Task DeleteNode()
/// If this node is a value-type node that does not support children.
public async Task AddChildNode(bool isGroup)
{
- if (IsValue)
+ if (!IsGroup)
throw new InvalidOperationException("Cannot add a child item to an item that does not support children.");
- var r = await _windowService.ShowDialogAsync(isGroup);
- Children.Add(new StructureNodeViewModel(_windowService, this)
- {
- Label = r.Label,
- Server = isGroup ? null : r.Server,
- Topic = isGroup ? null : r.Topic,
- Type = isGroup ? null : r.Type,
- Children = isGroup ? new ObservableCollection() : null
- });
+ var child = new StructureDefinitionNode("", Guid.Empty, "", typeof(string), isGroup);
+ var childVm = new StructureNodeViewModel(_windowService, this, child);
+
+ var dialogResult = await _windowService.ShowDialogAsync(childVm);
+
+ if (dialogResult is null)
+ return;
+
+ Children.Add(childVm);
}
#endregion
diff --git a/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml b/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml
index eab3a60..b565a6d 100644
--- a/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml
+++ b/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml
@@ -4,13 +4,13 @@
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=netstandard"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
- xmlns:scr="clr-namespace:Artemis.Plugins.Mqtt.Screens"
xmlns:controls="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
+ xmlns:viewModels="clr-namespace:Artemis.Plugins.Mqtt.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="600"
- x:Class="Artemis.Plugins.Mqtt.Screens.MqttPluginConfigurationView"
- x:DataType="scr:MqttPluginConfigurationViewModel">
+ x:Class="Artemis.Plugins.Mqtt.Views.MqttPluginConfigurationView"
+ x:DataType="viewModels:MqttPluginConfigurationViewModel">
True
False
@@ -77,7 +77,7 @@
Margin="0,8">
diff --git a/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml.cs b/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml.cs
index 6cb9daf..2fb41d5 100644
--- a/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml.cs
+++ b/src/Artemis.Plugins.Mqtt/Views/MqttPluginConfigurationView.axaml.cs
@@ -1,6 +1,7 @@
-using Avalonia.ReactiveUI;
+using Artemis.Plugins.Mqtt.ViewModels;
+using Avalonia.ReactiveUI;
-namespace Artemis.Plugins.Mqtt.Screens;
+namespace Artemis.Plugins.Mqtt.Views;
public partial class MqttPluginConfigurationView : ReactiveUserControl
{
diff --git a/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml b/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml
index e0fcc5f..8b612ad 100644
--- a/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml
+++ b/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml
@@ -2,10 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:Artemis.Plugins.Mqtt.Screens"
+ xmlns:viewModels="clr-namespace:Artemis.Plugins.Mqtt.ViewModels"
mc:Ignorable="d" Width="450"
- x:Class="Artemis.Plugins.Mqtt.Screens.ServerConnectionDialogView"
- x:DataType="local:ServerConnectionDialogViewModel">
+ x:Class="Artemis.Plugins.Mqtt.Views.ServerConnectionDialogView"
+ x:DataType="viewModels:ServerConnectionDialogViewModel">
diff --git a/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml.cs b/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml.cs
index 13e82c7..765c9c2 100644
--- a/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml.cs
+++ b/src/Artemis.Plugins.Mqtt/Views/ServerConnectionDialogView.axaml.cs
@@ -1,6 +1,7 @@
-using Artemis.UI.Shared;
+using Artemis.Plugins.Mqtt.ViewModels;
+using Artemis.UI.Shared;
-namespace Artemis.Plugins.Mqtt.Screens;
+namespace Artemis.Plugins.Mqtt.Views;
public partial class ServerConnectionDialogView : ReactiveAppWindow
{
diff --git a/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml b/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml
index 26e8414..315b5b0 100644
--- a/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml
+++ b/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml
@@ -4,9 +4,9 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:converters="clr-namespace:Artemis.UI.Shared.Converters;assembly=Artemis.UI.Shared"
- xmlns:local="clr-namespace:Artemis.Plugins.Mqtt.Screens"
- x:Class="Artemis.Plugins.Mqtt.Screens.StructureNodeConfigurationDialogView"
- x:DataType="local:StructureNodeConfigurationDialogViewModel"
+ xmlns:viewModels="clr-namespace:Artemis.Plugins.Mqtt.ViewModels"
+ x:Class="Artemis.Plugins.Mqtt.Views.StructureNodeConfigurationDialogView"
+ x:DataType="viewModels:StructureNodeConfigurationDialogViewModel"
mc:Ignorable="d" Width="450">
diff --git a/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml.cs b/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml.cs
index a945d34..a2eb121 100644
--- a/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml.cs
+++ b/src/Artemis.Plugins.Mqtt/Views/StructureNodeConfigurationDialogView.axaml.cs
@@ -1,7 +1,7 @@
-using Artemis.UI.Shared;
-using Avalonia.ReactiveUI;
+using Artemis.Plugins.Mqtt.ViewModels;
+using Artemis.UI.Shared;
-namespace Artemis.Plugins.Mqtt.Screens;
+namespace Artemis.Plugins.Mqtt.Views;
public partial class StructureNodeConfigurationDialogView : ReactiveAppWindow
{
diff --git a/src/Artemis.Plugins.Mqtt/plugin.json b/src/Artemis.Plugins.Mqtt/plugin.json
index 1325e72..327dbfd 100644
--- a/src/Artemis.Plugins.Mqtt/plugin.json
+++ b/src/Artemis.Plugins.Mqtt/plugin.json
@@ -5,6 +5,6 @@
"Author": "Wibble199 & diogotr7",
"Icon": "AccessPoint",
"Description": "Provides a customisable data model based on data received by subscribing to an MQTT broker.",
- "Version": "1.0.0.0",
+ "Version": "1.1.0.0",
"Main": "Artemis.Plugins.Mqtt.dll"
}
\ No newline at end of file