1 //
2 // Copyright (c) 2010-2025 Antmicro
3 //
4 // This file is licensed under the MIT License.
5 // Full license text is available in 'licenses/MIT.txt'.
6 //
7 using System;
8 using System.Linq;
9 using System.Text;
10 using Antmicro.Renode.Peripherals.UART;
11 using Antmicro.Renode.Testing;
12 using Antmicro.Renode.Time;
13 using Antmicro.Renode.Utilities;
14 
15 namespace Antmicro.Renode.RobotFramework
16 {
17     internal class UartKeywords : TestersProvider<TerminalTester, IUART>, IRobotFrameworkKeywordProvider
18     {
Dispose()19         public void Dispose()
20         {
21         }
22 
23         [RobotFrameworkKeyword(replayMode: Replay.Always)]
SetDefaultUartTimeout(float timeout)24         public void SetDefaultUartTimeout(float timeout)
25         {
26             globalTimeout = timeout;
27         }
28 
29         [RobotFrameworkKeyword(replayMode: Replay.Always)]
SetDefaultTester(int? id)30         public void SetDefaultTester(int? id)
31         {
32             SetDefaultTesterId(id);
33         }
34 
35         [RobotFrameworkKeyword]
GetTerminalTesterReport(int? testerId = null)36         public string GetTerminalTesterReport(int? testerId = null)
37         {
38             return GetTesterOrThrowException(testerId).GetReport();
39         }
40 
41         [RobotFrameworkKeyword]
ClearTerminalTesterReport(int? testerId = null)42         public void ClearTerminalTesterReport(int? testerId = null)
43         {
44             GetTesterOrThrowException(testerId).ClearReport();
45         }
46 
47         [RobotFrameworkKeyword(replayMode: Replay.Always)]
CreateTerminalTester(string uart, float? timeout = null, string machine = null, string endLineOption = null, bool? defaultPauseEmulation = null, bool? defaultWaitForEcho = null, bool? defaultMatchNextLine = null, bool binaryMode = false)48         public int CreateTerminalTester(string uart, float? timeout = null, string machine = null, string endLineOption = null,
49             bool? defaultPauseEmulation = null, bool? defaultWaitForEcho = null, bool? defaultMatchNextLine = null, bool binaryMode = false)
50         {
51             this.defaultPauseEmulation = defaultPauseEmulation.GetValueOrDefault();
52             this.defaultMatchNextLine = defaultMatchNextLine.GetValueOrDefault();
53             this.defaultWaitForEcho = defaultWaitForEcho ?? true;
54 
55             return CreateNewTester(uartObject =>
56             {
57                 var timeoutInSeconds = timeout ?? globalTimeout;
58 
59                 TerminalTester tester;
60                 if(Enum.TryParse<EndLineOption>(endLineOption, out var result))
61                 {
62                     tester = new TerminalTester(TimeInterval.FromSeconds(timeoutInSeconds), result, binaryMode: binaryMode);
63                 }
64                 else
65                 {
66                     tester = new TerminalTester(TimeInterval.FromSeconds(timeoutInSeconds), binaryMode: binaryMode);
67                 }
68                 tester.AttachTo(uartObject);
69                 return tester;
70             }, uart, machine);
71         }
72 
73         [RobotFrameworkKeyword]
WaitForPromptOnUart(string prompt, int? testerId = null, float? timeout = null, bool treatAsRegex = false, bool? pauseEmulation = null)74         public TerminalTesterResult WaitForPromptOnUart(string prompt, int? testerId = null, float? timeout = null, bool treatAsRegex = false,
75             bool? pauseEmulation = null)
76         {
77             return WaitForLineOnUart(prompt, timeout, testerId, treatAsRegex, true, pauseEmulation);
78         }
79 
80         [RobotFrameworkKeyword]
WaitForLineOnUart(string content, float? timeout = null, int? testerId = null, bool treatAsRegex = false, bool includeUnfinishedLine = false, bool? pauseEmulation = null, bool? matchNextLine = null)81         public TerminalTesterResult WaitForLineOnUart(string content, float? timeout = null, int? testerId = null, bool treatAsRegex = false,
82             bool includeUnfinishedLine = false, bool? pauseEmulation = null, bool? matchNextLine = null)
83         {
84             return DoTest(timeout, testerId, (tester, timeInterval) =>
85             {
86                 return tester.WaitFor(content, timeInterval, treatAsRegex, includeUnfinishedLine,
87                 pauseEmulation ?? defaultPauseEmulation, matchNextLine ?? defaultMatchNextLine);
88             });
89         }
90 
91         [RobotFrameworkKeyword]
WaitForLinesOnUart(string[] content, float? timeout = null, int? testerId = null, bool treatAsRegex = false, bool includeUnfinishedLine = false, bool? pauseEmulation = null, bool? matchFromNextLine = null)92         public TerminalTesterResult WaitForLinesOnUart(string[] content, float? timeout = null, int? testerId = null, bool treatAsRegex = false,
93             bool includeUnfinishedLine = false, bool? pauseEmulation = null, bool? matchFromNextLine = null)
94         {
95             return DoTest(timeout, testerId, (tester, timeInterval) =>
96             {
97                 return tester.WaitFor(content, timeInterval, treatAsRegex, includeUnfinishedLine,
98                     pauseEmulation ?? defaultPauseEmulation, matchFromNextLine ?? defaultMatchNextLine);
99             });
100         }
101 
102         [RobotFrameworkKeyword]
WaitForBytesOnUart(string content, float? timeout = null, int? testerId = null, bool treatAsRegex = false, bool? pauseEmulation = null, bool? matchStart = false)103         public BinaryTerminalTesterResult WaitForBytesOnUart(string content, float? timeout = null, int? testerId = null, bool treatAsRegex = false,
104             bool? pauseEmulation = null, bool? matchStart = false)
105         {
106             return DoTest(timeout, testerId, (tester, timeInterval) =>
107             {
108                 var result = tester.WaitFor(content, timeInterval, treatAsRegex, includeUnfinishedLine: true,
109                     pauseEmulation ?? defaultPauseEmulation, matchStart ?? defaultMatchNextLine);
110 
111                 return result != null ? new BinaryTerminalTesterResult(result) : null;
112             }, expectBinaryModeTester: true);
113         }
114 
115         [RobotFrameworkKeyword]
ShouldNotBeOnUart(string content, float? timeout = null, int? testerId = null, bool treatAsRegex = false, bool includeUnfinishedLine = false)116         public void ShouldNotBeOnUart(string content, float? timeout = null, int? testerId = null, bool treatAsRegex = false,
117             bool includeUnfinishedLine = false)
118         {
119             TimeInterval? timeInterval = null;
120             if(timeout.HasValue)
121             {
122                 timeInterval = TimeInterval.FromSeconds(timeout.Value);
123             }
124 
125             var tester = GetTesterOrThrowException(testerId);
126             var result = tester.WaitFor(content, timeInterval, treatAsRegex, includeUnfinishedLine, false);
127             if(result != null)
128             {
129                 throw new InvalidOperationException($"Terminal tester failed!\n\nUnexpected entry has been found on UART#:\n{result.line}");
130             }
131         }
132 
133         [RobotFrameworkKeyword]
WaitForNextLineOnUart(float? timeout = null, int? testerId = null, bool? pauseEmulation = null)134         public TerminalTesterResult WaitForNextLineOnUart(float? timeout = null, int? testerId = null, bool? pauseEmulation = null)
135         {
136             return DoTest(timeout, testerId, (tester, timeInterval) =>
137             {
138                 return tester.NextLine(timeInterval, pauseEmulation ?? defaultPauseEmulation);
139             });
140         }
141 
142         [RobotFrameworkKeyword]
SendKeyToUart(byte c, int? testerId = null)143         public TerminalTesterResult SendKeyToUart(byte c, int? testerId = null)
144         {
145             return WriteCharOnUart((char)c, testerId);
146         }
147 
148         [RobotFrameworkKeyword]
WriteCharOnUart(char c, int? testerId = null)149         public TerminalTesterResult WriteCharOnUart(char c, int? testerId = null)
150         {
151             GetTesterOrThrowException(testerId).Write(c.ToString());
152             return new TerminalTesterResult(string.Empty, 0);
153         }
154 
155         [RobotFrameworkKeyword]
WriteToUart(string content, int? testerId = null)156         public TerminalTesterResult WriteToUart(string content, int? testerId = null)
157         {
158             GetTesterOrThrowException(testerId).Write(content);
159             return new TerminalTesterResult(string.Empty, 0);
160         }
161 
162         [RobotFrameworkKeyword]
WriteLineToUart(string content = R, int? testerId = null, bool? waitForEcho = null, bool? pauseEmulation = null)163         public TerminalTesterResult WriteLineToUart(string content = "", int? testerId = null, bool? waitForEcho = null, bool? pauseEmulation = null)
164         {
165             var tester = GetTesterOrThrowException(testerId);
166             tester.WriteLine(content);
167             if((waitForEcho ?? defaultWaitForEcho) && tester.WaitFor(content, includeUnfinishedLine: true, pauseEmulation: pauseEmulation ?? defaultPauseEmulation) == null)
168             {
169                 OperationFail(tester);
170             }
171             return new TerminalTesterResult(string.Empty, 0);
172         }
173 
174         [RobotFrameworkKeyword]
TestIfUartIsIdle(float timeout, int? testerId = null, bool? pauseEmulation = null)175         public void TestIfUartIsIdle(float timeout, int? testerId = null, bool? pauseEmulation = null)
176         {
177             var tester = GetTesterOrThrowException(testerId);
178             var result = tester.IsIdle(TimeInterval.FromSeconds(timeout), pauseEmulation ?? defaultPauseEmulation);
179             if(!result)
180             {
181                 OperationFail(tester);
182             }
183         }
184 
185         [RobotFrameworkKeyword]
WriteCharDelay(float delay, int? testerId = null)186         public void WriteCharDelay(float delay, int? testerId = null)
187         {
188             var tester = GetTesterOrThrowException(testerId);
189             tester.WriteCharDelay = TimeSpan.FromSeconds(delay);
190         }
191 
DoTest(float? timeout, int? testerId, Func<TerminalTester, TimeInterval?, T> test, bool expectBinaryModeTester = false)192         private T DoTest<T>(float? timeout, int? testerId, Func<TerminalTester, TimeInterval?, T> test, bool expectBinaryModeTester = false)
193         {
194             TimeInterval? timeInterval = null;
195             if(timeout.HasValue)
196             {
197                 timeInterval = TimeInterval.FromSeconds(timeout.Value);
198             }
199 
200             var tester = GetTesterOrThrowException(testerId);
201             if(tester.BinaryMode != expectBinaryModeTester)
202             {
203                 var waitedThing = expectBinaryModeTester ? "bytes" : "text";
204                 var testerMode = tester.BinaryMode ? "binary" : "text";
205                 throw new InvalidOperationException($"Attempt to wait for {waitedThing} on a tester configured in {testerMode} mode. " +
206                         $"Please set binaryMode={!tester.BinaryMode} when creating the tester.");
207             }
208 
209             var result = test(tester, timeInterval);
210             if(result == null)
211             {
212                 OperationFail(tester);
213             }
214             return result;
215         }
216 
OperationFail(TerminalTester tester)217         private void OperationFail(TerminalTester tester)
218         {
219             throw new InvalidOperationException($"Terminal tester failed!\n\nFull report:\n{tester.GetReport()}");
220         }
221 
222         private bool defaultPauseEmulation;
223         private bool defaultWaitForEcho;
224         private bool defaultMatchNextLine;
225         private float globalTimeout = 8;
226 
227         // The 'binary strings' used internally are not safe to pass through XML-RPC, probably due to special character escaping
228         // issues. See https://github.com/antmicro/renode/commit/7739c14c6275058e71da30997c8e0f80144ed81c
229         // and Misc.StripNonSafeCharacters. Here we represent the results as byte[] which get represented as base64 in the
230         // XML-RPC body and <class 'bytes'> in the Python client.
231         public class BinaryTerminalTesterResult
232         {
BinaryTerminalTesterResult(TerminalTesterResult result)233             public BinaryTerminalTesterResult(TerminalTesterResult result)
234             {
235                 this.content = Encode(result.line) ?? Array.Empty<byte>();
236                 this.timestamp = timestamp;
237                 this.groups = result.groups.Select(Encode).ToArray();
238             }
239 
240             public byte[] content { get; }
241             public double timestamp { get; }
242             public byte[][] groups { get; }
243 
Encode(string str)244             private byte[] Encode(string str)
245             {
246                 if(str == null)
247                 {
248                     return null;
249                 }
250                 // Encode using the Latin1 encoding, which maps each character directly to its
251                 // corresponding byte value (that is, "\x00\x80\xff" becomes { 0x00, 0x80, 0xff }).
252                 return Encoding.GetEncoding("iso-8859-1").GetBytes(str);
253             }
254         }
255     }
256 }
257