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