From 70fca2d84cc0574c144c454b51a1f654e9bcd88c Mon Sep 17 00:00:00 2001 From: longfellowJian <779035789@qq.com> Date: Thu, 27 Mar 2025 16:01:28 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4=EF=BC=9A=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- App.config | 67 +++++++++++ AutomationInterface.SampleApp.sln | 35 ++++++ AutomationInterfaceAccess.cs | 94 +++++++++++++++ CreateZipFile.ps1 | 63 ++++++++++ DemoAttribute.cs | 17 +++ ExportDemo.cs | 11 ++ InputData.cs | 92 +++++++++++++++ InstrumentDemo.cs | 60 ++++++++++ MethodExecutionDemo.cs | 61 ++++++++++ MiscDemo.cs | 15 +++ Program.cs | 119 +++++++++++++++++++ Properties/AssemblyInfo.cs | 36 ++++++ QueryDemo.cs | 41 +++++++ README.txt | 21 ++++ SampleApp.cs | 185 ++++++++++++++++++++++++++++++ SampleApp.csproj | 78 +++++++++++++ SessionData.cs | 24 ++++ WorkflowDemo.cs | 61 ++++++++++ 18 files changed, 1080 insertions(+) create mode 100644 App.config create mode 100644 AutomationInterface.SampleApp.sln create mode 100644 AutomationInterfaceAccess.cs create mode 100644 CreateZipFile.ps1 create mode 100644 DemoAttribute.cs create mode 100644 ExportDemo.cs create mode 100644 InputData.cs create mode 100644 InstrumentDemo.cs create mode 100644 MethodExecutionDemo.cs create mode 100644 MiscDemo.cs create mode 100644 Program.cs create mode 100644 Properties/AssemblyInfo.cs create mode 100644 QueryDemo.cs create mode 100644 README.txt create mode 100644 SampleApp.cs create mode 100644 SampleApp.csproj create mode 100644 SessionData.cs create mode 100644 WorkflowDemo.cs diff --git a/App.config b/App.config new file mode 100644 index 0000000..69d1086 --- /dev/null +++ b/App.config @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AutomationInterface.SampleApp.sln b/AutomationInterface.SampleApp.sln new file mode 100644 index 0000000..0289750 --- /dev/null +++ b/AutomationInterface.SampleApp.sln @@ -0,0 +1,35 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.1022 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleApp", "SampleApp.csproj", "{153C39DC-DB8A-4E8D-9503-A51F4BE96513}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {153C39DC-DB8A-4E8D-9503-A51F4BE96513}.Debug|x64.ActiveCfg = Debug|x64 + {153C39DC-DB8A-4E8D-9503-A51F4BE96513}.Debug|x64.Build.0 = Debug|x64 + {153C39DC-DB8A-4E8D-9503-A51F4BE96513}.Release|x64.ActiveCfg = Release|x64 + {153C39DC-DB8A-4E8D-9503-A51F4BE96513}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {E41B27F9-49B0-48B3-B3DD-9DA06108CE7B} + EndGlobalSection + GlobalSection(TeamFoundationVersionControl) = preSolution + SccNumberOfProjects = 1 + SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} + SccTeamFoundationServer = http://tfsapp01.tecan.net:8080/tfs/at_dragonfly + SccProjectUniqueName0 = SampleApp.csproj + SccProjectName0 = . + SccAuxPath0 = http://tfsapp01.tecan.net:8080/tfs/at_dragonfly + SccLocalPath0 = . + SccProvider0 = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} + EndGlobalSection +EndGlobal diff --git a/AutomationInterfaceAccess.cs b/AutomationInterfaceAccess.cs new file mode 100644 index 0000000..fa363b8 --- /dev/null +++ b/AutomationInterfaceAccess.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Tecan.At.Dragonfly.AutomationInterface; +using Tecan.At.Dragonfly.AutomationInterface.Data; +using Tecan.At.Dragonfly.AutomationInterface.MethodModification; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + public static class AutomationInterfaceAccess + { + public static IReadOnlyCollection GetInstruments() + { + using (var ai = AutomationInterfaceFactory.Build()) + { + return ai.InstrumentManagement.GetInstruments(); + } + } + + public static List GetMethods() + { + using (var ai = AutomationInterfaceFactory.Build()) + { + return ai.Queries.GetMethods().ToList(); + } + } + + public static string GetMethodXml(string methodName) + { + using (var ai = AutomationInterfaceFactory.Build()) + { + return ai.Queries.GetMethodXml(methodName); + } + } + + public static bool CheckMethod(IInstrument selectedInstrument, string methodAsXml, string methodName, out IEnumerable messages) + { + messages = null; + using (var ai = AutomationInterfaceFactory.Build()) + { + return ai.MethodExecution.CheckMethod(selectedInstrument, methodAsXml, methodName, out messages); + } + } + + public static IMethodExecutionResult ExecuteMethod(IInstrument selectedInstrument, string methodAsXml, string methodName, bool isStacker) + { + using (var ai = AutomationInterfaceFactory.Build()) + { + return ai.MethodExecution.ExecuteMethod(selectedInstrument, methodAsXml, methodName, isStacker); + } + } + + public static Task ExecuteMethodAsync(IInstrument selectedInstrument, string methodAsXml, string methodName, bool isStacker) + { + using (var ai = AutomationInterfaceFactory.Build()) + { + return ai.MethodExecution.ExecuteMethodAsync(selectedInstrument, methodAsXml, methodName, isStacker); + } + } + + public static void CancelAsyncMethodExecution(IInstrument selectedInstrument) + { + using (var ai = AutomationInterfaceFactory.Build()) + { + ai.MethodExecution.Cancel(selectedInstrument); + } + } + + public static string ExportData(Guid workspaceId, Guid executionId) + { + using (var ai = AutomationInterfaceFactory.Build()) + { + return ai.MethodExecution.GetResults(workspaceId, executionId); + } + } + + public static string ChangePlateLayout(string methodAsXml, int[] wellsToSelect) + { + using (var plateLayoutModifier = MethodModificationFactory.Build(ref methodAsXml)) + { + var plateInfo = plateLayoutModifier.GetPlateInfo(); + for (uint wellIndex = 0; wellIndex < plateInfo.WellAmount; wellIndex++) + { + plateLayoutModifier.SelectWell(wellIndex, wellsToSelect.Any(x => x == wellIndex)); + } + + methodAsXml = plateLayoutModifier.ApplyChangesToXmlString(); + } + + return methodAsXml; + } + } +} diff --git a/CreateZipFile.ps1 b/CreateZipFile.ps1 new file mode 100644 index 0000000..f477740 --- /dev/null +++ b/CreateZipFile.ps1 @@ -0,0 +1,63 @@ +try +{ + # see https://stackoverflow.com/questions/9948517/how-to-stop-a-powershell-script-on-the-first-error + $ErrorActionPreference = "Stop" + + Add-Type -A System.IO.Compression.FileSystem + + + # Write-Host ('OS Tmp Dir: ' + $env:TEMP ) + + # create tmo dir + $tmpDir = $env:TEMP + [guid]::NewGuid() + Write-Host ('Use tmp-dir: ' + $tmpDir) + + New-Item -ItemType Directory -Force -Path $tmpDir + + + # copy Sample App source code files to tmp dir + Copy-Item $($PSScriptRoot + "\*.cs") -Destination $tmpDir + Copy-Item $($PSScriptRoot + "\*.config") -Destination $tmpDir + Copy-Item $($PSScriptRoot + "\*.csproj") -Destination $tmpDir + Copy-Item $($PSScriptRoot + "\*.sln") -Destination $tmpDir + Copy-Item $($PSScriptRoot + "\*.txt") -Destination $tmpDir + Copy-Item $($PSScriptRoot + "\Properties") -Destination $tmpDir -Recurse + + # copy Automation Interface docu to tmp dir + # -> PDF is currently not included into ZIP file + # $aiDocuFileName = Resolve-Path -Path $($PSScriptRoot + "\..\..\Production\Automation\AutomationInterface\Doc\AutomationInterfaceManual.pdf") + # Write-Host ('aiDocuFileName : ' + $aiDocuFileName ) + # Copy-Item $aiDocuFileName -Destination $tmpDir + + + # create zip file + $zipFileName = $PSScriptRoot + "\SampleApp.zip" + Write-Host ('zipFileName: ' + $zipFileName ) + + if (Test-Path $zipFileName) + { + Write-Host("Delete already available .zip file...") + Remove-Item -Recurse -Force $zipFileName + } + + # ...and create it + [IO.Compression.ZipFile]::CreateFromDirectory($tmpDir, $zipFileName) + + # delete temp directory + Write-Host ('Del tmp folder : ' + $tmpDir ) + Remove-Item -Recurse -Force $tmpDir + + + Write-Host('') + Write-Host('') + Write-Host('##############################################################################') + Write-Host('Successfully created Sample Application .zip file: ' + $zipFileName) + Write-Host('##############################################################################') + + exit $LASTEXITCODE +} +catch +{ + Write-Error('ERROR: ' + $_.Exception) + exit $LASTEXITCODE +} diff --git a/DemoAttribute.cs b/DemoAttribute.cs new file mode 100644 index 0000000..69149dd --- /dev/null +++ b/DemoAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + [AttributeUsage(AttributeTargets.Method)] + public class DemoAttribute : Attribute + { + public string Name { get; private set; } + public char Key { get; private set; } + + public DemoAttribute(string name, char key) + { + Name = name; + Key = key; + } + } +} \ No newline at end of file diff --git a/ExportDemo.cs b/ExportDemo.cs new file mode 100644 index 0000000..cafda77 --- /dev/null +++ b/ExportDemo.cs @@ -0,0 +1,11 @@ +namespace Tecan.At.AutomationInterface.SampleApp +{ + internal class ExportDemo + { + [Demo("exportData", 'a')] + public static void Export(SampleApp sampleApp, SessionData sessionData) + { + sampleApp.ExportData(sessionData); + } + } +} \ No newline at end of file diff --git a/InputData.cs b/InputData.cs new file mode 100644 index 0000000..c24b760 --- /dev/null +++ b/InputData.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + public enum AutomationInterfaceActions + { + Undefined, + GetInstrument, + GetAllMethods, + GetMethod, + CheckMethod, + ExecuteMethod, + PlateOut, + PlateIn, + ExportData, + ClearSession, + StartWorkflow + } + + public class InputData + { + public AutomationInterfaceActions Action { get; private set; } + public List Parameter { get; } = new List(); + + public bool Parse(string value) + { + if (string.IsNullOrWhiteSpace(value)) + return false; + + string[] parts = value.Split(new[] {" "}, StringSplitOptions.RemoveEmptyEntries); + + if (!GetAction(parts[0])) + { + return false; + } + + for (int i = 1; i < parts.Length; i++) + { + Parameter.Add(parts[i]); + } + + return true; + } + + private bool GetAction(string part) + { + if (!part.StartsWith("-")) + { + return false; + } + + switch (part.ToLower()) + { + case "-getinstrument": + Action = AutomationInterfaceActions.GetInstrument; + break; + case "-getallmethods": + Action = AutomationInterfaceActions.GetAllMethods; + break; + case "-getmethod": + Action = AutomationInterfaceActions.GetMethod; + break; + case "-checkmethod": + Action = AutomationInterfaceActions.CheckMethod; + break; + case "-executemethod": + Action = AutomationInterfaceActions.ExecuteMethod; + break; + case "-plateout": + Action = AutomationInterfaceActions.PlateOut; + break; + case "-platein": + Action = AutomationInterfaceActions.PlateIn; + break; + case "-exportdata": + Action = AutomationInterfaceActions.ExportData; + break; + case "-clearsession": + Action = AutomationInterfaceActions.ClearSession; + break; + case "-startworkflow": + Action = AutomationInterfaceActions.StartWorkflow; + break; + default: + return false; + } + + return true; + } + } +} diff --git a/InstrumentDemo.cs b/InstrumentDemo.cs new file mode 100644 index 0000000..82f1d04 --- /dev/null +++ b/InstrumentDemo.cs @@ -0,0 +1,60 @@ +using System; +using Tecan.At.Dragonfly.AutomationInterface; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + internal class InstrumentDemo + { + [Demo("getInstrument ", '1')] + public static void GetInstrumentByAlias(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("Instrument alias? "); + var aliasPart = Console.ReadLine(); + + sampleApp.GetInstrument(sessionData, aliasPart); + + if (sessionData?.SelectedInstrument != null) + { + Console.WriteLine($"Selected instrument: {sessionData.SelectedInstrument.Alias ?? ""}"); + } + else + { + Console.WriteLine($"No instrument available"); + } + + } + + [Demo("plateOut", '8')] + public static void PlateOut(SampleApp sampleApp, SessionData sessionData) + { + sessionData.EnforceSetInstrument(); + + using (AutomationInterfaceFactory.Build()) + { + sessionData.SelectedInstrument.PlateOut(); + } + } + + [Demo("plateIn", '9')] + public static void PlateIn(SampleApp sampleApp, SessionData sessionData) + { + sessionData.EnforceSetInstrument(); + + using (AutomationInterfaceFactory.Build()) + { + sessionData.SelectedInstrument.PlateIn(); + } + } + + [Demo("getInstrumentState", 'f')] + public static void GetState(SampleApp sampleApp, SessionData sessionData) + { + sessionData.EnforceSetInstrument(); + + using (AutomationInterfaceFactory.Build()) + { + Console.WriteLine($"Instrument state is '{ sessionData.SelectedInstrument.State}'."); + } + } + } +} \ No newline at end of file diff --git a/MethodExecutionDemo.cs b/MethodExecutionDemo.cs new file mode 100644 index 0000000..54e8f4b --- /dev/null +++ b/MethodExecutionDemo.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + internal class MethodExecutionDemo + { + [Demo("checkMethod ", '4')] + public static void CheckMethod(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("methodFile? "); + var methodFile = Console.ReadLine(); + + IEnumerable messages; + sampleApp.CheckMethod(sessionData, methodFile, out messages); + + Console.WriteLine(sessionData.MethodIsValid ? "valid" : "invalid"); + + foreach (var message in messages) + { + Console.WriteLine(message); + } + } + + [Demo("executeMethod ", '5')] + public static void ExecuteMethod(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("methodFile? "); + var methodFile = Console.ReadLine(); + + sampleApp.ExecuteMethod(sessionData, methodFile, "AutomationInterfaceMethod", new int[] { }); + } + + [Demo("executeMethodAsync ", '6')] + public static void ExecuteMethodAsync(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("methodFile? "); + var methodFile = Console.ReadLine(); + + sampleApp.ExecuteMethodAsync(sessionData, methodFile, "AutomationInterfaceMethod", new int[] { }); + } + + [Demo("executeMethodAsyncAndCancel ", '7')] + public static void ExecuteMethodAsyncWithCancellation(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("methodFile? "); + var methodFile = Console.ReadLine(); + + sampleApp.ExecuteMethodAsyncAndCancel(sessionData, methodFile, "AutomationInterfaceMethod", new int[] { }); + } + + [Demo("executeMethod (with stacker) ", 'e')] + public static void ExecuteMethodWithStacker(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("methodFile? "); + var methodFile = Console.ReadLine(); + + sampleApp.ExecuteMethod(sessionData, methodFile, "AutomationInterfaceMethod", new int[] { }, true); + } + } +} \ No newline at end of file diff --git a/MiscDemo.cs b/MiscDemo.cs new file mode 100644 index 0000000..b746895 --- /dev/null +++ b/MiscDemo.cs @@ -0,0 +1,15 @@ +namespace Tecan.At.AutomationInterface.SampleApp +{ + internal class MiscDemo + { + [Demo("No Automation Interface scope available", 'd')] + public static void Export(SampleApp sampleApp, SessionData sessionData) + { + // inside method a scope is spanned + sampleApp.GetInstrument(sessionData, string.Empty); + + // no scope here + sessionData.SelectedInstrument.PlateIn(); + } + } +} \ No newline at end of file diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..e395629 --- /dev/null +++ b/Program.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Tecan.At.Dragonfly.AutomationInterface; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + class Program + { + static void Main(string[] args) + { + var commands = BuildCommands(); + + try + { + // Needed in your code + AutomationInterfaceFactory.Start(); + + SessionData sessionData = null; + var automationInterfaceAccess = new SampleApp(); + + var quitCommands = new[] { "q", "Q" }; + + while (true) + { + RenderMenu(commands); + + var consoleInput = Console.ReadKey(); + Console.WriteLine(); + if (quitCommands.Any(x => x == consoleInput.KeyChar.ToString())) + { + break; + } + + var command = '0'; + if (!char.TryParse(consoleInput.KeyChar.ToString(), out command)) + { + Console.WriteLine($"'{consoleInput}' isn't a valid command key."); + continue; + } + else if (commands.ContainsKey(command)) + { + if (sessionData == null) + { + sessionData = new SessionData(); + } + + try + { + var theCommand = commands[command]; + + Console.WriteLine($"--- Execute command {theCommand.Item1}..."); + + theCommand.Item2.Invoke(automationInterfaceAccess, sessionData); + + Console.WriteLine("--- done"); + } + catch (TargetInvocationException e) + { + Console.WriteLine($"### Error: {e.InnerException.Message} ###"); + Console.WriteLine(); + Console.WriteLine(e.InnerException.ToString()); + } + catch (Exception e) + { + Console.WriteLine(e.ToString()); + } + } + else + { + Console.WriteLine($"Unknown command '{command}'."); + } + } + } + finally + { + // Needed in your code + AutomationInterfaceFactory.Stop(); + } + } + + private static void RenderMenu(Dictionary>> commands) + { + Console.WriteLine(); + commands.Keys.OrderBy(x => x).ToList().ForEach(c => + { + var actCmd = commands[c]; + Console.WriteLine($"{c}. {actCmd.Item1}"); + }); + Console.WriteLine(); + Console.WriteLine($"Press q to exit."); + Console.WriteLine(); + Console.Write("> "); + } + + private static Dictionary>> BuildCommands() + { + var methods = typeof(Program).Assembly.GetTypes() + .SelectMany(t => t.GetMethods()) + .Where(m => m.GetCustomAttributes(typeof(DemoAttribute), false).Length > 0) + .Select(x => new { Attribute = (DemoAttribute)x.GetCustomAttributes(typeof(DemoAttribute), false).Single(), Delegate = x }) + .ToArray(); + + var commands = new Dictionary>>(methods.Length); + + foreach (var actDemo in methods) + { + commands.Add(actDemo.Attribute.Key, Tuple.Create>(actDemo.Attribute.Name, (aia, sd) => { actDemo.Delegate.Invoke(null, new object[] { aia, sd }); })); + } + + Console.WriteLine($"Count: " + methods.Length); + + return commands; + } + + + } +} diff --git a/Properties/AssemblyInfo.cs b/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d4e3c01 --- /dev/null +++ b/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AutomationInterface.IntegrationsTestsConsole")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AutomationInterface.IntegrationsTestsConsole")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("153c39dc-db8a-4e8d-9503-a51f4be96513")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/QueryDemo.cs b/QueryDemo.cs new file mode 100644 index 0000000..38620d3 --- /dev/null +++ b/QueryDemo.cs @@ -0,0 +1,41 @@ +using System; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + internal class QueryDemo + { + [Demo("getMethods", '2')] + public static void GetAllMethods(SampleApp sampleApp, SessionData sessionData) + { + sampleApp.GetMethods(sessionData); + + foreach (var methodName in sessionData.AvailableMethods) + { + Console.WriteLine(methodName); + } + } + + [Demo("getMethodXml ", '3')] + public static void GetMethodByNameAndTarget(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("method name? "); + var methodName = Console.ReadLine(); + + var xmlString = sampleApp.GetMethodXml(methodName); + + if (String.IsNullOrWhiteSpace(xmlString)) + { + Console.WriteLine($"Method '{methodName}' not available."); + return; + } + + Console.WriteLine($"Loaded method '{methodName}'. Length: '{xmlString.Length}'."); + + Console.Write("target file name? "); + var targetFileName = Console.ReadLine(); + + // Console.WriteLine(xmlString); + System.IO.File.WriteAllText(targetFileName, xmlString); + } + } +} diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..20e5d7f --- /dev/null +++ b/README.txt @@ -0,0 +1,21 @@ +Automation Interface Sample Application +----------------------------------------- + +To get the Sample Application running follow these steps: + +1. Add a reference to your project to the file 'Tecan.At.Dragonfly.AutomationInterface.dll'. + This file is located in the Clients folder usually under C:\Program Files\Tecan\SparkControl. + +2. In Visual Studio project post build event add call to a powershell script. + The script is called 'CopyAdditionalAutomationInterfaceFiles.ps1' and is located in the same folder as the 'AutomationInterface.dll'. + + Example: + powershell.exe \CopyAdditionalAutomationInterfaceFiles.ps1 $(TargetDir) + + +Remark: +It might be necessary that you add following call in your pre-build step: + +powershell.exe Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted -Force; + +Try it first without this call! \ No newline at end of file diff --git a/SampleApp.cs b/SampleApp.cs new file mode 100644 index 0000000..49cf977 --- /dev/null +++ b/SampleApp.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + public class SampleApp + { + public void GetInstrument(SessionData sessionData, string aliasPart) + { + try + { + var availableInstruments = AutomationInterfaceAccess.GetInstruments(); + sessionData.SelectedInstrument = availableInstruments.FirstOrDefault(x => string.IsNullOrEmpty(aliasPart) || x.Alias.Contains(aliasPart)); + + if (sessionData.SelectedInstrument == null) + { + sessionData.SelectedInstrument = availableInstruments.FirstOrDefault(x => string.IsNullOrEmpty(aliasPart) || x.SerialNumber.Contains(aliasPart)); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public void GetMethods(SessionData sessionData) + { + try + { + sessionData.AvailableMethods = AutomationInterfaceAccess.GetMethods(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public string GetMethodXml(string methodName) + { + try + { + return AutomationInterfaceAccess.GetMethodXml(methodName); + } + catch (Exception e) + { + Console.WriteLine(e); + + return null; + } + } + + public void CheckMethod(SessionData sessionData, string methodFile, out IEnumerable messages) + { + sessionData.EnforceSetInstrument(); + + messages = null; + try + { + string methodAsXml = System.IO.File.ReadAllText(methodFile); + sessionData.MethodIsValid = AutomationInterfaceAccess.CheckMethod(sessionData.SelectedInstrument, methodAsXml, "MethodToCheck", out messages); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public void ExecuteMethod(SessionData sessionData, string methodFile, string methodName, int[] wellsToSelect, bool isStacker = false) + { + sessionData.EnforceSetInstrument(); + + try + { + string methodAsXml = System.IO.File.ReadAllText(methodFile); + + if (wellsToSelect.Any()) + { + methodAsXml = AutomationInterfaceAccess.ChangePlateLayout(methodAsXml, wellsToSelect); + } + + var result = AutomationInterfaceAccess.ExecuteMethod(sessionData.SelectedInstrument, methodAsXml, methodName, isStacker); + if (result != null) + { + sessionData.WorkspaceId = result.WorkspaceId; + sessionData.ExecutionId = result.ExecutionId; + } + else + { + Console.WriteLine("Error executing method (checkMethod failed)."); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public void ExecuteMethodAsync(SessionData sessionData, string methodFile, string methodName, int[] wellsToSelect, bool isStacker = false) + { + sessionData.EnforceSetInstrument(); + + try + { + string methodAsXml = System.IO.File.ReadAllText(methodFile); + + if (wellsToSelect.Any()) + { + methodAsXml = AutomationInterfaceAccess.ChangePlateLayout(methodAsXml, wellsToSelect); + } + + var task = AutomationInterfaceAccess.ExecuteMethodAsync(sessionData.SelectedInstrument, methodAsXml, methodName, isStacker); + + Console.WriteLine($"Started method '{methodName}'..."); + while (task.Status == TaskStatus.Running) + { + Thread.Sleep(1000); + Console.Write("."); + } + + task.Wait(); + + if (task.Result != null) + { + sessionData.WorkspaceId = task.Result.WorkspaceId; + sessionData.ExecutionId = task.Result.ExecutionId; + + Console.WriteLine(); + Console.WriteLine($"Executed method '{methodName}'. WorkspaceId: '{sessionData.WorkspaceId}'."); + } + else + { + Console.WriteLine("Error executing method (checkMethod failed)."); + } + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public void ExecuteMethodAsyncAndCancel(SessionData sessionData, string methodFile, string methodName, int[] wellsToSelect, bool isStacker = false) + { + sessionData.EnforceSetInstrument(); + + try + { + string methodAsXml = System.IO.File.ReadAllText(methodFile); + + if (wellsToSelect.Any()) + { + methodAsXml = AutomationInterfaceAccess.ChangePlateLayout(methodAsXml, wellsToSelect); + } + + var task = AutomationInterfaceAccess.ExecuteMethodAsync(sessionData.SelectedInstrument, methodAsXml, methodName, isStacker); + Console.WriteLine($"Started method '{methodName}'..."); + + // method should be a rather complex method that it takes longer to execute as this thread is sleeping + Thread.Sleep(5000); + Console.WriteLine($"Cancel method execution."); + + AutomationInterfaceAccess.CancelAsyncMethodExecution(sessionData.SelectedInstrument); + Console.WriteLine($"Cancellation finished."); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + public void ExportData(SessionData sessionData) + { + try + { + sessionData.ExportedDataFile = AutomationInterfaceAccess.ExportData(sessionData.WorkspaceId, sessionData.ExecutionId); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + } +} diff --git a/SampleApp.csproj b/SampleApp.csproj new file mode 100644 index 0000000..b6cb894 --- /dev/null +++ b/SampleApp.csproj @@ -0,0 +1,78 @@ + + + + + Debug + AnyCPU + {153C39DC-DB8A-4E8D-9503-A51F4BE96513} + Exe + Tecan.At.AutomationInterface.SampleApp + Tecan.At.AutomationInterface.SampleApp + v4.6.2 + 512 + true + true + SAK + SAK + SAK + SAK + + + + + true + bin\Debug\ + DEBUG;TRACE + full + x64 + prompt + MinimumRecommendedRules.ruleset + false + + + bin\Release\ + TRACE + true + pdbonly + x64 + prompt + MinimumRecommendedRules.ruleset + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + powershell.exe Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted -Force; + + + powershell.exe <your path>\CopyAdditionalAutomationInterfaceFiles.ps1 $(TargetDir) + + \ No newline at end of file diff --git a/SessionData.cs b/SessionData.cs new file mode 100644 index 0000000..199040c --- /dev/null +++ b/SessionData.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using Tecan.At.Dragonfly.AutomationInterface.Data; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + public class SessionData + { + public List AvailableMethods { get; set; } + public IInstrument SelectedInstrument { get; set; } + public bool MethodIsValid { get; set; } + public Guid WorkspaceId { get; set; } + public Guid ExecutionId { get; set; } + public string ExportedDataFile { get; set; } + + public void EnforceSetInstrument() + { + if (SelectedInstrument == null) + { + throw new InvalidOperationException("Instrument is not set."); + } + } + } +} \ No newline at end of file diff --git a/WorkflowDemo.cs b/WorkflowDemo.cs new file mode 100644 index 0000000..7f86520 --- /dev/null +++ b/WorkflowDemo.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace Tecan.At.AutomationInterface.SampleApp +{ + internal class WorkflowDemo + { + [Demo("startWorkflow ", 'b')] + public static void Export(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("instrument alias? "); + var instrumentAlias = Console.ReadLine(); + + Console.WriteLine("methodFile? "); + var methodFile = Console.ReadLine(); + + sessionData = new SessionData(); + sampleApp.GetInstrument(sessionData, instrumentAlias); + sampleApp.ExecuteMethod(sessionData, methodFile, "AutomationInterfaceMethod", new int[] { }); + + sampleApp.ExportData(sessionData); + + Console.WriteLine("Export: " + sessionData.ExportedDataFile); + } + + [Demo("select wells and execute ", 'c')] + public static void SelectWellsAndExport(SampleApp sampleApp, SessionData sessionData) + { + Console.Write("instrument alias? "); + var instrumentAlias = Console.ReadLine(); + + Console.WriteLine("methodFile? "); + var methodFile = Console.ReadLine(); + + Console.WriteLine("wells to select? Bsp: 2, 5, 32, ..."); + var wellsToSelectString = Console.ReadLine(); + + List wellsToSelect = new List(); + + if (!string.IsNullOrWhiteSpace(wellsToSelectString)) + { + string[] intParts = wellsToSelectString.Split(new char[] { ',' }); + foreach (var intPart in intParts) + { + if (int.TryParse(intPart.Trim(), out int wellIndex)) + { + wellsToSelect.Add(wellIndex); + } + } + } + + sessionData = new SessionData(); + sampleApp.GetInstrument(sessionData, instrumentAlias); + sampleApp.ExecuteMethod(sessionData, methodFile, "AutomationInterfaceMethod", wellsToSelect.ToArray()); + + sampleApp.ExportData(sessionData); + + Console.WriteLine("Export: " + sessionData.ExportedDataFile); + } + } +} \ No newline at end of file