// Copyright Keysight Technologies 2012-2019 // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, you can obtain one at http://mozilla.org/MPL/2.0/. using System; using System.ComponentModel; using System.Linq; using System.Text.RegularExpressions; using System.Xml.Serialization; namespace OpenTap.Plugins.BasicSteps { [Display("Generic SCPI Instrument", Description: "Allows you to configure a VISA based connection to a SCPI instrument.")] [Obsolete ("Use OpenTap.ScpiInstrument instead.")] [Browsable(false)] public class GenericScpiInstrument : ScpiInstrument { public GenericScpiInstrument() { Name = "SCPI"; } } public enum SCPIAction { [Display("Command", "SCPI Command.")] Command, [Display("Query", "SCPI Query.")] Query } public enum SCPIRegexBehavior { [Display("Groups As Columns", "Each regex group is treated as a separate column.")] GroupsAsDimensions, [Display("Groups As Rows", "Each regex group is treated as a separate row, with one column.")] GroupsAsResults } public abstract class RegexOutputStep : TestStep { [Browsable(false)] [XmlIgnore] public abstract bool GeneratesOutput { get; } private static bool IsValidRegex(string pattern) { if (string.IsNullOrEmpty(pattern)) return false; try { Regex.Match("", pattern); } catch (ArgumentException) { return false; } return true; } [EnabledIf(nameof(GeneratesOutput), true, HideIfDisabled = true)] [Display("Regular Expression", Group: "Set Verdict", Order: 1.1, Collapsed: true, Description: "The regular expression to apply to the output.")] [HelpLink("EditorHelp.chm::/CreatingATestPlan/Working with Test Steps/Using Regex in Output Parameters.html")] public Enabled RegularExpressionPattern { get; set; } [EnabledIf(nameof(GeneratesOutput), true, HideIfDisabled = true)] [EnabledIf("RegularExpressionPattern", true, HideIfDisabled = true)] [Display("Verdict on Match", Group: "Set Verdict", Order: 1.2, Collapsed: true, Description: "The verdict of the step when the regex did match the result.")] public Verdict VerdictOnMatch { get; set; } [EnabledIf(nameof(GeneratesOutput), true, HideIfDisabled = true)] [EnabledIf("RegularExpressionPattern", true, HideIfDisabled = true)] [Display("Verdict on No Match", Group: "Set Verdict", Order: 1.3, Collapsed: true, Description: "The verdict of the step when the regex did not match the result.")] public Verdict VerdictOnNoMatch { get; set; } [EnabledIf(nameof(GeneratesOutput), true, HideIfDisabled = true)] [Display("Regular Expression", Group: "Results", Order: 1.5, Collapsed: true, Description: "The regular expression to apply to the output.")] [HelpLink("EditorHelp.chm::/CreatingATestPlan/Working with Test Steps/Using Regex in Output Parameters.html")] public Enabled ResultRegularExpressionPattern { get; set; } [EnabledIf(nameof(GeneratesOutput), true, HideIfDisabled = true)] [EnabledIf(nameof(ResultRegularExpressionPattern), true, HideIfDisabled = true)] [Display("Result Name", Group: "Results", Order: 1.51, Collapsed: true, Description: "The name of the result.")] public string ResultName { get; set; } [EnabledIf(nameof(GeneratesOutput), true, HideIfDisabled = true)] [EnabledIf(nameof(ResultRegularExpressionPattern), true, HideIfDisabled = true)] [Display("Regex Behavior", Group: "Results", Order: 1.51, Collapsed: true, Description: "How the step should publish the matched values of the regular expression as a result table.")] public SCPIRegexBehavior Behavior { get; set; } [EnabledIf(nameof(GeneratesOutput), true, HideIfDisabled = true)] [EnabledIf(nameof(ResultRegularExpressionPattern), true, HideIfDisabled = true)] [Display("Column Names", Group: "Results", Order: 1.51, Collapsed: true, Description: "The name of the columns of the resulting groups. The titles must be separated by commas.")] public string DimensionTitles { get; set; } public RegexOutputStep() { RegularExpressionPattern = new Enabled() { IsEnabled = false, Value = "(.*)" }; VerdictOnMatch = Verdict.Pass; VerdictOnNoMatch = Verdict.Fail; ResultRegularExpressionPattern = new Enabled() { IsEnabled = false, Value = "(.*)" }; Behavior = SCPIRegexBehavior.GroupsAsDimensions; ResultName = "Regex Result"; DimensionTitles = ""; Rules.Add(new ValidationRule(() => ResultRegularExpressionPattern.IsEnabled == false || IsValidRegex(ResultRegularExpressionPattern.Value), "Invalid regular expression.", nameof(ResultRegularExpressionPattern))); Rules.Add(new ValidationRule(() => RegularExpressionPattern.IsEnabled == false || IsValidRegex(RegularExpressionPattern.Value), "Invalid regular expression.", "RegularExpressionPattern")); } protected void ProcessOutput(string output) { if (RegularExpressionPattern.IsEnabled) { var Matches = Regex.Matches(output, RegularExpressionPattern.Value); if (Matches.Count > 0) UpgradeVerdict(VerdictOnMatch); else UpgradeVerdict(VerdictOnNoMatch); } if (ResultRegularExpressionPattern.IsEnabled) { var Matches = Regex.Matches(output, ResultRegularExpressionPattern.Value); foreach (Match Match in Matches) { if ((Match.Length <= 0) || (!Match.Success)) continue; switch (Behavior) { case SCPIRegexBehavior.GroupsAsDimensions: { var Name = ResultName; var titles = DimensionTitles.Split(',').ToList(); var results = Match.Groups.OfType().Skip(1).Select(x => x.Value).ToList(); if (titles.Count != results.Count) { Log.Error("Number of Column Names ({0}) does not match number of results ({1}).", titles.Count, results.Count); UpgradeVerdict(Verdict.Error); return; } Results.Publish(Name, titles, results.ToArray()); break; } case SCPIRegexBehavior.GroupsAsResults: { var Name = ResultName; var titles = DimensionTitles.Split(',').ToList(); if (titles.Count != 1) { Log.Error("Number of Column Names ({0}) does not match number of results (1).", titles.Count); UpgradeVerdict(Verdict.Error); return; } for (int i = 1; i < Match.Groups.Count; i++) { Results.Publish(Name, titles, Match.Groups[i].Value); } break; } } } } } } [Browsable(false)] [Display("SCPI", Group: "Basic Steps", Description: "Sends a SCPI (Standard Commands for Programmable Instruments) command or query to a SCPI instrument. For queries, it processes the result with regular expressions.")] public class SCPIRegexStep : RegexOutputStep { [Browsable(false)] [XmlIgnore] public override bool GeneratesOutput => Action == SCPIAction.Query; [Display("Instrument", Order: 0.0, Description: "The instrument that the query is sent to.")] public ScpiInstrument Instrument { get; set; } [Display("Action", Order: 0.1, Description: "The type of SCPI action to perform.")] public SCPIAction Action { get; set; } [Display("Command", Order: 0.2, Description: "The command or query to send to the instrument.")] public string Query { get; set; } [EnabledIf(nameof(Action), SCPIAction.Query)] [Display("Add to Log", Order: 0.3, Description: "If enabled the result of the query is added to the log.")] public bool AddToLog { get; set; } [EnabledIf(nameof(AddToLog), true, HideIfDisabled = true)] [EnabledIf(nameof(Action), SCPIAction.Query, HideIfDisabled = true)] [Display("Log Header", Order: 0.4, Description: "This string is added to the front of the result of the query.")] public string LogHeader { get; set; } [Browsable(true)] [Display("Response", "The text response from the instrument. This is only valid when the action type is query.", Group: "Results", Order: 1.55 )] [EnabledIf(nameof(Action), SCPIAction.Query, HideIfDisabled = true)] public string Response { get; private set; } public SCPIRegexStep() { Action = SCPIAction.Query; AddToLog = true; Rules.Add(() => !string.IsNullOrEmpty(Query), "Command cannot be empty.", nameof(Query)); Rules.Add(() => Instrument != null, "An instrument must be selected.", nameof(Instrument)); Rules.Add(() => !(Query?.EndsWith("?") == true && Action == SCPIAction.Command), "A command cannot end with '?'", nameof(Query)); // Queries may contain arguments e.g 'SYST:CHAN:MOD? (@1,2)' Rules.Add(() => !(Query?.Contains("?") == false && Action == SCPIAction.Query), "A query must contain '?'", nameof(Query)); Query = "*IDN?"; } public override void Run() { Response = ""; if (Action == SCPIAction.Command) { Instrument.ScpiCommand(Query); } else { string Result = Instrument.ScpiQuery(Query); Response = Result; if (AddToLog) Log.Info(LogHeader + Result); ProcessOutput(Result); } } } }