chr
2026-04-05 fe750b791d5b517cc4e9bc8e99a9a75139a0cfba
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//            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<string> 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<string> 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<string>() { IsEnabled = false, Value = "(.*)" };
            VerdictOnMatch = Verdict.Pass;
            VerdictOnNoMatch = Verdict.Fail;
 
            ResultRegularExpressionPattern = new Enabled<string>() { 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<Capture>().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);
            }
        }
    }
}