diff --git a/Directory.Build.props b/Directory.Build.props
index 4978ab48..67aea515 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -54,10 +54,10 @@
-
+
-
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 122988b1..20e647fd 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ Atc.Kepware is a .NET library and CLI tool for configuring Kepware servers via R
The library provides comprehensive Kepware server management capabilities:
-- ✅ **Channel Management**: Create, retrieve, and delete channels for various drivers (EuroMap63, OPC UA Client, Simulator)
+- ✅ **Channel Management**: Create, retrieve, and delete channels for 70+ [supported drivers](#supported-drivers)
- ✅ **Device Management**: Configure devices under channels with driver-specific settings
- ✅ **Tag Management**: Create tags and tag groups with hierarchical structures, search for tags with wildcards
- ✅ **IoT Gateway**: Manage MQTT and REST client/server agents and their associated items
@@ -397,6 +397,14 @@ The library supports a comprehensive range of Kepware drivers organized by manuf
| Wago Ethernet | Wago controllers |
| Yaskawa MP Series Ethernet | MP series motion controllers |
+### Flow Computers / Metering
+| Driver | Description |
+|--------|-------------|
+| ABB Totalflow | ABB Totalflow flow computers |
+| Fisher ROC Ethernet | Fisher ROC flow computers over Ethernet |
+| Fisher ROC Plus Ethernet | Fisher ROC Plus flow computers over Ethernet |
+| Omni Flow Computer | Omni Flow Computer metering |
+
### CNC / Motion
| Driver | Description |
|--------|-------------|
@@ -444,11 +452,11 @@ dotnet tool update --global atc-kepware-configuration
## Commands
-The CLI is organized into two main command groups:
+The CLI is organized into two main command groups. The connectivity group includes subcommands for channels, devices, tags, and meters:
### Connectivity Commands
-Manage channels, devices, and tags:
+Manage channels, devices, tags, and meters:
```bash
# List all channels
@@ -477,6 +485,19 @@ atc-kepware-configuration connectivity tags create tag -s \
--address R0001 \
--data-type Word \
--scan-rate 1000
+
+# Get meters for a flow computer meter group
+atc-kepware-configuration connectivity meters get abbtotalflow -s \
+ --channel-name MyChannel \
+ --device-name MyDevice \
+ --meter-group-name MyMeterGroup
+
+# Create a meter
+atc-kepware-configuration connectivity meters create fisherrocethernet -s \
+ --channel-name MyChannel \
+ --device-name MyDevice \
+ --meter-group-name MyMeterGroup \
+ --name MyMeter
```
### IoT Gateway Commands
diff --git a/sample/Atc.Kepware.Sample/Atc.Kepware.Sample.csproj b/sample/Atc.Kepware.Sample/Atc.Kepware.Sample.csproj
index d6df31e0..07949c14 100644
--- a/sample/Atc.Kepware.Sample/Atc.Kepware.Sample.csproj
+++ b/sample/Atc.Kepware.Sample/Atc.Kepware.Sample.csproj
@@ -7,8 +7,8 @@
-
-
+
+
diff --git a/src/Atc.Kepware.Configuration.CLI/Atc.Kepware.Configuration.CLI.csproj b/src/Atc.Kepware.Configuration.CLI/Atc.Kepware.Configuration.CLI.csproj
index c4bae84f..7dcd48bd 100644
--- a/src/Atc.Kepware.Configuration.CLI/Atc.Kepware.Configuration.CLI.csproj
+++ b/src/Atc.Kepware.Configuration.CLI/Atc.Kepware.Configuration.CLI.csproj
@@ -17,12 +17,12 @@
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateAbbTotalflowCommand.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateAbbTotalflowCommand.cs
new file mode 100644
index 00000000..2fc9c822
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateAbbTotalflowCommand.cs
@@ -0,0 +1,73 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Create;
+
+public sealed class MeterCreateAbbTotalflowCommand : AsyncCommand
+{
+ private readonly ILogger logger;
+ private readonly IKepwareConfigurationClient kepwareConfigurationClient;
+
+ public MeterCreateAbbTotalflowCommand(
+ ILoggerFactory loggerFactory,
+ IKepwareConfigurationClient kepwareConfigurationClient)
+ {
+ logger = loggerFactory.CreateLogger();
+ this.kepwareConfigurationClient = kepwareConfigurationClient;
+ }
+
+ public override Task ExecuteAsync(
+ CommandContext context,
+ MeterCreateCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return ExecuteInternalAsync(settings, cancellationToken);
+ }
+
+ private async Task ExecuteInternalAsync(
+ MeterCreateCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ConsoleHelper.WriteHeader();
+
+ try
+ {
+ kepwareConfigurationClient.SetConnectionInformation(
+ new Uri(settings.ServerUrl),
+ settings.UserName!.Value,
+ settings.Password!.Value);
+
+ var request = BuildAbbTotalflowMeterRequest(settings);
+ var result = await kepwareConfigurationClient.CreateAbbTotalflowMeter(
+ request,
+ settings.ChannelName,
+ settings.DeviceName,
+ settings.MeterGroupName,
+ cancellationToken);
+
+ if (!result.CommunicationSucceeded ||
+ result.StatusCode is not (HttpStatusCode.OK or HttpStatusCode.Created))
+ {
+ return ConsoleExitStatusCodes.Failure;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"{EmojisConstants.Error} {ex.GetMessage()}");
+ return ConsoleExitStatusCodes.Failure;
+ }
+
+ logger.LogInformation($"{EmojisConstants.Success} Done");
+ return ConsoleExitStatusCodes.Success;
+ }
+
+ private static AbbTotalflowMeterRequest BuildAbbTotalflowMeterRequest(
+ MeterCreateCommandBaseSettings settings)
+ => new()
+ {
+ Name = settings.Name,
+ Description = settings.Description is not null && settings.Description.IsSet
+ ? settings.Description.Value
+ : string.Empty,
+ };
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateFisherRocEthernetCommand.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateFisherRocEthernetCommand.cs
new file mode 100644
index 00000000..73452e27
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateFisherRocEthernetCommand.cs
@@ -0,0 +1,73 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Create;
+
+public sealed class MeterCreateFisherRocEthernetCommand : AsyncCommand
+{
+ private readonly ILogger logger;
+ private readonly IKepwareConfigurationClient kepwareConfigurationClient;
+
+ public MeterCreateFisherRocEthernetCommand(
+ ILoggerFactory loggerFactory,
+ IKepwareConfigurationClient kepwareConfigurationClient)
+ {
+ logger = loggerFactory.CreateLogger();
+ this.kepwareConfigurationClient = kepwareConfigurationClient;
+ }
+
+ public override Task ExecuteAsync(
+ CommandContext context,
+ MeterCreateCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return ExecuteInternalAsync(settings, cancellationToken);
+ }
+
+ private async Task ExecuteInternalAsync(
+ MeterCreateCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ConsoleHelper.WriteHeader();
+
+ try
+ {
+ kepwareConfigurationClient.SetConnectionInformation(
+ new Uri(settings.ServerUrl),
+ settings.UserName!.Value,
+ settings.Password!.Value);
+
+ var request = BuildFisherRocEthernetMeterRequest(settings);
+ var result = await kepwareConfigurationClient.CreateFisherRocEthernetMeter(
+ request,
+ settings.ChannelName,
+ settings.DeviceName,
+ settings.MeterGroupName,
+ cancellationToken);
+
+ if (!result.CommunicationSucceeded ||
+ result.StatusCode is not (HttpStatusCode.OK or HttpStatusCode.Created))
+ {
+ return ConsoleExitStatusCodes.Failure;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"{EmojisConstants.Error} {ex.GetMessage()}");
+ return ConsoleExitStatusCodes.Failure;
+ }
+
+ logger.LogInformation($"{EmojisConstants.Success} Done");
+ return ConsoleExitStatusCodes.Success;
+ }
+
+ private static FisherRocEthernetMeterRequest BuildFisherRocEthernetMeterRequest(
+ MeterCreateCommandBaseSettings settings)
+ => new()
+ {
+ Name = settings.Name,
+ Description = settings.Description is not null && settings.Description.IsSet
+ ? settings.Description.Value
+ : string.Empty,
+ };
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateFisherRocPlusEthernetCommand.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateFisherRocPlusEthernetCommand.cs
new file mode 100644
index 00000000..3351ec9b
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Create/MeterCreateFisherRocPlusEthernetCommand.cs
@@ -0,0 +1,73 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Create;
+
+public sealed class MeterCreateFisherRocPlusEthernetCommand : AsyncCommand
+{
+ private readonly ILogger logger;
+ private readonly IKepwareConfigurationClient kepwareConfigurationClient;
+
+ public MeterCreateFisherRocPlusEthernetCommand(
+ ILoggerFactory loggerFactory,
+ IKepwareConfigurationClient kepwareConfigurationClient)
+ {
+ logger = loggerFactory.CreateLogger();
+ this.kepwareConfigurationClient = kepwareConfigurationClient;
+ }
+
+ public override Task ExecuteAsync(
+ CommandContext context,
+ MeterCreateCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return ExecuteInternalAsync(settings, cancellationToken);
+ }
+
+ private async Task ExecuteInternalAsync(
+ MeterCreateCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ConsoleHelper.WriteHeader();
+
+ try
+ {
+ kepwareConfigurationClient.SetConnectionInformation(
+ new Uri(settings.ServerUrl),
+ settings.UserName!.Value,
+ settings.Password!.Value);
+
+ var request = BuildFisherRocPlusEthernetMeterRequest(settings);
+ var result = await kepwareConfigurationClient.CreateFisherRocPlusEthernetMeter(
+ request,
+ settings.ChannelName,
+ settings.DeviceName,
+ settings.MeterGroupName,
+ cancellationToken);
+
+ if (!result.CommunicationSucceeded ||
+ result.StatusCode is not (HttpStatusCode.OK or HttpStatusCode.Created))
+ {
+ return ConsoleExitStatusCodes.Failure;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"{EmojisConstants.Error} {ex.GetMessage()}");
+ return ConsoleExitStatusCodes.Failure;
+ }
+
+ logger.LogInformation($"{EmojisConstants.Success} Done");
+ return ConsoleExitStatusCodes.Success;
+ }
+
+ private static FisherRocPlusEthernetMeterRequest BuildFisherRocPlusEthernetMeterRequest(
+ MeterCreateCommandBaseSettings settings)
+ => new()
+ {
+ Name = settings.Name,
+ Description = settings.Description is not null && settings.Description.IsSet
+ ? settings.Description.Value
+ : string.Empty,
+ };
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetAbbTotalflowCommand.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetAbbTotalflowCommand.cs
new file mode 100644
index 00000000..7a02db5b
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetAbbTotalflowCommand.cs
@@ -0,0 +1,80 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Retrieve;
+
+public sealed class MetersGetAbbTotalflowCommand : AsyncCommand
+{
+ private readonly ILogger logger;
+ private readonly IKepwareConfigurationClient kepwareConfigurationClient;
+
+ public MetersGetAbbTotalflowCommand(
+ ILoggerFactory loggerFactory,
+ IKepwareConfigurationClient kepwareConfigurationClient)
+ {
+ logger = loggerFactory.CreateLogger();
+ this.kepwareConfigurationClient = kepwareConfigurationClient;
+ }
+
+ public override Task ExecuteAsync(
+ CommandContext context,
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return ExecuteInternalAsync(settings, cancellationToken);
+ }
+
+ private async Task ExecuteInternalAsync(
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ConsoleHelper.WriteHeader();
+
+ try
+ {
+ kepwareConfigurationClient.SetConnectionInformation(
+ new Uri(settings.ServerUrl),
+ settings.UserName!.Value,
+ settings.Password!.Value);
+
+ var result = await kepwareConfigurationClient.GetAbbTotalflowMeters(
+ settings.ChannelName,
+ settings.DeviceName,
+ settings.MeterGroupName,
+ cancellationToken);
+
+ if (result is { CommunicationSucceeded: true, HasData: true })
+ {
+ foreach (var item in result.Data!)
+ {
+ var properties = item.GetType().GetPublicProperties();
+ foreach (var property in properties)
+ {
+ var typeName = $"{property.BeautifyName()}";
+ var spaces = string.Empty.PadRight(10 - typeName.Length);
+ logger.LogInformation($"{typeName}{spaces}{property.Name}: {item.GetPropertyValue(property.Name)}");
+ }
+
+ logger.LogInformation(string.Empty);
+ }
+ }
+ else
+ {
+ if (result is { HasMessage: true })
+ {
+ logger.LogWarning(result.Message);
+ }
+
+ return ConsoleExitStatusCodes.Failure;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"{EmojisConstants.Error} {ex.GetMessage()}");
+ return ConsoleExitStatusCodes.Failure;
+ }
+
+ logger.LogInformation($"{EmojisConstants.Success} Done");
+ return ConsoleExitStatusCodes.Success;
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetFisherRocEthernetCommand.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetFisherRocEthernetCommand.cs
new file mode 100644
index 00000000..0dc3e65e
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetFisherRocEthernetCommand.cs
@@ -0,0 +1,80 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Retrieve;
+
+public sealed class MetersGetFisherRocEthernetCommand : AsyncCommand
+{
+ private readonly ILogger logger;
+ private readonly IKepwareConfigurationClient kepwareConfigurationClient;
+
+ public MetersGetFisherRocEthernetCommand(
+ ILoggerFactory loggerFactory,
+ IKepwareConfigurationClient kepwareConfigurationClient)
+ {
+ logger = loggerFactory.CreateLogger();
+ this.kepwareConfigurationClient = kepwareConfigurationClient;
+ }
+
+ public override Task ExecuteAsync(
+ CommandContext context,
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return ExecuteInternalAsync(settings, cancellationToken);
+ }
+
+ private async Task ExecuteInternalAsync(
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ConsoleHelper.WriteHeader();
+
+ try
+ {
+ kepwareConfigurationClient.SetConnectionInformation(
+ new Uri(settings.ServerUrl),
+ settings.UserName!.Value,
+ settings.Password!.Value);
+
+ var result = await kepwareConfigurationClient.GetFisherRocEthernetMeters(
+ settings.ChannelName,
+ settings.DeviceName,
+ settings.MeterGroupName,
+ cancellationToken);
+
+ if (result is { CommunicationSucceeded: true, HasData: true })
+ {
+ foreach (var item in result.Data!)
+ {
+ var properties = item.GetType().GetPublicProperties();
+ foreach (var property in properties)
+ {
+ var typeName = $"{property.BeautifyName()}";
+ var spaces = string.Empty.PadRight(10 - typeName.Length);
+ logger.LogInformation($"{typeName}{spaces}{property.Name}: {item.GetPropertyValue(property.Name)}");
+ }
+
+ logger.LogInformation(string.Empty);
+ }
+ }
+ else
+ {
+ if (result is { HasMessage: true })
+ {
+ logger.LogWarning(result.Message);
+ }
+
+ return ConsoleExitStatusCodes.Failure;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"{EmojisConstants.Error} {ex.GetMessage()}");
+ return ConsoleExitStatusCodes.Failure;
+ }
+
+ logger.LogInformation($"{EmojisConstants.Success} Done");
+ return ConsoleExitStatusCodes.Success;
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetFisherRocPlusEthernetCommand.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetFisherRocPlusEthernetCommand.cs
new file mode 100644
index 00000000..3fa2dccd
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetFisherRocPlusEthernetCommand.cs
@@ -0,0 +1,80 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Retrieve;
+
+public sealed class MetersGetFisherRocPlusEthernetCommand : AsyncCommand
+{
+ private readonly ILogger logger;
+ private readonly IKepwareConfigurationClient kepwareConfigurationClient;
+
+ public MetersGetFisherRocPlusEthernetCommand(
+ ILoggerFactory loggerFactory,
+ IKepwareConfigurationClient kepwareConfigurationClient)
+ {
+ logger = loggerFactory.CreateLogger();
+ this.kepwareConfigurationClient = kepwareConfigurationClient;
+ }
+
+ public override Task ExecuteAsync(
+ CommandContext context,
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return ExecuteInternalAsync(settings, cancellationToken);
+ }
+
+ private async Task ExecuteInternalAsync(
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ConsoleHelper.WriteHeader();
+
+ try
+ {
+ kepwareConfigurationClient.SetConnectionInformation(
+ new Uri(settings.ServerUrl),
+ settings.UserName!.Value,
+ settings.Password!.Value);
+
+ var result = await kepwareConfigurationClient.GetFisherRocPlusEthernetMeters(
+ settings.ChannelName,
+ settings.DeviceName,
+ settings.MeterGroupName,
+ cancellationToken);
+
+ if (result is { CommunicationSucceeded: true, HasData: true })
+ {
+ foreach (var item in result.Data!)
+ {
+ var properties = item.GetType().GetPublicProperties();
+ foreach (var property in properties)
+ {
+ var typeName = $"{property.BeautifyName()}";
+ var spaces = string.Empty.PadRight(10 - typeName.Length);
+ logger.LogInformation($"{typeName}{spaces}{property.Name}: {item.GetPropertyValue(property.Name)}");
+ }
+
+ logger.LogInformation(string.Empty);
+ }
+ }
+ else
+ {
+ if (result is { HasMessage: true })
+ {
+ logger.LogWarning(result.Message);
+ }
+
+ return ConsoleExitStatusCodes.Failure;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"{EmojisConstants.Error} {ex.GetMessage()}");
+ return ConsoleExitStatusCodes.Failure;
+ }
+
+ logger.LogInformation($"{EmojisConstants.Success} Done");
+ return ConsoleExitStatusCodes.Success;
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetOmniFlowComputerCommand.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetOmniFlowComputerCommand.cs
new file mode 100644
index 00000000..037e64e3
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Connectivity/Meters/Retrieve/MetersGetOmniFlowComputerCommand.cs
@@ -0,0 +1,80 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Retrieve;
+
+public sealed class MetersGetOmniFlowComputerCommand : AsyncCommand
+{
+ private readonly ILogger logger;
+ private readonly IKepwareConfigurationClient kepwareConfigurationClient;
+
+ public MetersGetOmniFlowComputerCommand(
+ ILoggerFactory loggerFactory,
+ IKepwareConfigurationClient kepwareConfigurationClient)
+ {
+ logger = loggerFactory.CreateLogger();
+ this.kepwareConfigurationClient = kepwareConfigurationClient;
+ }
+
+ public override Task ExecuteAsync(
+ CommandContext context,
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(settings);
+
+ return ExecuteInternalAsync(settings, cancellationToken);
+ }
+
+ private async Task ExecuteInternalAsync(
+ MeterCommandBaseSettings settings,
+ CancellationToken cancellationToken)
+ {
+ ConsoleHelper.WriteHeader();
+
+ try
+ {
+ kepwareConfigurationClient.SetConnectionInformation(
+ new Uri(settings.ServerUrl),
+ settings.UserName!.Value,
+ settings.Password!.Value);
+
+ var result = await kepwareConfigurationClient.GetOmniFlowComputerMeters(
+ settings.ChannelName,
+ settings.DeviceName,
+ settings.MeterGroupName,
+ cancellationToken);
+
+ if (result is { CommunicationSucceeded: true, HasData: true })
+ {
+ foreach (var item in result.Data!)
+ {
+ var properties = item.GetType().GetPublicProperties();
+ foreach (var property in properties)
+ {
+ var typeName = $"{property.BeautifyName()}";
+ var spaces = string.Empty.PadRight(10 - typeName.Length);
+ logger.LogInformation($"{typeName}{spaces}{property.Name}: {item.GetPropertyValue(property.Name)}");
+ }
+
+ logger.LogInformation(string.Empty);
+ }
+ }
+ else
+ {
+ if (result is { HasMessage: true })
+ {
+ logger.LogWarning(result.Message);
+ }
+
+ return ConsoleExitStatusCodes.Failure;
+ }
+ }
+ catch (Exception ex)
+ {
+ logger.LogError($"{EmojisConstants.Error} {ex.GetMessage()}");
+ return ConsoleExitStatusCodes.Failure;
+ }
+
+ logger.LogInformation($"{EmojisConstants.Success} Done");
+ return ConsoleExitStatusCodes.Success;
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Settings/Connectivity/Meters/MeterCommandBaseSettings.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Settings/Connectivity/Meters/MeterCommandBaseSettings.cs
new file mode 100644
index 00000000..c578ebf7
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Settings/Connectivity/Meters/MeterCommandBaseSettings.cs
@@ -0,0 +1,22 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Settings.Connectivity.Meters;
+
+public class MeterCommandBaseSettings : ChannelAndDeviceCommandBaseSettings
+{
+ [CommandOption("-m|--meter-group-name ")]
+ [Description("MeterGroupName")]
+ public string MeterGroupName { get; init; } = string.Empty;
+
+ public override ValidationResult Validate()
+ {
+ var validationResult = base.Validate();
+ if (!validationResult.Successful)
+ {
+ return validationResult;
+ }
+
+ var isValidMeterGroupName = IsValidName("meter-group-name", MeterGroupName);
+ return isValidMeterGroupName.Successful
+ ? ValidationResult.Success()
+ : isValidMeterGroupName;
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Commands/Settings/Connectivity/Meters/MeterCreateCommandBaseSettings.cs b/src/Atc.Kepware.Configuration.CLI/Commands/Settings/Connectivity/Meters/MeterCreateCommandBaseSettings.cs
new file mode 100644
index 00000000..2d668b36
--- /dev/null
+++ b/src/Atc.Kepware.Configuration.CLI/Commands/Settings/Connectivity/Meters/MeterCreateCommandBaseSettings.cs
@@ -0,0 +1,26 @@
+namespace Atc.Kepware.Configuration.CLI.Commands.Settings.Connectivity.Meters;
+
+public class MeterCreateCommandBaseSettings : MeterCommandBaseSettings
+{
+ [CommandOption("-n|--name ")]
+ [Description("Requested Name")]
+ public string Name { get; init; } = string.Empty;
+
+ [CommandOption("--description [DESCRIPTION]")]
+ [Description("Requested Description")]
+ public FlagValue? Description { get; init; }
+
+ public override ValidationResult Validate()
+ {
+ var validationResult = base.Validate();
+ if (!validationResult.Successful)
+ {
+ return validationResult;
+ }
+
+ var isValidName = IsValidName(Name);
+ return isValidName.Successful
+ ? ValidationResult.Success()
+ : isValidName;
+ }
+}
\ No newline at end of file
diff --git a/src/Atc.Kepware.Configuration.CLI/Extensions/CommandAppExtensions.cs b/src/Atc.Kepware.Configuration.CLI/Extensions/CommandAppExtensions.cs
index 0d7ab84a..9c0823f3 100644
--- a/src/Atc.Kepware.Configuration.CLI/Extensions/CommandAppExtensions.cs
+++ b/src/Atc.Kepware.Configuration.CLI/Extensions/CommandAppExtensions.cs
@@ -19,6 +19,7 @@ private static Action> ConfigureConnectivityComma
node.AddBranch("channels", ConfigureChannelsCommands());
node.AddBranch("devices", ConfigureDevicesCommands());
node.AddBranch("tags", ConfigureTagsCommands());
+ node.AddBranch("meters", ConfigureMetersCommands());
};
private static Action> ConfigureChannelsCommands()
@@ -1416,6 +1417,57 @@ private static void ConfigureTagDeleteCommands(
.WithExample("connectivity tags delete taggroup -s [server-url] --name [tagGroupName]");
});
+ private static Action> ConfigureMetersCommands()
+ => node =>
+ {
+ node.SetDescription("Commands for meters");
+
+ ConfigureMeterGetCommands(node);
+ ConfigureMeterCreateCommands(node);
+ };
+
+ private static void ConfigureMeterGetCommands(
+ IConfigurator node)
+ => node.AddBranch("get", get =>
+ {
+ get.SetDescription("Operations related to retrieving meters.");
+
+ get.AddCommand("abbtotalflow")
+ .WithDescription("Get ABB Totalflow meters for a meter group.")
+ .WithExample("connectivity meters get abbtotalflow -s [server-url] -c [channelName] -d [deviceName] -m [meterGroupName]");
+
+ get.AddCommand("fisherrocethernet")
+ .WithDescription("Get Fisher ROC Ethernet meters for a meter group.")
+ .WithExample("connectivity meters get fisherrocethernet -s [server-url] -c [channelName] -d [deviceName] -m [meterGroupName]");
+
+ get.AddCommand("fisherrocplusethernet")
+ .WithDescription("Get Fisher ROC Plus Ethernet meters for a meter group.")
+ .WithExample("connectivity meters get fisherrocplusethernet -s [server-url] -c [channelName] -d [deviceName] -m [meterGroupName]");
+
+ get.AddCommand("omniflowcomputer")
+ .WithDescription("Get Omni Flow Computer meters for a meter group.")
+ .WithExample("connectivity meters get omniflowcomputer -s [server-url] -c [channelName] -d [deviceName] -m [meterGroupName]");
+ });
+
+ private static void ConfigureMeterCreateCommands(
+ IConfigurator node)
+ => node.AddBranch("create", create =>
+ {
+ create.SetDescription("Operations related to creating meters.");
+
+ create.AddCommand("abbtotalflow")
+ .WithDescription("Creates an ABB Totalflow meter.")
+ .WithExample("connectivity meters create abbtotalflow -s [server-url] -c [channelName] -d [deviceName] -m [meterGroupName] -n [meterName]");
+
+ create.AddCommand("fisherrocethernet")
+ .WithDescription("Creates a Fisher ROC Ethernet meter.")
+ .WithExample("connectivity meters create fisherrocethernet -s [server-url] -c [channelName] -d [deviceName] -m [meterGroupName] -n [meterName]");
+
+ create.AddCommand("fisherrocplusethernet")
+ .WithDescription("Creates a Fisher ROC Plus Ethernet meter.")
+ .WithExample("connectivity meters create fisherrocplusethernet -s [server-url] -c [channelName] -d [deviceName] -m [meterGroupName] -n [meterName]");
+ });
+
private static Action> ConfigureIotGatewayCommands()
=> node =>
{
diff --git a/src/Atc.Kepware.Configuration.CLI/GlobalUsings.cs b/src/Atc.Kepware.Configuration.CLI/GlobalUsings.cs
index cc8ff6a8..bce45061 100644
--- a/src/Atc.Kepware.Configuration.CLI/GlobalUsings.cs
+++ b/src/Atc.Kepware.Configuration.CLI/GlobalUsings.cs
@@ -16,6 +16,8 @@
global using Atc.Kepware.Configuration.CLI.Commands.Connectivity.Devices.Create;
global using Atc.Kepware.Configuration.CLI.Commands.Connectivity.Devices.Delete;
global using Atc.Kepware.Configuration.CLI.Commands.Connectivity.Devices.Retrieve;
+global using Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Create;
+global using Atc.Kepware.Configuration.CLI.Commands.Connectivity.Meters.Retrieve;
global using Atc.Kepware.Configuration.CLI.Commands.Connectivity.Tags;
global using Atc.Kepware.Configuration.CLI.Commands.DescriptionAttributes.Connectivity;
global using Atc.Kepware.Configuration.CLI.Commands.DescriptionAttributes.IotGateway;
@@ -38,6 +40,7 @@
global using Atc.Kepware.Configuration.CLI.Commands.Settings.Connectivity.Devices.Create;
global using Atc.Kepware.Configuration.CLI.Commands.Settings.Connectivity.Devices.Delete;
global using Atc.Kepware.Configuration.CLI.Commands.Settings.Connectivity.Devices.Retrieve;
+global using Atc.Kepware.Configuration.CLI.Commands.Settings.Connectivity.Meters;
global using Atc.Kepware.Configuration.CLI.Commands.Settings.Connectivity.Tags;
global using Atc.Kepware.Configuration.CLI.Commands.Settings.IotGateway;
global using Atc.Kepware.Configuration.CLI.Commands.Settings.IotGateway.IotAgent.Create;
@@ -48,6 +51,7 @@
global using Atc.Kepware.Configuration.CLI.Extensions;
global using Atc.Kepware.Configuration.Contracts;
global using Atc.Kepware.Configuration.Contracts.Connectivity;
+global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.AbbTotalflow;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.AllenBradleyControlLogixEthernet;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.AllenBradleyControlLogixServerEthernet;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.AllenBradleyEthernet;
@@ -67,6 +71,8 @@
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.DnpClientEthernet;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.EuroMap63;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.FanucFocasEthernet;
+global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.FisherRocEthernet;
+global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.FisherRocPlusEthernet;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.GeEthernet;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.GeEthernetGlobalData;
global using Atc.Kepware.Configuration.Contracts.Connectivity.Drivers.HoneywellHc900Ethernet;
diff --git a/src/Atc.Kepware.Configuration.Contracts/Atc.Kepware.Configuration.Contracts.csproj b/src/Atc.Kepware.Configuration.Contracts/Atc.Kepware.Configuration.Contracts.csproj
index 2b7dca69..4e0b9ba7 100644
--- a/src/Atc.Kepware.Configuration.Contracts/Atc.Kepware.Configuration.Contracts.csproj
+++ b/src/Atc.Kepware.Configuration.Contracts/Atc.Kepware.Configuration.Contracts.csproj
@@ -12,7 +12,7 @@
-
+
diff --git a/src/Atc.Kepware.Configuration/Atc.Kepware.Configuration.csproj b/src/Atc.Kepware.Configuration/Atc.Kepware.Configuration.csproj
index bb65f8e4..94c76251 100644
--- a/src/Atc.Kepware.Configuration/Atc.Kepware.Configuration.csproj
+++ b/src/Atc.Kepware.Configuration/Atc.Kepware.Configuration.csproj
@@ -12,10 +12,10 @@
-
+
-
-
+
+
diff --git a/src/Atc.Kepware.Configuration/Services/Connectivity/KepwareConfigurationClientConnectivity.cs b/src/Atc.Kepware.Configuration/Services/Connectivity/KepwareConfigurationClientConnectivity.cs
index fff6a97e..8f338088 100644
--- a/src/Atc.Kepware.Configuration/Services/Connectivity/KepwareConfigurationClientConnectivity.cs
+++ b/src/Atc.Kepware.Configuration/Services/Connectivity/KepwareConfigurationClientConnectivity.cs
@@ -268,19 +268,19 @@ public async Task> GetTags(
}
var tagGroupResult = await GetTagGroupResultForPathTemplate(basePathTemplate, cancellationToken);
-
if (tagGroupResult is { CommunicationSucceeded: true, HasData: true } &&
tagGroupResult.Data!.Any())
{
foreach (var kepwareTagGroup in tagGroupResult.Data!)
{
var tagGroup = kepwareTagGroup.Adapt();
-
if (maxDepth >= currentDepth && kepwareTagGroup.TagCountInTree > 0)
{
await IterateTagGroup(
tagGroup,
$"{basePathTemplate}/{EndpointPathTemplateConstants.TagGroups}",
+ kepwareTagGroup.TagCountInGroup,
+ kepwareTagGroup.TagCountInTree,
currentDepth + 1,
maxDepth,
cancellationToken);
@@ -922,6 +922,8 @@ private static bool IsValidConnectivityName(
private async Task IterateTagGroup(
TagGroup tagGroup,
string tagGroupPathTemplate,
+ int tagCountInGroup,
+ int tagCountInTree,
int currentDepth,
int maxDepth,
CancellationToken cancellationToken)
@@ -933,43 +935,49 @@ private async Task IterateTagGroup(
tagGroupPathTemplate = $"{tagGroupPathTemplate}/{tagGroup.Name}";
- // TODO: Optimize if tagGroup.TagCountInGroup > 0
- var tagResult = await GetTagsResultForPathTemplate(tagGroupPathTemplate, cancellationToken);
- if (!tagResult.CommunicationSucceeded)
+ if (tagCountInGroup > 0)
{
- return;
- }
+ var tagResult = await GetTagsResultForPathTemplate(tagGroupPathTemplate, cancellationToken);
+ if (!tagResult.CommunicationSucceeded)
+ {
+ return;
+ }
- if (tagResult.HasData &&
- tagResult.Data!.Any())
- {
- foreach (var tag in tagResult.Data!.Adapt>())
+ if (tagResult.HasData &&
+ tagResult.Data!.Any())
{
- tagGroup.Tags.Add(tag);
+ foreach (var tag in tagResult.Data!.Adapt>())
+ {
+ tagGroup.Tags.Add(tag);
+ }
}
}
- // TODO: Optimize if tagGroup.TagCountInTree > 0
- var tagGroupResult = await GetTagGroupResultForPathTemplate(tagGroupPathTemplate, cancellationToken);
-
- if (tagGroupResult is { CommunicationSucceeded: true, HasData: true } &&
- tagGroupResult.Data!.Any())
+ if (tagCountInTree > tagCountInGroup)
{
- foreach (var kepwareTagGroup in tagGroupResult.Data!)
- {
- var subTagGroup = kepwareTagGroup.Adapt();
+ var tagGroupResult = await GetTagGroupResultForPathTemplate(tagGroupPathTemplate, cancellationToken);
- if (kepwareTagGroup.TagCountInTree > 0)
+ if (tagGroupResult is { CommunicationSucceeded: true, HasData: true } &&
+ tagGroupResult.Data!.Any())
+ {
+ foreach (var kepwareTagGroup in tagGroupResult.Data!)
{
- await IterateTagGroup(
- subTagGroup,
- $"{tagGroupPathTemplate}/{EndpointPathTemplateConstants.TagGroups}",
- currentDepth + 1,
- maxDepth,
- cancellationToken);
- }
+ var subTagGroup = kepwareTagGroup.Adapt();
- tagGroup.TagGroups.Add(subTagGroup);
+ if (kepwareTagGroup.TagCountInTree > 0)
+ {
+ await IterateTagGroup(
+ subTagGroup,
+ $"{tagGroupPathTemplate}/{EndpointPathTemplateConstants.TagGroups}",
+ kepwareTagGroup.TagCountInGroup,
+ kepwareTagGroup.TagCountInTree,
+ currentDepth + 1,
+ maxDepth,
+ cancellationToken);
+ }
+
+ tagGroup.TagGroups.Add(subTagGroup);
+ }
}
}
}
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index acc4a622..1d166bfe 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -53,7 +53,7 @@
-
+
\ No newline at end of file
diff --git a/test/Atc.Kepware.Configuration.Tests/Atc.Kepware.Configuration.Tests.csproj b/test/Atc.Kepware.Configuration.Tests/Atc.Kepware.Configuration.Tests.csproj
index 69dd4ae4..b3b8adc6 100644
--- a/test/Atc.Kepware.Configuration.Tests/Atc.Kepware.Configuration.Tests.csproj
+++ b/test/Atc.Kepware.Configuration.Tests/Atc.Kepware.Configuration.Tests.csproj
@@ -1,4 +1,4 @@
-
+
false
@@ -6,7 +6,7 @@
-
+
diff --git a/test/Directory.Build.props b/test/Directory.Build.props
index 2b2e1fac..1f1fa3ae 100644
--- a/test/Directory.Build.props
+++ b/test/Directory.Build.props
@@ -17,11 +17,11 @@
-
-
+
+
-
+
all