Below is a full example of an integration of the PIXL client in C# 7.0.
To test this client:
A full example showing how to create a client for the PIXL in C# 7.0. |
Copy Code
|
---|---|
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Grpc.Core; using Sila2; using SI.PIXL.Client.Enums; using SI.PIXL.Client.Structs.CommandResponse.Workflows.ColonyDetection; using SI.PIXL.Client.Structs.CommandResponse.Workflows.RandomColonyPicking; using SI.PIXL.Client.Structs.CommandResponse.Workflows.Rearray; using SI.PIXL.Client.Structs.Instructions; using SI.PIXL.Client.Structs.PlateHandling; // disable CS4014 'await' warnings as this is running in a static context #pragma warning disable CS4014 namespace SI.PIXL.Client.Console { class Program { #region Notes /* This example application is provided by Singer Instrument Company Limited with the intention of providing an example integration of the SI.PIXL.Client NuGet package for either: - .NET Framework 4.6.1 or above - .NET Core 2.0 or above - .NET Standard 2.0 or above For help please visit https://si-pixl-client-documentation.azurewebsites.net/Webframe.html or email technicalsupport@singerinstruments.com. */ #endregion #region Constants /// <summary> /// Get the argument used when specifying the gRPC host and port. /// </summary> public const string SpecifyHostAndPortArgument = "-P"; /// <summary> /// Get the argument to use if it is desired that debug information should be displayed. /// </summary> public const string DisplayDebugInformationArgument = "-D"; /// <summary> /// Get a character that is used for line breaks. /// </summary> private static readonly char LineBreakCharacter = char.Parse("-"); /// <summary> /// Get a character representing a child elements prefix. /// </summary> private const string ChildElementPrefex = "\t-"; #endregion #region StaticFields /// <summary> /// Get or set if debugging information is displayed. /// </summary> private static bool displayDebuggingInformation; #endregion #region StaticProperties /// <summary> /// Get or set the pending plate requests. /// </summary> protected static PlateRequest[] PendingPlateRequests { get; set; } /// <summary> /// Get or set if debugging information is displayed. /// </summary> public static bool DisplayDebuggingInformation { get { return displayDebuggingInformation; } set { // if was displaying debugging information before this value changed unsubscribe from the event if (displayDebuggingInformation) PIXL.DebugInformationReceived -= PIXL_DebugInformationReceived; displayDebuggingInformation = value; // if now displaying debugging information then subscribe to the event if (displayDebuggingInformation) PIXL.DebugInformationReceived += PIXL_DebugInformationReceived; } } /// <summary> /// Get or set the PIXL client. /// </summary> protected static PIXLClient PIXL { get; set; } #endregion #region Connection /// <summary> /// Parse a host and a port. /// </summary> /// <param name="argument">The argument. Should be in the following format: -P[HOST:PORT]</param> /// <param name="host">The parsed host.</param> /// <param name="port">The parsed port.</param> /// <returns>The port number.</returns> private static bool TryParseHostAndPort(string argument, out string host, out int port) { // check for null or empty string if (string.IsNullOrEmpty(argument)) { host = string.Empty; port = 0; return false; } // remove -P argument = argument.ToUpper().Replace(SpecifyHostAndPortArgument, string.Empty); // trim white space argument = argument.Trim(); // split at : var arguments = argument.Split(":".ToCharArray()); // if incorrect number of arguments if (arguments.Length != 2) { host = string.Empty; port = 0; return false; } // set host host = arguments[0]; // try parse port return int.TryParse(arguments[1], out port); } #endregion #region Formatting /// <summary> /// Append a line break. /// </summary> /// <param name="character">The character to use for breaks.</param> public static void AppendLineBreak(char character) { // get width var width = Math.Max(0, Console.WindowWidth - 1); // set cursor position if (Console.CursorLeft != 0) Console.Write(Environment.NewLine); // append characters for (var i = 0; i < width; i++) Console.Write(character); // end line Console.Write("\n"); } /// <summary> /// Log an error message. /// </summary> /// <param name="message">The error message.</param> private static void LogError(string message) { // log the error, in red Log(message, ConsoleColor.Red); } /// <summary> /// Log a information message. /// </summary> /// <param name="message">The information message.</param> private static void LogInfo(string message) { // log the info, in dark cyan Log(message, ConsoleColor.DarkCyan); } /// <summary> /// Log a debug message. /// </summary> /// <param name="message">The information message.</param> private static void LogDebugInfo(string message) { // log the debugging information, in dark magenta Log(message, ConsoleColor.DarkMagenta); } /// <summary> /// Log an information message. /// </summary> /// <param name="message">The message.</param> /// <param name="color">The color to display the message in.</param> private static void Log(string message, ConsoleColor color) { // hold previous colour var previous = Console.ForegroundColor; // change colour Console.ForegroundColor = color; // append message Console.WriteLine(message); // revert to previous colour Console.ForegroundColor = previous; } #endregion #region Parsing /// <summary> /// Try to parse a plate type. /// </summary> /// <param name="input">The plate type.</param> /// <param name="type">The parsed type.</param> /// <returns>True if the plate type could be parsed, else false.</returns> private static bool TryParsePlateType(string input, out PlateTypes type) { // determine the plate type by parsing the input switch (input.ToUpper()) { case ("PLUSPLATE"): case ("SBS"): type = PlateTypes.PlusPlate; return true; case ("PLUSPLATE6"): case ("SBS6"): type = PlateTypes.PlusPlate_6; return true; case ("PLUSPLATE12"): case ("SBS12"): type = PlateTypes.PlusPlate_12; return true; case ("PLUSPLATE24"): case ("SBS24"): type = PlateTypes.PlusPlate_24; return true; case ("PLUSPLATE48"): case ("SBS48"): type = PlateTypes.PlusPlate_48; return true; case ("PLUSPLATE96"): case ("SBS96"): type = PlateTypes.PlusPlate_96; return true; case ("PLUSPLATE384"): case ("SBS384"): type = PlateTypes.PlusPlate_384; return true; case ("PLUSPLATE1536"): case ("SBS1536"): type = PlateTypes.PlusPlate_1536; return true; case ("MTP6"): case ("MWP6"): type = PlateTypes.MWP_6; return true; case ("MTP12"): case ("MWP12"): type = PlateTypes.MWP_12; return true; case ("MTP24"): case ("MWP24"): type = PlateTypes.MWP_24; return true; case ("MTP48"): case ("MWP48"): type = PlateTypes.MWP_48; return true; case ("MTP96"): case ("MWP96"): type = PlateTypes.MWP_96; return true; case ("MTP384"): case ("MWP384"): type = PlateTypes.MWP_384; return true; case ("DWP6"): type = PlateTypes.DeepMWP_6; return true; case ("DWP12"): type = PlateTypes.DeepMWP_12; return true; case ("DWP24"): type = PlateTypes.DeepMWP_24; return true; case ("DWP48"): type = PlateTypes.DeepMWP_48; return true; case ("DWP96"): type = PlateTypes.DeepMWP_96; return true; case ("DWP384"): type = PlateTypes.DeepMWP_384; return true; case ("PCR96_200ul"): type = PlateTypes.PCR_96_200ul; return true; case ("PCR96_300ul"): type = PlateTypes.PCR_96_300ul; return true; case ("PCR384_40ul"): type = PlateTypes.PCR_384_40ul; return true; case ("P90"): case ("PETRI90"): type = PlateTypes.Petri_90; return true; case ("P150"): case ("PETRI150"): type = PlateTypes.Petri_150; return true; default: type = PlateTypes.None; return false; } } /// <summary> /// Try and parse a plate role. /// </summary> /// <param name="input">The plate role.</param> /// <param name="role">The parsed role.</param> /// <returns>True if the plate role could be parsed, else false.</returns> private static bool TryParsePlateRole(string input, out PlateRoles role) { // determine the plate role by parsing the input switch (input.ToUpper()) { case ("SOURCE"): case ("S"): role = PlateRoles.Source; return true; case ("TARGET"): case ("T"): role = PlateRoles.Target; return true; default: role = PlateRoles.None; return false; } } /// <summary> /// Try and parse a bay. /// </summary> /// <param name="input">The bay.</param> /// <param name="bay">The parsed bay.</param> /// <returns>True if the bay could be parsed, else false.</returns> private static bool TryParseBay(string input, out Bays bay) { // determine the bay by parsing the input switch (input.ToUpper()) { case ("BLACK"): case ("BL"): case ("S"): case ("SOURCE"): bay = Bays.Black; return true; case ("RED"): case ("T1"): case ("R"): bay = Bays.Red; return true; case ("BLUE"): case ("T2"): case ("B"): bay = Bays.Blue; return true; case ("YELLOW"): case ("T3"): case ("Y"): bay = Bays.Yellow; return true; case ("GREEN"): case ("G"): case ("T4"): bay = Bays.Green; return true; default: bay = Bays.None; return false; } } #endregion #region InputProcessing /// <summary> /// Process user input. /// </summary> /// <param name="input">The user input.</param> /// <param name="command">The derived command.</param> /// <param name="arguments">The derived arguments.</param> private static void ProcessUserInput(string input, out string command, out string[] arguments) { // check something if (string.IsNullOrEmpty(input)) { command = string.Empty; arguments = new string[0]; return; } // split at spaces var splitInput = input.Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); // if nothing, just a command with no arguments if (splitInput.Length == 0) { command = input; arguments = new string[0]; return; } // set command command = splitInput[0]; // set arguments var argumentsAsList = new List<string>(); // set all arguments for (var i = 1; i < splitInput.Length; i++) argumentsAsList.Add(splitInput[i]); // set arguments arguments = argumentsAsList.ToArray(); } /// <summary> /// Process input. /// </summary> private static void ProcessInput() { var continueExecution = true; while (continueExecution) { try { // line break AppendLineBreak(LineBreakCharacter); // ensure console colour is correct Console.ForegroundColor = ConsoleColor.White; // prompt for command Console.WriteLine("Enter a command: "); // read input var input = Console.ReadLine()?.ToUpper() ?? string.Empty; // process the input ProcessUserInput(input, out var command, out var arguments); // select the command switch (command) { case ("ABORT"): // abort any running command var abortResult = PIXL.Abort(); // log info LogInfo(abortResult.ToString()); break; case ("AWAKEN"): // awaken the PIXL var awakenResult = PIXL.Awaken(); // log info LogInfo(awakenResult.ToString()); break; case ("BAYSTATUS"): // iterate bays foreach (var bay in new[] { Bays.Black, Bays.Red, Bays.Blue, Bays.Yellow, Bays.Green }) { // check if occupied if (PIXL.IsBayOccupied(bay)) { // get plate var plate = PIXL.GetPlateLoadedInBay(bay); // display the contents of the bay LogInfo($"{bay}: {plate}."); } else { // display that bay is empty LogInfo($"{bay}: Empty."); } } break; case ("DETERMINEPOSITION"): // get position var position = PIXL.DetermineRandomColonyPickingStartPosition(arguments[0], arguments[1], arguments[2]); // display as info in the console LogInfo($"Is available: {position.IsPositionAvailable}."); LogInfo($"Row: {position.TargetLayoutStartRow}."); LogInfo($"Column: {position.TargetLayoutStartColumn}."); break; case ("EXIT"): // break execution continueExecution = false; break; case ("HELP"): // display help DisplayCommandsList(); break; case ("INFO"): // display info DisplayInfo(); break; case ("INITIALISE"): // initialise and log result PIXL.Initialise(x => LogInfo(x.ToString())); break; case ("LISTPLATES"): // display available plates DisplayAvailablePlates(); break; case ("LISTPROFILES"): // display available pinning profiles DisplayAvailablePinningProfiles(); break; case ("LISTTEMPLATES"): // display available project templates DisplayAvailableProjectTemplates(); break; case ("LOADED"): // if there are no arguments if (arguments.Length > 0) { /* expected arguments: 0 - bay 1 - plate ID 2 - plate type 3 - plate role */ // try and parse the bay if (!TryParseBay(arguments[0], out var loadBay)) throw new Exception("Bay could not be identified from the input."); // try and parse the plate type if (!TryParsePlateType(arguments[2], out var loadType)) throw new Exception("Plate type could not be identified from the input."); // try and parse the plate role if (!TryParsePlateRole(arguments[3], out var loadRole)) throw new Exception("Plate role could not be identified from the input."); // notify PIXL plate is loaded var plateLoadedResult = PIXL.PlateLoaded(loadBay, arguments[1], loadType, loadRole, string.Empty); // log result LogInfo(plateLoadedResult.ToString()); } else { // get loads and swaps var pendingLoadAndSwaps = PendingPlateRequests?.Where(x => ((x.Action == PlateActions.Load) || (x.Action == PlateActions.Swap))); // iterate all pending loads and swaps foreach (var loadOrSwap in pendingLoadAndSwaps) { // notify PIXL plate is loaded var plateLoadedResult = PIXL.PlateLoaded(loadOrSwap.Bay, loadOrSwap.Plate.ID, loadOrSwap.Plate.Type, loadOrSwap.Plate.Role, string.Empty); // log result LogInfo(plateLoadedResult.ToString()); } } break; case ("MOTORISEDDOORCLOSE"): // close the motorised door and log the result PIXL.CloseMotorisedDoor(x => LogInfo(x.ToString())); break; case ("MOTORISEDDOOROPEN"): // open the motorised door and log the result PIXL.OpenMotorisedDoor(x => LogInfo(x.ToString())); break; case ("REMOVED"): // if some arguments if (arguments.Length > 0) { // try and parse the bay if (!TryParseBay(arguments[0], out var removeBay)) throw new Exception("Bay could not be identified from the input."); // notify PIXL plates are removed var removedResult = PIXL.PlateRemoved(removeBay); // log result LogInfo(removedResult.ToString()); } else { // get pending removes var pendingRemoves = PendingPlateRequests?.Where(x => x.Action == PlateActions.Remove); // iterate all pending removes, foreach (var remove in pendingRemoves) { // notify PIXL that plate has been removed var removedResult = PIXL.PlateRemoved(remove.Bay); // log result LogInfo(removedResult.ToString()); } } break; case ("REMOVEDALL"): // remove all plates from all bays foreach (var bay in new[] { Bays.Black, Bays.Red, Bays.Blue, Bays.Yellow, Bays.Green }) { // notify PIXL that plate has been removed var removedResult = PIXL.PlateRemoved(bay); // log result LogInfo(removedResult.ToString()); } break; case ("RESET"): // reset the PIXL and log the result PIXL.Reset(x => LogInfo(x.ToString())); break; case ("RESTART"): // restart the PIXL var restartResult = PIXL.Restart(); // log result LogInfo(restartResult.ToString()); break; case ("RUNCD"): // run the Colony Detection workflow PIXL.RunColonyDetectionWorkflow("ID1234", string.Empty, OnRunColonyDetectionWorkflowResponseCallback); break; case ("RUNRCP"): // run the Random Colony Picking workflow PIXL.RunRandomColonyPickingWorkflow("ID1234", string.Empty, "", PIXLClient.NoColonyLimit, OnRunRandomColonyPickingWorkflowResponseCallback); break; case ("RUNREARRAY"): // create some test plates var plate1 = new Plate("Source_Plate_1", PlateTypes.PlusPlate_96, PlateRoles.Source); var plate2 = new Plate("Target_Plate_1", PlateTypes.PlusPlate_96, PlateRoles.Target); // create some test pinnings var pinnings = new[] { new ConsecutivePinningInstruction(new[] { new PinningInstruction(plate1, 1, 1), new PinningInstruction(plate2, 1, 1) }) }; // run the Re-array workflow PIXL.RunRearrayWorkflow("ID1234", string.Empty, string.Empty, pinnings, OnRunRearrayWorkflowResponseCallback); break; case ("RUNREARRAYFILE"): // check arguments if (arguments.Length == 0) throw new Exception("Please specify the [PATH] and [ITERATIONS] as the input."); // hold iterations var iterations = 1; // if more than just path specified if (arguments.Length > 1) { // parse minutes, if specified as an argument if (!int.TryParse(arguments[1], out iterations)) throw new Exception("Iterations could not be identified from the input."); } // create thread for background var thread = new Thread(() => { // iterate all required cycles for (var i = 0; i < iterations; i++) { // display result LogInfo($"Beginning re-array cycle {i + 1} of {iterations}."); // run the Re-array workflow var result = PIXL.RunRearrayWorkflowFromFile("ID1234", arguments[0]).Result; // display result LogInfo($"Re-array cycle {i + 1} of {iterations} finished, result: {result.Result}"); // break if aborted if ((PIXL.OperationalStatus.CommandStatus == CommandStatus.Aborted) || (PIXL.OperationalStatus.CommandStatus == CommandStatus.Aborting)) break; // break if failed if (!result.Result.IsSuccess) break; } }); // start operation thread.Start(); break; case ("RUNUV"): // parse minutes, if specified as an argument if (!int.TryParse(arguments[0], out var minutes)) throw new Exception("Minutes could not be identified from the input."); // run sterilisation and log the result PIXL.RunUVSterilisation(minutes, x => LogInfo(x.ToString())); break; case ("SLEEP"): // put the PIXL to sleep var sleepResult = PIXL.Sleep(); // log result LogInfo(sleepResult.ToString()); break; case ("SHUTDOWN"): // shutdown the PIXL var shutdownResult = PIXL.ShutDown(); // log result LogInfo(shutdownResult.ToString()); break; default: // display the default command or unknown command prompts if (string.IsNullOrEmpty(command)) LogError("Please enter a command or type HELP to view command list."); else LogError($"{command} is an unrecognized command."); break; } } catch (RpcException e) { // handle RCP exceptions HandleRPCException(e); } catch (Exception e) { // log general exceptions LogError($"Exception caught processing input: {e.Message}"); } } } #endregion #region Callbacks /// <summary> /// Handle a Run Random Colony Picking command response. /// </summary> /// <param name="response">The command response.</param> private static void OnRunRandomColonyPickingWorkflowResponseCallback(RunRandomColonyPickingCommandResponse response) { // format and display the output from the Random Colony Picking workflow on the console AppendLineBreak(LineBreakCharacter); // display program info LogInfo(response.Result.ToString()); LogInfo($"Tracking base: {response.TrackingPath}"); LogInfo($"Program ID: {response.ProgramInformation.ProgramID}"); AppendLineBreak(LineBreakCharacter); LogInfo("Colonies:"); var stringBuilder = new StringBuilder(); // iterate and display all colony information for (var i = 0; i < response.ProgramInformation.ColonyInformation.Length; i++) { var colony = response.ProgramInformation.ColonyInformation[i]; stringBuilder.AppendLine($"---{i + 1} of {response.ProgramInformation.ColonyInformation.Length}:"); stringBuilder.AppendLine($"------ID: {colony.ID}"); stringBuilder.AppendLine($"------Name: {colony.Name}"); stringBuilder.AppendLine($"------IsSelected: {colony.IsSelected}"); stringBuilder.AppendLine($"------SectorID: {colony.SectorID}"); stringBuilder.AppendLine($"------X: {colony.X}"); stringBuilder.AppendLine($"------Y: {colony.Y}"); stringBuilder.AppendLine($"------Area: {colony.Area}"); stringBuilder.AppendLine($"------Diameter: {colony.Diameter}"); stringBuilder.AppendLine($"------Brightness: {colony.Brightness}"); stringBuilder.AppendLine($"------AverageRed: {colony.AverageRed}"); stringBuilder.AppendLine($"------AverageGreen: {colony.AverageGreen}"); stringBuilder.AppendLine($"------AverageBlue: {colony.AverageBlue}"); stringBuilder.AppendLine($"------Redness: {colony.Redness}"); stringBuilder.AppendLine($"------Greenness: {colony.Greenness}"); stringBuilder.AppendLine($"------Blueness: {colony.Blueness}"); stringBuilder.AppendLine($"------ProximityToClosest: {colony.ProximityToClosest}"); stringBuilder.AppendLine($"------Circularity: {colony.Circularity}"); } if (response.ProgramInformation.ColonyInformation.Length == 0) stringBuilder.AppendLine("---None"); LogInfo(stringBuilder.ToString()); stringBuilder.Clear(); AppendLineBreak(LineBreakCharacter); LogInfo("Pinnings:"); // iterate and display all pinning information for (var i = 0; i < response.ProgramInformation.Pinnings.Length; i++) { var pinning = response.ProgramInformation.Pinnings[i]; stringBuilder.AppendLine($"---{i + 1} of {response.ProgramInformation.Pinnings.Length}: "); stringBuilder.AppendLine($"------ID: {pinning.PinningID}"); stringBuilder.AppendLine($"------Date: {pinning.DateTime.Day}/{pinning.DateTime.Month}/{pinning.DateTime.Year}"); stringBuilder.AppendLine($"------Time: {pinning.DateTime.Hour}:{pinning.DateTime.Minute}:{pinning.DateTime.Second}"); stringBuilder.AppendLine($"---------Source: {1} of {1}"); stringBuilder.AppendLine($"------------Plate ID: {pinning.Source.PlateID}"); stringBuilder.AppendLine($"------------Plate Name: {pinning.Source.PlateName}"); stringBuilder.AppendLine($"------------Colony ID: {pinning.Source.ColonyID}"); stringBuilder.AppendLine($"------------Colony Name: {pinning.Source.ColonyName}"); stringBuilder.AppendLine($"------------X: {pinning.Source.X}"); stringBuilder.AppendLine($"------------Y: {pinning.Source.Y}"); stringBuilder.AppendLine($"------------Result: {pinning.Source.Result}"); for (var j = 0; j < pinning.Targets.Length; j++) { var target = pinning.Targets[j]; stringBuilder.AppendLine($"---------Target {j + 1} of {pinning.Targets.Length}:"); stringBuilder.AppendLine($"------------Plate ID: {target.PlateID}"); stringBuilder.AppendLine($"------------Plate Name: {target.PlateName}"); stringBuilder.AppendLine($"------------Location: {target.Location}"); stringBuilder.AppendLine($"------------X: {target.X}"); stringBuilder.AppendLine($"------------Y: {target.Y}"); stringBuilder.AppendLine($"------------Result: {target.Result}"); } } if (response.ProgramInformation.Pinnings.Length == 0) stringBuilder.AppendLine("-None"); LogInfo(stringBuilder.ToString()); AppendLineBreak(LineBreakCharacter); } /// <summary> /// Handle a Run re-array workflow command response. /// </summary> /// <param name="response">The command response.</param> private static void OnRunRearrayWorkflowResponseCallback(RunRearrayCommandResponse response) { // format and display the output from the Random Colony Picking workflow on the console AppendLineBreak(LineBreakCharacter); // display program info LogInfo(response.Result.ToString()); LogInfo($"Tracking base: {response.TrackingPath}"); LogInfo($"Program ID: {response.ProgramInformation.ProgramID}"); AppendLineBreak(LineBreakCharacter); var stringBuilder = new StringBuilder(); LogInfo("Pinnings:"); // iterate and display all colony information for (var i = 0; i < response.ProgramInformation.Pinnings.Length; i++) { var pinning = response.ProgramInformation.Pinnings[i]; stringBuilder.AppendLine($"---{i + 1} of {response.ProgramInformation.Pinnings.Length}: "); stringBuilder.AppendLine($"------ID: {pinning.PinningID}"); stringBuilder.AppendLine($"------Date: {pinning.DateTime.Day}/{pinning.DateTime.Month}/{pinning.DateTime.Year}"); stringBuilder.AppendLine($"------Time: {pinning.DateTime.Hour}:{pinning.DateTime.Minute}:{pinning.DateTime.Second}"); for (var j = 0; j < pinning.Positions.Length; j++) { var position = pinning.Positions[j]; stringBuilder.AppendLine($"---------Position {j + 1} of {pinning.Positions.Length}:"); stringBuilder.AppendLine($"------------Plate ID: {position.PlateID}"); stringBuilder.AppendLine($"------------Plate Name: {position.PlateName}"); stringBuilder.AppendLine($"------------Location: {position.Location}"); stringBuilder.AppendLine($"------------X: {position.X}"); stringBuilder.AppendLine($"------------Y: {position.Y}"); stringBuilder.AppendLine($"------------Result: {position.Result}"); } } if (response.ProgramInformation.Pinnings.Length == 0) stringBuilder.AppendLine("-None"); LogInfo(stringBuilder.ToString()); AppendLineBreak(LineBreakCharacter); } /// <summary> /// Handle a Run Colony Detection command response. /// </summary> /// <param name="response">The command response.</param> private static void OnRunColonyDetectionWorkflowResponseCallback(RunColonyDetectionCommandResponse response) { // format and display the output from the Colony Detection workflow on the console AppendLineBreak(LineBreakCharacter); // display program info LogInfo(response.Result.ToString()); LogInfo($"Tracking base: {response.TrackingPath}"); LogInfo($"Program ID: {response.ProgramInformation.ProgramID}"); AppendLineBreak(LineBreakCharacter); LogInfo("Colonies:"); var stringBuilder = new StringBuilder(); // iterate and display all colony information for (var i = 0; i < response.ProgramInformation.ColonyInformation.Length; i++) { var colony = response.ProgramInformation.ColonyInformation[i]; stringBuilder.AppendLine($"---{i + 1} of {response.ProgramInformation.ColonyInformation.Length}:"); stringBuilder.AppendLine($"------ID: {colony.ID}"); stringBuilder.AppendLine($"------Name: {colony.Name}"); stringBuilder.AppendLine($"------IsSelected: {colony.IsSelected}"); stringBuilder.AppendLine($"------SectorID: {colony.SectorID}"); stringBuilder.AppendLine($"------X: {colony.X}"); stringBuilder.AppendLine($"------Y: {colony.Y}"); stringBuilder.AppendLine($"------Area: {colony.Area}"); stringBuilder.AppendLine($"------Diameter: {colony.Diameter}"); stringBuilder.AppendLine($"------Brightness: {colony.Brightness}"); stringBuilder.AppendLine($"------AverageRed: {colony.AverageRed}"); stringBuilder.AppendLine($"------AverageGreen: {colony.AverageGreen}"); stringBuilder.AppendLine($"------AverageBlue: {colony.AverageBlue}"); stringBuilder.AppendLine($"------Redness: {colony.Redness}"); stringBuilder.AppendLine($"------Greenness: {colony.Greenness}"); stringBuilder.AppendLine($"------Blueness: {colony.Blueness}"); stringBuilder.AppendLine($"------ProximityToClosest: {colony.ProximityToClosest}"); stringBuilder.AppendLine($"------Circularity: {colony.Circularity}"); } if (response.ProgramInformation.ColonyInformation.Length == 0) stringBuilder.AppendLine("---None"); LogInfo(stringBuilder.ToString()); AppendLineBreak(LineBreakCharacter); } #endregion #region Main /// <summary> /// Main entry point for the application. /// </summary> /// <param name="args"> /// Start up arguments. Valid values are: /// -P[HOST]:[PORT] - specify the PIXL's IP address and port number. For example -P127.0.0.1:50052 for an IP address of 127.0.0.1 and a port number of 50052. /// -D - specify that debug information should be displayed on the Console. /// </param> static void Main(string[] args) { try { // display startup DisplayStartup(); // the gRPC channel Channel channel; // determine if using a specified host and port var useSpecifiedHostAndPort = args.Any(x => x.ToUpper().StartsWith(SpecifyHostAndPortArgument)); // determine if debug info should be displayed var displayDebugInfo = args.Any(x => x.ToUpper().StartsWith(DisplayDebugInformationArgument)); // if using a specified host and port if (useSpecifiedHostAndPort) { // get the host and port argument var hostAndPortArgument = args.FirstOrDefault(x => x.ToUpper().StartsWith(SpecifyHostAndPortArgument)) ?? string.Empty; // try and parse host and port if (!TryParseHostAndPort(hostAndPortArgument, out var host, out var port)) throw new ArgumentException($"{hostAndPortArgument} could not be parsed to a host and port."); // display connection method LogInfo($"Connecting to the PIXL with specified socket ({host}:{port})."); // use socket to find the PIXL channel = PIXLClient.GetPIXLChannel(host, port); } else { // display connection method LogInfo("Connecting to the PIXL using SiLA discovery."); // use SiLA Server Discovery to find the PIXL channel = PIXLClient.GetPIXLChannel(); } // if no channel was created there is an issue with either connectivity or the host and port (if specified) if (channel == null) throw new NullReferenceException("No PIXL found."); // create client PIXL = new PIXLClient(channel); // display SiLA2 device info on the Console DisplaySiLA2DeviceInfo(); // subscribe to events SubscribeToEvents(); // set if debugging info should be displayed DisplayDebuggingInformation = displayDebugInfo; // process all input ProcessInput(); // line break AppendLineBreak(LineBreakCharacter); } catch (Exception e) { // log the error LogError(ErrorHandling.HandleException(e)); } finally { // dispose the PIXL PIXL?.Dispose(); } // display message LogInfo("Press any key to exit..."); // wait for a key press Console.ReadKey(); } #endregion #region HelperMethods /// <summary> /// Subscribe to events from a PIXL client. /// </summary> private static void SubscribeToEvents() { // handle required plate interaction instructions changed PIXL.RequiredPlateInteractionInstructionsChanged += (s, e) => LogInfo(e); // handle required plate interactions changed PIXL.RequiredPlateInteractionsChanged += (s, e) => PendingPlateRequests = e; // handle active error information changed PIXL.ActiveErrorChanged += (s, e) => { // check for error and display if (e.HasActiveError) LogError($"Error: {e.Code}: {e.Description}"); }; // handle connection changed PIXL.IsConnectionLiveChanged += (s, e) => LogInfo($"The connection to the PIXL has changed and is now {(e ? "live" : "dead")}."); } /// <summary> /// Display the startup message on the Console. /// </summary> private static void DisplayStartup() { // display startup AppendLineBreak(LineBreakCharacter); LogInfo("Starting SI.PIXL.Client.Console.exe."); AppendLineBreak(LineBreakCharacter); LogInfo("This program can be started with the following arguments:"); LogInfo(" -P[HOST]:[PORT] : Specify the PIXL's IP address and port number."); LogInfo(" For example -P127.0.0.1:50052 for an IP address of 127.0.0.1 and a port number of 50052."); LogInfo(" -D : Specify that debug information should be displayed on the Console."); AppendLineBreak(LineBreakCharacter); } /// <summary> /// Display SiLA2 device info for the PIXL. /// </summary> private static void DisplaySiLA2DeviceInfo() { // read properties from SiLAService feature Console.WriteLine("SiLA server description: " + PIXL.ServerDescription); Console.WriteLine("SilA server vendor URL: " + PIXL.ServerVendorUrl); Console.WriteLine("SiLA server version: " + PIXL.ServerVersion); } /// <summary> /// Display info about the PIXL. /// </summary> private static void DisplayInfo() { LogInfo("DEVICE INFO:"); LogInfo($"{ChildElementPrefex}Serial number: {PIXL.SerialNumber}"); LogInfo($"{ChildElementPrefex}Software Version: {PIXL.SoftwareVersion}"); LogInfo($"{ChildElementPrefex}Server Description: {PIXL.ServerDescription}"); LogInfo($"{ChildElementPrefex}Server Vendor Url: {PIXL.ServerVendorUrl}"); LogInfo($"{ChildElementPrefex}Server Version: {PIXL.ServerVersion}"); LogInfo($"{ChildElementPrefex}Is Door Closed: {PIXL.IsDoorClosed}"); LogInfo($"{ChildElementPrefex}Is Door Locked: {PIXL.IsDoorLocked}"); LogInfo("STATUS INFO"); LogInfo($"{ChildElementPrefex}Command Name: {PIXL.OperationalStatus.CommandName}"); LogInfo($"{ChildElementPrefex}Command Status: {PIXL.OperationalStatus.CommandStatus}"); LogInfo($"{ChildElementPrefex}Command Percentage Complete: {Math.Round(PIXL.OperationalStatus.ProgressAsPercentage, 1)}%"); LogInfo($"{ChildElementPrefex}Command Estimated Remaining Time: {PIXL.OperationalStatus.EstimatedRemainingTime}"); LogInfo("INITIALISED INFO:"); LogInfo($"{ChildElementPrefex}Is Initialised: {PIXL.IsInitialised}"); LogInfo("COMSUMABLES INFO:"); LogInfo($"{ChildElementPrefex}UV Bulb Total Time: {PIXL.UVBulbTotalDuration}"); LogInfo($"{ChildElementPrefex}UV Bulb Remaining Time: {PIXL.UVBulbRemainingDuration}"); LogInfo($"{ChildElementPrefex}Cleaves on current blade: {PIXL.CleavesOnCurrentBlade}"); LogInfo($"{ChildElementPrefex}Cleaves remaining on current blade: {PIXL.RemainingCleavesOnCurrentBlade}"); LogInfo($"{ChildElementPrefex}PickupLine used (m): {PIXL.PickupLineUsedInMeters}"); LogInfo($"{ChildElementPrefex}PickupLine remaining (m): {PIXL.PickupLineRemainingInMeters}"); LogInfo($"{ChildElementPrefex}Allow workflow execution with insufficient PickupLine: {PIXL.IsWorkflowExecutionAllowedWithInsufficientPickupLine}"); LogInfo("POWER INFO:"); LogInfo($"{ChildElementPrefex}Is Asleep: {PIXL.IsAsleep}"); LogInfo($"{ChildElementPrefex}Can Sleep: {PIXL.CanSleep}"); LogInfo($"{ChildElementPrefex}Can Awaken: {PIXL.CanAwaken}"); } /// <summary> /// Display the commands list. /// </summary> private static void DisplayCommandsList() { LogInfo("ABORT Abort any running operation"); LogInfo("AWAKEN Awaken the PIXL"); LogInfo("BAYSTATUS Display the status of each bay"); LogInfo("DETERMINEPOSITION Determine the next available position given a project template and a row and column [TEMPLATE] [ROW] [COLUMN]"); LogInfo("EXIT Exit the client"); LogInfo("HELP View command list"); LogInfo("INFO Display information about the PIXL"); LogInfo("INITIALISE Initialise the PIXL"); LogInfo("LOADED Notify the PIXL that a plate has been loaded in the format [BAY] [ID] [TYPE] [ROLE]"); LogInfo("LISTPLATES Lists the available plates and their available plate dimension file names"); LogInfo("LISTPROFILES Lists the available pinning profiles"); LogInfo("LISTTEMPLATES Lists the available project templates"); LogInfo("MOTORISEDDOORCLOSE Close the motorised door"); LogInfo("MOTORISEDDOOROPEN Open the motorised door"); LogInfo("REMOVED Notify the PIXL that a plate has been removed in the format [BAY]"); LogInfo("REMOVEDALL Notify the PIXL that all plates have been removed"); LogInfo("RESET Reset the PIXL"); LogInfo("RESTART Restart the PIXL"); LogInfo("RUNCD Run an example of the colony detection protocol"); LogInfo("RUNRCP Run an example of the random colony picking protocol"); LogInfo("RUNREARRAY Run an example of the re-array protocol"); LogInfo("RUNREARRAYFILE Run an example of the re-array protocol from a file a specified number of times [PATH] [ITERATIONS]"); LogInfo("RUNUV Run the UV sterilisation with a specified duration in [MINUTES]"); LogInfo("SLEEP Put the PIXL to sleep"); LogInfo("SHUTDOWN Shutdown the PIXL"); } /// <summary> /// Display the available plates. /// </summary> private static void DisplayAvailablePlates() { // display header LogInfo("AVAILABLE PLATE TYPES:"); // hold plate types var plateTypes = new[] { PlateTypes.PlusPlate, PlateTypes.PlusPlate_6, PlateTypes.PlusPlate_12, PlateTypes.PlusPlate_24, PlateTypes.PlusPlate_48, PlateTypes.PlusPlate_96, PlateTypes.PlusPlate_384, PlateTypes.PlusPlate_1536, PlateTypes.Petri_90, PlateTypes.Petri_150, PlateTypes.DeepMWP_6, PlateTypes.DeepMWP_12, PlateTypes.DeepMWP_24, PlateTypes.DeepMWP_48, PlateTypes.DeepMWP_96, PlateTypes.DeepMWP_384, PlateTypes.MWP_6, PlateTypes.MWP_12, PlateTypes.MWP_24, PlateTypes.MWP_48, PlateTypes.MWP_96, PlateTypes.MWP_384, PlateTypes.PCR_96_200ul, PlateTypes.PCR_96_300ul, PlateTypes.PCR_384_40ul }; // iterate plate types foreach (var platetype in plateTypes) { // get all dimension names var plateDimensionNames = PIXL.GetAvailablePlateDimensionFileNames(platetype); // display type on console LogInfo($"{ChildElementPrefex}{platetype}"); // display all available plate dimensions on console foreach (var profile in plateDimensionNames) LogInfo($"{ChildElementPrefex}{ChildElementPrefex}{profile}"); } } /// <summary> /// Display the available pinning profiles. /// </summary> private static void DisplayAvailablePinningProfiles() { // get all profiles var profiles = PIXL.AvailablePinningProfiles; // display header LogInfo("AVAILABLE PINNING PROFILES:"); // display on console foreach (var profile in profiles) LogInfo($"{ChildElementPrefex}{profile}"); } /// <summary> /// Display the available project templates. /// </summary> private static void DisplayAvailableProjectTemplates() { // get all random colony picking templates var templates = PIXL.AvailableRandomColonyPickingProjectTemplates; // display header LogInfo("AVAILABLE RANDOM COLONY PICKING PROJECT TEMPLATES:"); // display on console foreach (var template in templates) LogInfo($"{ChildElementPrefex}{template}"); // get all re-array templates templates = PIXL.AvailableRearrayProjectTemplates; // display header LogInfo("AVAILABLE RE-ARRAY PROJECT TEMPLATES:"); // display on console foreach (var template in templates) LogInfo($"{ChildElementPrefex}{template}"); // get all colony detection templates templates = PIXL.AvailableColonyDetectionProjectTemplates; // display header LogInfo("AVAILABLE COLONY DETECTION PROJECT TEMPLATES:"); // display on console foreach (var template in templates) LogInfo($"{ChildElementPrefex}{template}"); } #endregion #region ExceptionHandling /// <summary> /// Handle a gRPC exception. /// </summary> /// <param name="e">The exception to handle.</param> private static void HandleRPCException(RpcException e) { LogError(ErrorHandling.HandleException(e)); } #endregion #region EventHandlers /// <summary> /// Handle PIXL debug information received. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The debugging information.</param> private static void PIXL_DebugInformationReceived(object sender, string e) { // log the debugging information LogDebugInfo(e); } #endregion } } |