diff --git a/Sharp7.Monitor.sln b/Sharp7.Monitor.sln new file mode 100644 index 0000000..1611437 --- /dev/null +++ b/Sharp7.Monitor.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34728.123 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp7.Monitor", "Sharp7.Monitor\Sharp7.Monitor.csproj", "{9E5BF5E6-D1A1-4252-92DA-B66A04C744D0}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E5BF5E6-D1A1-4252-92DA-B66A04C744D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E5BF5E6-D1A1-4252-92DA-B66A04C744D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E5BF5E6-D1A1-4252-92DA-B66A04C744D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E5BF5E6-D1A1-4252-92DA-B66A04C744D0}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D9D2B518-DC9C-4776-85F3-BB507BC3DA21} + EndGlobalSection +EndGlobal diff --git a/Sharp7.Monitor/Program.cs b/Sharp7.Monitor/Program.cs new file mode 100644 index 0000000..c71a020 --- /dev/null +++ b/Sharp7.Monitor/Program.cs @@ -0,0 +1,160 @@ +using System.ComponentModel; +using System.Reactive.Linq; +using System.Reactive.Threading.Tasks; +using System.Text; +using JetBrains.Annotations; +using Sharp7.Read; +using Sharp7.Rx; +using Sharp7.Rx.Enums; +using Spectre.Console; +using Spectre.Console.Cli; + +Console.InputEncoding = Console.OutputEncoding = Encoding.UTF8; + +using var cancellationSource = new CancellationTokenSource(); + +Console.CancelKeyPress += OnCancelKeyPress; +AppDomain.CurrentDomain.ProcessExit += onProcessExit; + + +void OnCancelKeyPress(object? sender, ConsoleCancelEventArgs e) +{ + if (!cancellationSource.IsCancellationRequested) + // NOTE: cancel event, don't terminate the process + e.Cancel = true; + + cancellationSource.Cancel(); +} + +void onProcessExit(object? sender, EventArgs e) +{ + if (cancellationSource.IsCancellationRequested) + { + // NOTE: SIGINT (cancel key was pressed, this shouldn't ever actually hit however, as we remove the event handler upon cancellation of the `cancellationSource`) + return; + } + + cancellationSource.Cancel(); +} + +await using var t = cancellationSource.Token.Register(() => Console.WriteLine("Cancelled!")); + +try +{ + var app = new CommandApp(); + app.WithData(cancellationSource.Token); + return await app.RunAsync(args); +} +finally +{ + Console.WriteLine("all done"); + AppDomain.CurrentDomain.ProcessExit -= onProcessExit; + Console.CancelKeyPress -= OnCancelKeyPress; +} + +internal sealed class ReadPlcCommand : AsyncCommand +{ + public override async Task ExecuteAsync(CommandContext context, Settings settings) + { + var token = (CancellationToken) (context.Data ?? CancellationToken.None); + AnsiConsole.WriteLine(); + AnsiConsole.MarkupLine($"Establishing connection to plc [green]{settings.PlcIp}[/], CPU [green]{settings.CpuMpiAddress}[/], rack [green]{settings.RackNumber}[/]."); + using var plc = new Sharp7Plc(settings.PlcIp, settings.RackNumber, settings.CpuMpiAddress); + + await plc.InitializeAsync(); + + // Connect + await AnsiConsole.Status() + .Spinner(Spinner.Known.BouncingBar) + .StartAsync("Connecting...", async ctx => + { + var lastState = ConnectionState.Initial; + ctx.Status(lastState.ToString()); + + while (!token.IsCancellationRequested) + { + var state = await plc.ConnectionState.FirstAsync(s => s != lastState).ToTask(token); + ctx.Status(state.ToString()); + + if (state == ConnectionState.Connected) + return; + } + }); + + if (token.IsCancellationRequested) + return 0; + + // Create a table + var table = new Table(); + + table.AddColumn("Variable"); + table.AddColumn("Value"); + + foreach (var variable in settings.Variables) + { + table.AddRow(variable, ""); + } + + await AnsiConsole.Live(table) + .StartAsync(async ctx => + { + int i = 0; + while (!token.IsCancellationRequested) + { + table.Rows.Update(0, 1, new Text((++i).ToString())); + ctx.Refresh(); + await Task.Delay(1000, token); + + } + }); + + + + //for (int i = 0; i < 10; i++) + //{ + // await plc.SetValue($"DB{db}.Int6", (short)i); + // var value = await plc.GetValue($"DB{db}.Int6"); + // value.Dump(); + + // await Task.Delay(200); + //} + + + // AnsiConsole.MarkupLine($"Total file size for [green]{searchPattern}[/] files in [green]{searchPath}[/]: [blue]{totalFileSize:N0}[/] bytes"); + + return 0; + } + + [NoReorder] + public sealed class Settings : CommandSettings + { + [Description("IP address of S7")] + [CommandArgument(0, "")] + public string PlcIp { get; init; } + + [CommandArgument(1, "[variables]")] + [Description("Variables to read from S7, like Db200.Int4.\r\nFor format description see https://github.com/evopro-ag/Sharp7Reactive.")] + public string[] Variables { get; init; } + + [CommandOption("-c|--cpu")] + [Description("CPU MPI address of S7 instance.\r\nSee https://github.com/fbarresi/Sharp7/wiki/Connection#rack-and-slot.\r\n")] + [DefaultValue(0)] + public int CpuMpiAddress { get; init; } + + [CommandOption("-r|--rack")] + [Description("Rack number of S7 instance.\r\nSee https://github.com/fbarresi/Sharp7/wiki/Connection#rack-and-slot.\r\n")] + [DefaultValue(0)] + public int RackNumber { get; init; } + + public override ValidationResult Validate() + { + if (!StringHelper.IsValidIp4(PlcIp)) + return ValidationResult.Error($"\"{PlcIp}\" is not a valid IP V4 address"); + + if (Variables == null || Variables.Length == 0) + return ValidationResult.Error("Please supply at least one variable to read"); + + return ValidationResult.Success(); + } + } +} \ No newline at end of file diff --git a/Sharp7.Monitor/Properties/launchSettings.json b/Sharp7.Monitor/Properties/launchSettings.json new file mode 100644 index 0000000..ad0a3d0 --- /dev/null +++ b/Sharp7.Monitor/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Sharp7.Monitor": { + "commandName": "Project", + "commandLineArgs": "10.30.110.62 DB2050.String10.5" + } + } +} \ No newline at end of file diff --git a/Sharp7.Monitor/Sharp7.Monitor.csproj b/Sharp7.Monitor/Sharp7.Monitor.csproj new file mode 100644 index 0000000..cd2cdac --- /dev/null +++ b/Sharp7.Monitor/Sharp7.Monitor.csproj @@ -0,0 +1,17 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + diff --git a/Sharp7.Monitor/StringHelper.cs b/Sharp7.Monitor/StringHelper.cs new file mode 100644 index 0000000..b161664 --- /dev/null +++ b/Sharp7.Monitor/StringHelper.cs @@ -0,0 +1,13 @@ +namespace Sharp7.Read; + +public static class StringHelper +{ + public static bool IsValidIp4(string? ipString) + { + if (string.IsNullOrWhiteSpace(ipString)) + return false; + + var splitValues = ipString.Split('.'); + return splitValues.Length == 4 && splitValues.All(r => byte.TryParse(r, out _)); + } +} \ No newline at end of file