Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -452,3 +452,8 @@ $RECYCLE.BIN/
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json


# My own files to ignore
*.ssm
*.bin
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
11 changes: 7 additions & 4 deletions RS485 Monitor/src/CmdOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
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; }
}
151 changes: 91 additions & 60 deletions RS485 Monitor/src/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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<CmdOptions>(args)
.WithParsed<CmdOptions>(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;
}
);

Expand All @@ -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)
Expand All @@ -63,6 +60,83 @@
StartMonitor(cfg);
}

/// <summary>
/// Start the serial monitor
/// </summary>
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();
}


/// <summary>
/// Read a local file and parse it
Expand All @@ -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;
Expand Down Expand Up @@ -105,49 +179,6 @@ async Task ReadLocalFile(String file)
}


/// <summary>
/// Start the serial monitor
/// </summary>
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);
}
}


/// <summary>
/// Read a local file and parse it
Expand Down
22 changes: 13 additions & 9 deletions RS485 Monitor/src/SerialMonitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,11 @@

using System.IO.Ports;
using NLog;
using System.Diagnostics;
using System.Text;

/// <summary>
/// Class reading data from the serial interface and parsing using the TelegramParser.
/// </summary>
public class SerialMonitor
public class SerialMonitor : IDisposable
{
#region Private Members
/// <summary>
Expand Down Expand Up @@ -56,7 +54,7 @@ public class SerialMonitor
/// </summary>
private const int BAUDRATE = 9600;
/// <summary>
/// Maximum size of the buffer / chunks
/// Maximum size of the buffer / chunks
/// </summary>
private const int BUFFER_SIZE = 256;
#endregion
Expand Down Expand Up @@ -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);
}
};
}
Expand All @@ -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)
Expand Down Expand Up @@ -163,4 +162,9 @@ public void Stop()

Running = false;
}
}

public void Dispose()
{
Stop();
}
}
20 changes: 3 additions & 17 deletions RS485 Monitor/src/TelegramParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using NLog;
using RS485Monitor.Telegrams;

/// <summary>
/// Class extracting telegrams from byte streams.
Expand Down Expand Up @@ -199,23 +200,8 @@ private void FinishBlock()
return null;
}

// Define known telegrams
Dictionary<UInt16, Type> 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;
}
}
20 changes: 15 additions & 5 deletions RS485 Monitor/src/Telegrams/BaseTelegram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public class BaseTelegram : IEquatable<BaseTelegram>
/// <summary>
/// Maximum supported data length
/// </summary>
private const byte MAX_DATA_LEN = 32;
public const byte MAX_PDU_LEN = 32;

/// <summary>
/// Minimum length of a telegram. This contains the following data:
Expand All @@ -29,10 +29,15 @@ public class BaseTelegram : IEquatable<BaseTelegram>
/// </summary>
private const byte MIN_DATA_LEN = 7;

/// <summary>
/// Maximum expected size of a raw telegram including PDU, header and checksum
/// </summary>
public const byte MAX_RAW_DATA_LEN = MAX_PDU_LEN + MIN_DATA_LEN;

/// <summary>
/// End byte of the telegram
/// </summary>
private const byte END_TELEGRAM = 0x0D;
public const byte END_TELEGRAM = 0x0D;

/// <summary>
/// Offset of the high Byte of the the telegram type
Expand Down Expand Up @@ -159,11 +164,16 @@ protected BaseTelegram()
/// <param name="c">Object to copy from</param>
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;
}

/// <summary>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

}
Loading