diff --git a/.gitignore b/.gitignore index 5d6a9ca..60bc0ac 100644 --- a/.gitignore +++ b/.gitignore @@ -452,3 +452,8 @@ $RECYCLE.BIN/ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json + + +# My own files to ignore +*.ssm +*.bin diff --git a/.vscode/launch.json b/.vscode/launch.json index 4f33f50..2247a8d 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "preLaunchTask": "build", // If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/RS485 Monitor/bin/Debug/net8.0/RS485 Monitor.dll", - "args": [""], + "args": ["-w"], "cwd": "${workspaceFolder}", // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console "console": "internalConsole", diff --git a/RS485 Monitor/src/CmdOptions.cs b/RS485 Monitor/src/CmdOptions.cs index e8da053..b553e2e 100644 --- a/RS485 Monitor/src/CmdOptions.cs +++ b/RS485 Monitor/src/CmdOptions.cs @@ -6,11 +6,14 @@ public class CmdOptions { [Value(0, MetaName = "InputFile", HelpText = "File to parse")] - public String? InputFile { get; set; } + public string? InputFile { get; set; } [Option('r', "replay", HelpText = "Replay data on serial port", Default = false)] - public bool replayOnSerial { get; set; } + public bool ReplayOnSerial { get; set; } [Option('g', "group", HelpText = "Group telegrams in output", Default = false)] - public bool groupOutput { get; set; } -} \ No newline at end of file + public bool GroupOutput { get; set; } + + [Option('w', "write-to-file", HelpText = "Write telegrams including timestamp to a file", Default = false)] + public bool WriteToFile { get; set; } +} diff --git a/RS485 Monitor/src/Program.cs b/RS485 Monitor/src/Program.cs index 09a3b73..5a0ca42 100644 --- a/RS485 Monitor/src/Program.cs +++ b/RS485 Monitor/src/Program.cs @@ -2,6 +2,10 @@ using NLog.Extensions.Logging; using Microsoft.Extensions.Configuration; using CommandLine; +using RS485_Monitor.Utils.Storage; +using System.Runtime.CompilerServices; + +const string FILE_EXTENSION = ".ssm"; // Configuration var config = new ConfigurationBuilder() @@ -10,25 +14,26 @@ var sec = config.GetSection("NLog"); LogManager.Configuration = new NLogLoggingConfiguration(sec); -NLog.Logger log = LogManager.GetCurrentClassLogger(); +Logger log = LogManager.GetCurrentClassLogger(); Configuration cfg = new(config.GetSection("Monitor")); -// Commandline parser -String? parseFile = null; +// command line parser +string? parseFile = null; bool replayToSerial = false; bool groupOutput = false; - +bool writeToFile = false; var result = Parser.Default.ParseArguments(args) - .WithParsed(o => + .WithParsed(o => { if (o.InputFile != null) { parseFile = o.InputFile; } - replayToSerial = o.replayOnSerial; - groupOutput = o.groupOutput; + replayToSerial = o.ReplayOnSerial; + groupOutput = o.GroupOutput; + writeToFile = o.WriteToFile; } ); @@ -40,15 +45,7 @@ } // Configure output -IUserVisualizable printer; -if (groupOutput) -{ - printer = new ConsolePrinter(); -} -else -{ - printer = new LogPrinter(); -} +IUserVisualizable printer = groupOutput ? new ConsolePrinter() : new LogPrinter(); // Select execution if (parseFile != null) @@ -63,6 +60,83 @@ StartMonitor(cfg); } +/// +/// Start the serial monitor +/// +void StartMonitor(Configuration cfg) +{ + // Semaphore for ending the monitor + Semaphore endMonitor = new(0, 1); + + // Register CTRL+C + Console.CancelKeyPress += new ConsoleCancelEventHandler((sender, args) => + { + log.Info("Exiting ..."); + + // Set cancel to true to prevent default CTRL+C behaviour + args.Cancel = true; + + // release semaphore + endMonitor.Release(); + }); + + log.Info($"Starting Serial Monitor on port {cfg.ComPort}"); + + // create telegram exporter + TelegramExporter? exporter = null; + if (writeToFile) + { + FileInfo? outputFile = null; + string filename = DateTime.Now.ToString("yyyyMMdd_HHmmss") + "_telegram" + FILE_EXTENSION; + + // Determine full output file path + if (cfg.OutputDir != null) + { + outputFile = new(cfg.OutputDir + "/" + filename); + } + else + { + outputFile = new(filename); + } + + // create exporter + exporter = new(outputFile.OpenWrite()); + } + + // Start serial monitor on the given port + using (SerialMonitor monitor = new(cfg.ComPort, cfg.WriteRawData)) + { + if (cfg.OutputDir != null) + { + monitor.OutputDir = cfg.OutputDir; + } + monitor.TelegramReceived += (o, e) => + { + // Print the received telegram + printer.PrintTelegram(e.Telegram); + exporter?.PushTelegram(e.Telegram); + }; + + // Start reading monitor + try + { + monitor.Start(); + } + catch (IOException ex) + { + log.Fatal(ex, "Could not start monitor"); + return; + } + + // Wait for CTRL+C + endMonitor.WaitOne(); + } + log.Info("Monitor stopped"); + + // close exporter + exporter?.Dispose(); +} + /// /// Read a local file and parse it @@ -72,7 +146,7 @@ async Task ReadLocalFile(String file) TelegramParser parser = new(); TelegramPlayer player = new(cfg.ReplayCycle); - // Register new telegram handler + // Register new telegram handler parser.NewTelegram += (sender, evt) => { BaseTelegram telegram = ((TelegramParser.TelegramArgs)evt).Telegram; @@ -105,49 +179,6 @@ async Task ReadLocalFile(String file) } -/// -/// Start the serial monitor -/// -void StartMonitor(Configuration cfg) -{ - log.Info($"Starting Serial Monitor on port {cfg.ComPort}"); - - SerialMonitor monitor = new(cfg.ComPort, cfg.WriteRawData); - if (cfg.OutputDir != null) - { - monitor.OutputDir = cfg.OutputDir; - } - monitor.TelegramReceived += (o, e) => - { - // Print the received telegram - printer.PrintTelegram(e.Telegram); - }; - - // Register CTRL+C - Console.CancelKeyPress += delegate - { - log.Info("Exiting ..."); - monitor.Stop(); - }; - - // Start reading monitor - try - { - monitor.Start(); - } - catch (System.IO.IOException ex) - { - log.Fatal(ex, "Could not start monitor"); - return; - } - - // Endless loop - while (monitor.Running) - { - Thread.Sleep(10); - } -} - /// /// Read a local file and parse it diff --git a/RS485 Monitor/src/SerialMonitor.cs b/RS485 Monitor/src/SerialMonitor.cs index 8cff6c4..b60da8e 100644 --- a/RS485 Monitor/src/SerialMonitor.cs +++ b/RS485 Monitor/src/SerialMonitor.cs @@ -2,13 +2,11 @@ using System.IO.Ports; using NLog; -using System.Diagnostics; -using System.Text; /// /// Class reading data from the serial interface and parsing using the TelegramParser. /// -public class SerialMonitor +public class SerialMonitor : IDisposable { #region Private Members /// @@ -56,7 +54,7 @@ public class SerialMonitor /// private const int BAUDRATE = 9600; /// - /// Maximum size of the buffer / chunks + /// Maximum size of the buffer / chunks /// private const int BUFFER_SIZE = 256; #endregion @@ -85,11 +83,12 @@ public SerialMonitor(string comPort, bool writeRawData = false) parser = new(); parser.NewTelegram += (o, t) => { - BaseTelegram? tel = (t as TelegramParser.TelegramArgs)?.Telegram; + var args = t as TelegramParser.TelegramArgs; - if (tel != null && TelegramReceived != null) + if (args !=null) { - TelegramReceived.Invoke(this, new TelegramParser.TelegramArgs(tel)); + // Forward the telegram + TelegramReceived?.Invoke(this, args); } }; } @@ -111,7 +110,7 @@ private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs args } buffer = new byte[bytesToRead]; - // Read data + // Read data int readBytes = port.Read(buffer, 0, bytesToRead); if (readBytes != bytesToRead) @@ -163,4 +162,9 @@ public void Stop() Running = false; } -} \ No newline at end of file + + public void Dispose() + { + Stop(); + } +} diff --git a/RS485 Monitor/src/TelegramParser.cs b/RS485 Monitor/src/TelegramParser.cs index 88de773..d5ddca1 100644 --- a/RS485 Monitor/src/TelegramParser.cs +++ b/RS485 Monitor/src/TelegramParser.cs @@ -1,4 +1,5 @@ using NLog; +using RS485Monitor.Telegrams; /// /// Class extracting telegrams from byte streams. @@ -199,23 +200,8 @@ private void FinishBlock() return null; } - // Define known telegrams - Dictionary knownTelegrams = new() - { - {ControllerRequest.TELEGRAM_ID, typeof(ControllerRequest) }, - {ControllerResponse.TELEGRAM_ID, typeof(ControllerResponse) }, - {BatteryRequest.TELEGRAM_ID, typeof(BatteryRequest) }, - {BatteryResponse.TELEGRAM_ID, typeof(BatteryResponse) }, - {SpeedometerRequest.TELEGRAM_ID, typeof(SpeedometerRequest) }, - {SpeedometerResponse.TELEGRAM_ID, typeof(SpeedometerResponse) }, - }; - - // try to fetch the special telegram type - if (knownTelegrams.TryGetValue(tg.Id, out Type? specialType)) - { - tg = (BaseTelegram?)Activator.CreateInstance(specialType, [tg]); - } - + // try to specialize the telegram + tg = TelegramSpecializer.Specialize(tg); return tg; } } diff --git a/RS485 Monitor/src/Telegrams/BaseTelegram.cs b/RS485 Monitor/src/Telegrams/BaseTelegram.cs index 14c15f1..7afb887 100644 --- a/RS485 Monitor/src/Telegrams/BaseTelegram.cs +++ b/RS485 Monitor/src/Telegrams/BaseTelegram.cs @@ -16,7 +16,7 @@ public class BaseTelegram : IEquatable /// /// Maximum supported data length /// - private const byte MAX_DATA_LEN = 32; + public const byte MAX_PDU_LEN = 32; /// /// Minimum length of a telegram. This contains the following data: @@ -29,10 +29,15 @@ public class BaseTelegram : IEquatable /// private const byte MIN_DATA_LEN = 7; + /// + /// Maximum expected size of a raw telegram including PDU, header and checksum + /// + public const byte MAX_RAW_DATA_LEN = MAX_PDU_LEN + MIN_DATA_LEN; + /// /// End byte of the telegram /// - private const byte END_TELEGRAM = 0x0D; + public const byte END_TELEGRAM = 0x0D; /// /// Offset of the high Byte of the the telegram type @@ -159,11 +164,16 @@ protected BaseTelegram() /// Object to copy from protected BaseTelegram(BaseTelegram c) { + // Copy raw data this.Raw = new byte[c.Raw.Length]; Array.Copy(c.Raw, this.Raw, Raw.Length); + // Copy PDU this.PDU = new byte[c.PDU.Length]; Array.Copy(c.PDU, this.PDU, this.PDU.Length); + + // Copy timestamp + this.TimeStamp = c.TimeStamp; } /// @@ -193,9 +203,9 @@ public BaseTelegram(byte[] rawData, DateTime? timestamp = null) // fetch user data byte pduLen = rawData[POS_LEN]; - if (pduLen > MAX_DATA_LEN) + if (pduLen > MAX_PDU_LEN) { - throw new ArgumentException($"Invalid data len {pduLen}. Max supported: {MAX_DATA_LEN}"); + throw new ArgumentException($"Invalid data len {pduLen}. Max supported: {MAX_PDU_LEN}"); } // copy user data to PDU array @@ -266,7 +276,7 @@ public bool Equals(BaseTelegram? other) { return false; } - return this.Raw.SequenceEqual(other.Raw); + return this.Raw.SequenceEqual(other.Raw) && this.TimeStamp == other.TimeStamp; } } diff --git a/RS485 Monitor/src/Telegrams/TelegramSpecializer.cs b/RS485 Monitor/src/Telegrams/TelegramSpecializer.cs new file mode 100644 index 0000000..a09bd3e --- /dev/null +++ b/RS485 Monitor/src/Telegrams/TelegramSpecializer.cs @@ -0,0 +1,54 @@ +using NLog; + +namespace RS485Monitor.Telegrams +{ + /// + /// Static helper class to specialize telegrams + /// + public class TelegramSpecializer + { + + /// + /// Dictionary of known telegram types + /// + protected static Dictionary knownTelegrams = new() + { + {ControllerRequest.TELEGRAM_ID, typeof(ControllerRequest) }, + {ControllerResponse.TELEGRAM_ID, typeof(ControllerResponse) }, + {BatteryRequest.TELEGRAM_ID, typeof(BatteryRequest) }, + {BatteryResponse.TELEGRAM_ID, typeof(BatteryResponse) }, + {SpeedometerRequest.TELEGRAM_ID, typeof(SpeedometerRequest) }, + {SpeedometerResponse.TELEGRAM_ID, typeof(SpeedometerResponse) }, + }; + + /// + /// Class logger + /// + private static readonly Logger logger = LogManager.GetCurrentClassLogger(); + + /// + /// Try to get the specialization of a telegram. If the telegram cannot + /// be converted to a specialized type, the original telegram is returned. + /// + /// + /// + public static BaseTelegram Specialize(BaseTelegram telegram) + { + // try to fetch the special telegram type + if (knownTelegrams.TryGetValue(telegram.Id, out Type? specialType)) + { + var tg = (BaseTelegram?)Activator.CreateInstance(specialType, [telegram]); + if (tg != null) + { + return tg; + } + } + else + { + logger.Error("Unknown telegram type: {0}", telegram.Id); + } + + return telegram; + } + } +} diff --git a/RS485 Monitor/src/Utils/Storage/TelegramExporter.cs b/RS485 Monitor/src/Utils/Storage/TelegramExporter.cs new file mode 100644 index 0000000..84b6bfb --- /dev/null +++ b/RS485 Monitor/src/Utils/Storage/TelegramExporter.cs @@ -0,0 +1,51 @@ +using System.Text; + +namespace RS485_Monitor.Utils.Storage +{ + /// + /// This class will export the telegrams into a binary format to a stream. + /// + public class TelegramExporter :IDisposable + { + public const byte VERSION = 1; + public const string IDENTIFIER = "RS485MONITOR"; + public static readonly byte[] MAGIC_NUMBER = Encoding.ASCII.GetBytes(IDENTIFIER); + + private readonly BinaryWriter _writer; + + /// + /// Create a new TelegramExporter from a stream. + /// + /// stream to read from + /// leave the stream open after reading + public TelegramExporter(Stream stream, bool leaveOpen = false) + { + _writer = new BinaryWriter(stream, new UTF8Encoding(), leaveOpen); + WriteHeader(); + } + + public TelegramExporter(string path, bool leaveOpen = false) + { + FileStream stream = new(path, FileMode.Create); + _writer = new BinaryWriter(stream, new UTF8Encoding(), leaveOpen); + WriteHeader(); + } + + public void PushTelegram(BaseTelegram telegram) + { + _writer.Write(telegram.TimeStamp.ToBinary()); + _writer.Write(telegram.Raw); + } + + public void Dispose() + { + _writer?.Dispose(); + } + + protected void WriteHeader() + { + _writer.Write(MAGIC_NUMBER); + _writer.Write(VERSION); + } + } +} diff --git a/RS485 Monitor/src/Utils/Storage/TelegramImporter.cs b/RS485 Monitor/src/Utils/Storage/TelegramImporter.cs new file mode 100644 index 0000000..e2c0e7d --- /dev/null +++ b/RS485 Monitor/src/Utils/Storage/TelegramImporter.cs @@ -0,0 +1,103 @@ +using System.Text; +using RS485Monitor.Telegrams; + +namespace RS485_Monitor.Utils.Storage +{ + /// + /// This class will import telegrams from a stream created by the TelegramExporter. + /// + /// + public class TelegramImporter : IDisposable + { + private readonly BinaryReader _reader; + /// + /// Version read from the stream + /// + public byte FormatVersion { get; } + + /// + /// Create a new TelegramImporter from a stream. + /// + /// Stream to read from + /// Invalid file format + public TelegramImporter(Stream stream, bool leaveOpen = false) + { + _reader = new BinaryReader(stream, new UTF8Encoding(), leaveOpen); + FormatVersion = ReadHeader(); + } + + /// + /// Create a new TelegramImporter from a file. + /// + /// Path to the file + /// Invalid file format + public TelegramImporter(string path, bool leaveOpen = false) + { + FileStream stream = new(path, FileMode.Open); + _reader = new BinaryReader(stream, new UTF8Encoding(), leaveOpen); + FormatVersion = ReadHeader(); + } + + /// + /// Iterator function to iterate over all telegrams in the file. + /// + /// An enumerable of BaseTelegram objects. + public IEnumerable GetTelegram() + { + while (_reader.BaseStream.Position < _reader.BaseStream.Length) + { + yield return PopTelegram(); + } + } + + public void Dispose() + { + _reader?.Dispose(); + } + + /// + /// Read the header of the file and check if it is a valid file. + /// + protected byte ReadHeader() + { + byte[] magic_number = _reader.ReadBytes(TelegramExporter.IDENTIFIER.Length); + byte version = _reader.ReadByte(); + + if (!magic_number.SequenceEqual(TelegramExporter.MAGIC_NUMBER) || version != TelegramExporter.VERSION) + { + throw new InvalidDataException("Invalid file format"); + } + + return version; + } + + /// + /// Pop the next telegram from the stream + /// + /// + /// Could not find an endTag + /// + protected BaseTelegram PopTelegram() + { + long timestamp = _reader.ReadInt64(); + List raw = []; + byte b; + + // Read until end telegram is found or max length is reached + do + { + b = _reader.ReadByte(); + raw.Add(b); + } while (b != BaseTelegram.END_TELEGRAM && raw.Count < BaseTelegram.MAX_RAW_DATA_LEN); + + if (raw.Count >= BaseTelegram.MAX_RAW_DATA_LEN) + { + throw new InvalidDataException("Endtag not found. File is corrupted."); + } + + // Create and specialize the telegram + BaseTelegram baseTelegram = new(raw.ToArray(), DateTime.FromBinary(timestamp)); + return TelegramSpecializer.Specialize(baseTelegram); + } + } +} diff --git a/tests/BaseTelegramTest.cs b/tests/BaseTelegramTest.cs index 4181f68..8c7f317 100644 --- a/tests/BaseTelegramTest.cs +++ b/tests/BaseTelegramTest.cs @@ -96,21 +96,36 @@ public void EqualsTest() { byte[] raw1 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D]; byte[] raw2 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D]; + DateTime ts = DateTime.Now; - BaseTelegram telegram1 = new(raw1); - BaseTelegram telegram2 = new(raw2); + BaseTelegram telegram1 = new(raw1, timestamp: ts); + BaseTelegram telegram2 = new(raw2, timestamp: ts); Assert.That(telegram1.Equals(telegram2), Is.EqualTo(true)); } [Test] - public void NotEqualsTest() + public void NotEqualsDataTest() { byte[] raw1 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D]; byte[] raw2 = [0xC5, 0x5C, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D]; + DateTime ts = DateTime.Now; - BaseTelegram telegram1 = new(raw1); - BaseTelegram telegram2 = new(raw2); + BaseTelegram telegram1 = new(raw1, timestamp: ts); + BaseTelegram telegram2 = new(raw2, timestamp: ts); + + Assert.That(telegram1.Equals(telegram2), Is.EqualTo(false)); + } + + [Test] + public void NotEqualsTimestampTest() + { + byte[] raw1 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D]; + byte[] raw2 = [0xB6, 0x6B, 0xAA, 0xDA, 0x0A, 0x02, 0x00, 0x04, 0x00, 0x00, 0x13, 0x00, 0x00, 0x02, 0x01, 0x1C, 0x0D]; + DateTime ts = DateTime.Now; + + BaseTelegram telegram1 = new(raw1, timestamp: ts); + BaseTelegram telegram2 = new(raw2, timestamp: ts - TimeSpan.FromMinutes(1)); Assert.That(telegram1.Equals(telegram2), Is.EqualTo(false)); } diff --git a/tests/TelegramExporterTest.cs b/tests/TelegramExporterTest.cs new file mode 100644 index 0000000..52cc426 --- /dev/null +++ b/tests/TelegramExporterTest.cs @@ -0,0 +1,94 @@ +namespace RS485_Monitor.tests; +using RS485_Monitor.Utils.Storage; +using NUnit.Framework; +using System.Text; + +[TestFixture] +public class TelegramExporterTest +{ + + [Test] + public void WriteHeaderTest() + { + MemoryStream stream = new(); + using TelegramExporter writer = new(stream); + + // Extract identifier + byte[] expected = Encoding.ASCII.GetBytes(TelegramExporter.IDENTIFIER); + byte[] actual = new byte[expected.Length]; + + stream.Seek(0, SeekOrigin.Begin); + stream.Read(actual, 0, expected.Length); + + // Extract version + byte expected_version = 1; + byte actual_version = (byte)stream.ReadByte(); + + // Test the values + Assert.That(actual, Is.EqualTo(expected)); + Assert.That(actual_version, Is.EqualTo(expected_version)); + } + + [Test] + public void CreateFileSuccess() { + string path = Path.GetTempFileName(); + + using (TelegramExporter writer = new(path)) { + Assert.That(File.Exists(path), Is.True); + } + + using (Stream stream = new FileStream(path, FileMode.Open)) { + + // Read the content of the file and check the header + using (var reader = new BinaryReader(stream)) { + byte[] actual = new byte[TelegramExporter.IDENTIFIER.Length]; + reader.Read(actual, 0, TelegramExporter.IDENTIFIER.Length); + byte actual_version = reader.ReadByte(); + + byte[] expected = Encoding.ASCII.GetBytes(TelegramExporter.IDENTIFIER); + byte expected_version = TelegramExporter.VERSION; + + Assert.That(actual, Is.EqualTo(expected)); + Assert.That(actual_version, Is.EqualTo(expected_version)); + } + } + + // Cleanup + File.Delete(path); + } + + + [Test] + public void PushTelegrams() { + MemoryStream stream = new(); + using TelegramExporter writer = new(stream); + + + byte[] raw1 = [0xB6, 0x6B, 0xAA, 0x5A, 0x0A, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x00, 0x30, 0x0D]; + byte[] raw2 = [0xC5, 0x5C, 0x5A, 0xAA, 0x01, 0x00, 0x30, 0x0D]; + List telegrams = [new BaseTelegram(raw1), new BaseTelegram(raw2)]; + + // Push the first telegram to the writer + foreach (var telegram in telegrams) { + writer.PushTelegram(telegram); + } + + // Jump over the header and start reading the telegrams + var headerlen = TelegramExporter.IDENTIFIER.Length + 1; + stream.Seek(headerlen, SeekOrigin.Begin); + + using (var reader = new BinaryReader(stream)) { + foreach (var telegram in telegrams) { + long timestamp = reader.ReadInt64(); + byte[] actual = new byte[telegram.Raw.Length]; + reader.Read(actual, 0, telegram.Raw.Length); + + using (Assert.EnterMultipleScope()) + { + Assert.That(timestamp, Is.EqualTo(telegram.TimeStamp.ToBinary())); + Assert.That(actual, Is.EqualTo(telegram.Raw)); + } + } + } + } +} diff --git a/tests/TelegramImporterTest.cs b/tests/TelegramImporterTest.cs new file mode 100644 index 0000000..7e8b783 --- /dev/null +++ b/tests/TelegramImporterTest.cs @@ -0,0 +1,61 @@ +namespace RS485_Monitor.tests; +using RS485_Monitor.Utils.Storage; +using NUnit.Framework; + +[TestFixture] +public class TelegramImportTest +{ + public required Stream stream; + public required List telegrams; + + [OneTimeSetUp] + public void SetUp() + { + stream = new MemoryStream(); + telegrams = []; + + telegrams.Add(new BaseTelegram([0xB6, 0x6B, 0xAA, 0x5A, 0x0A, 0x4D, 0x48, 0x17, 0x00, 0x00, 0x23, 0x00, 0x0B, 0x00, 0x00, 0x30, 0x0D])); + telegrams.Add(new BaseTelegram([0xC5, 0x5C, 0x5A, 0xAA, 0x01, 0x00, 0x30, 0x0D])); + + using TelegramExporter writer = new(stream, leaveOpen: true); + foreach (BaseTelegram telegram in telegrams) + { + writer.PushTelegram(telegram); + } + } + + [OneTimeTearDown] + public void TearDown() + { + stream.Dispose(); + } + + [SetUp] + public void SetUpTest() + { + stream.Seek(0, SeekOrigin.Begin); + } + + [Test] + public void ReadHeaderTest() + { + using TelegramImporter reader = new(stream, leaveOpen: true); + Assert.That(reader.FormatVersion, Is.EqualTo(1)); + } + + [Test] + public void ReadTelegrams() + { + using TelegramImporter reader = new(stream, leaveOpen: true); + List actual = []; + + // read the telegrams + foreach (BaseTelegram telegram in reader.GetTelegram()) + { + actual.Add(telegram); + } + + // Check that the read telegrams are the same as the ones we wrote + Assert.That(actual, Is.EqualTo(telegrams)); + } +} diff --git a/tests/TelegramSpecializerTest.cs b/tests/TelegramSpecializerTest.cs new file mode 100644 index 0000000..f094fb7 --- /dev/null +++ b/tests/TelegramSpecializerTest.cs @@ -0,0 +1,29 @@ +namespace RS485_Monitor.tests; +using NUnit.Framework; +using RS485Monitor.Telegrams; + +[TestFixture] +public class TelegramSpecializerTest +{ + [Test] + public void Specialize_KnownTelegramType() + { + byte[] raw = [0xC5, 0x5C, 0xAA, 0xBA, 0x01, 0x00, 0x00, 0x0D]; + BaseTelegram telegram = new BaseTelegram(raw); + BaseTelegram specializedTelegram = TelegramSpecializer.Specialize(telegram); + + Assert.That(specializedTelegram, Is.InstanceOf()); + Assert.That(specializedTelegram, Is.EqualTo(telegram)); + } + + [Test] + public void Specialize_UnknownTelegramType() + { + byte[] raw = [0xC5, 0x5C, 0xBB, 0xCC, 0x01, 0x00, 0x00, 0x0D]; + BaseTelegram telegram = new BaseTelegram(raw); + BaseTelegram specializedTelegram = TelegramSpecializer.Specialize(telegram); + + Assert.That(specializedTelegram, Is.InstanceOf()); + } + +}