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
7 changes: 7 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

2.2.0 / 22.10.2025
------------------

- Data filtering feature
- History of sent data through send dialog
- Added CRC8 function

2.1.0 / 2.9.2025
------------------

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Program isn't serial port sniffer so can't monitor port that is already open by
* **Display data with timestamp or with gap between messages** - Time between received data messages is displayed (in miliseconds). In addition to the printing on the screen it allows write everything (or just communication) to a specified file.
* **Log communication into the file**
* **Pause data receive** - In GUI mode printing of received data can be temporarily paused for peaceful data analysis.
* **Filter** - In GUI mode displayed data can be filtered.
* **Send files** - Not only manually entered data can be sent, but also a prepared data file.
* **Manually control RTS and DTR pins** - In GUI mode it is possible to see the pins status and also control output pins.
* **Emulate serial device** - Program allows response to sender for specific message. This could be used for simple simulation of some device.
Expand All @@ -40,6 +41,7 @@ Inside message functions could be used. The function is represented by '@' follo

| Name | Description | Example |
|-------|-------------|---------|
| Crc8 | Compute CRC8 from all bytes in a packet | 0x00 0xAA @crc8 |
| Crc16 | Compute CRC16 from all bytes in a packet | 0x00 0xAA @crc16 |
| Rand | Generate random byte between 0-255. Value range can be specified. | 0x00 0xAA @rand[1..100] |
| Sum | Compute checksum from bytes in a packet. The first and the last packet byte can be specified. | 0x03 0x00 0xAA @sum[1..] |
Expand Down
45 changes: 39 additions & 6 deletions SerialMonitor/Config.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
class Config
{
const string CONFIG_FILE = "serialmonitor.cfg";
const string HISTORY_FILE = "serialmonitor.hist";
const string START_ARGUMENTS = "StartArgs";
const string START_ARGUMENTS_REGEX = "(" + START_ARGUMENTS + "=)([^\n]*)";
const string HISTORY = "CommandHistory";
Expand All @@ -24,13 +25,14 @@
const string FILE_LIST_REGEX = "(" + FILE_LIST + "=)([^\n]*)";
public const string SETTING_PORT = "Port";
public const string SETTING_BAUDRATE = "BaudRate";
public const string SETTING_PARITY = "Parity";
public const string SETTING_PARITY = "Parity";
public const string SETTING_SHOWTIME = "ShowTime";
public const string SETTING_SHOWTIMEGAP = "ShowTimeGap";
public const string SETTING_SHOWSENTDATA = "ShowSentData";
public const string SETTING_SHOWASCII = "ShowAscii";
public const string SETTING_SHOWASCII = "ShowAscii";

static readonly string filePath = Path.Combine(Directory.GetCurrentDirectory(), CONFIG_FILE);
static readonly string ConfigFilePath = Path.Combine(Directory.GetCurrentDirectory(), CONFIG_FILE);
static readonly string CommandHistoryFilePath = Path.Combine(Directory.GetCurrentDirectory(), HISTORY_FILE);

/// <summary>
/// Save started parameters
Expand Down Expand Up @@ -101,7 +103,7 @@
{
try
{
using FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
using FileStream fs = new FileStream(ConfigFilePath, FileMode.Create, FileAccess.Write);
using (StreamWriter sw = new StreamWriter(fs, Encoding.UTF8))
{
sw.Write(configuration);
Expand Down Expand Up @@ -252,18 +254,18 @@
{
string cfg = "";

if (File.Exists(filePath))
if (File.Exists(ConfigFilePath))
{
try
{
using FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using FileStream fs = new FileStream(ConfigFilePath, FileMode.Open, FileAccess.Read);
if (fs.Length > 0)
{
using TextReader sr = new StreamReader(fs, Encoding.UTF8);
cfg = sr.ReadToEnd();
}
}
catch (FileNotFoundException ex)

Check warning on line 268 in SerialMonitor/Config.cs

View workflow job for this annotation

GitHub Actions / build

The variable 'ex' is declared but never used
{
return null;
}
Expand All @@ -276,5 +278,36 @@

return cfg;
}

public static string[] ReadCommandHistory()
{
if (File.Exists(CommandHistoryFilePath))
{
try
{
return File.ReadAllLines(CommandHistoryFilePath);
}
catch (FileNotFoundException)
{
}
catch (Exception ex)
{
Console.WriteLine($"Error while open the history file. {ex}");
}
}
return [];
}

public static void WriteCommandHistory(string[] lines)
{
try
{
File.WriteAllLines(CommandHistoryFilePath, lines);
}
catch (Exception ex)
{
Console.WriteLine($"Error write into the history file. {ex}");
}
}
}
}
6 changes: 4 additions & 2 deletions SerialMonitor/Functions/BuiltInFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace SerialMonitor.Functions
{
internal class BuiltInFunctions
{
public static readonly string[] Available = [nameof(Crc16), nameof(Sum), nameof(Rand)];
public static readonly string[] Available = [nameof(Crc8), nameof(Crc16), nameof(Sum), nameof(Rand)];
private static readonly Regex functionRegex = new Regex(@"^(\w+)(\[(\d*)\.{2}(\d*)\])?$", RegexOptions.Compiled);

public static bool IsAvailable(string functionName)
Expand All @@ -40,7 +40,9 @@ public static IFunction Get(string functionName, int position)
int start = match.Groups[2].Success && match.Groups[3].Success && match.Groups[3].Value.Length > 0 ? int.Parse(match.Groups[3].Value) : 0;
int end = match.Groups[2].Success && match.Groups[4].Success && match.Groups[4].Value.Length > 0 ? int.Parse(match.Groups[4].Value) : int.MaxValue;

if (f.Equals("Crc16"))
if (f.Equals("Crc8"))
return new Crc8(position, start, end);
else if (f.Equals("Crc16"))
return new Crc16(position, start, end);
else if (f.Equals("Sum"))
return new Sum(position, start, end);
Expand Down
85 changes: 85 additions & 0 deletions SerialMonitor/Functions/Crc8.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
//---------------------------------------------------------------------------
//
// Name: Crc8.cs
// Author: Vita Tucek
// Created: 18.10.2025
// License: MIT
// Description: CRC8 calculation
//
//---------------------------------------------------------------------------

namespace SerialMonitor.Functions
{
public class Crc8 : FunctionBase
{
public Crc8(int position) : base(position)
{
}

public Crc8(int position, int start, int end) : base(position, start, end)
{
}

public override int Size => 1;

/// <summary>
/// Compute over specified length
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
/// <returns></returns>
public static byte ComputeCrc(byte[] data, int position, int start, int end)
{
int dataend = end < position ? end : position - 1;
int crc = 0;
int i, j;

for (j = start; j <= dataend; j++)
{
crc ^= (data[j] << 8);

for (i = 0; i < 8; i++)
{
if ((crc & 0x8000) != 0)
crc ^= (0x1070 << 3);

crc <<= 1;
}
}
return (byte)(crc >> 8);
}

public override void Compute(byte[] data)
{
var crc = Crc8.ComputeCrc(data, Position, Start, End);
data[Position] = crc;
}

/// <summary>
/// Verify data with CRC included
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
/// <returns></returns>
public static bool Verify(byte[] data)
{
return Verify(data, data.Length);
}

/// <summary>
/// Verify data with CRC included
/// </summary>
/// <param name="data"></param>
/// <param name="length"></param>
/// <returns></returns>
public static bool Verify(byte[] data, int length)
{
if (length < 3)
return false;
byte computed = ComputeCrc(data, length - 1, 0, length - 1);
byte received = data[length - 1];

return computed == received;
}
}
}
63 changes: 63 additions & 0 deletions SerialMonitor/LogRecord.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.CodeDom;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static System.Runtime.InteropServices.JavaScript.JSType;

namespace SerialMonitor
{
public enum LogRecordType
{
DataReceived,
DataSent,
ControlPinChanged,
ControlPinOn,
ControlPinOff,
Time,
Error,
Warning,
Default,
}

internal class LogRecord
{
public static bool ShowTime { get; set; } = true;
public LogRecord(string text, LogRecordType type, TraceEventType level)
{
Text = text;
Level = level;
Type = type;
TimeStamp = DateTime.Now;
}
public string Text { get; set; }
public TraceEventType Level { get; }
public LogRecordType Type { get; }
public DateTime TimeStamp { get; private set; }
public int Length { get => Text.Length; }

public static LogRecord operator +(LogRecord left, LogRecord right) => (new LogRecord(left.Text + right.Text, left.Type, left.Level));
public static LogRecord operator +(LogRecord left, string right) => (new LogRecord(left.Text + right, left.Type, left.Level));
public static LogRecord operator +(LogRecord operand) => operand;

public void Append(string message)
{
Text += message;
}

public override string ToString()
{
return Render();
}

private string Render()
{
if (ShowTime && (Type == LogRecordType.DataSent || Type == LogRecordType.DataReceived))
return $"{TimeStamp:HH:mm:ss.fff} {Text}";

return Text;
}
}
}
Loading
Loading