Skip to content

Commit 0f549bb

Browse files
authored
Merge pull request #2 from docbender/2.1.x
2.1.x
2 parents 3484b9a + 70cc936 commit 0f549bb

28 files changed

+1443
-252
lines changed

Changelog.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Changelog
2+
=========
3+
4+
2.1.0 / 2.9.2025
5+
------------------
6+
7+
- .NET 8.0
8+
- Repeat file improvement
9+
- Functions in the repeat file
10+
- Variables in the repeat file
11+
- User inteface improvement
12+
- Parity setup from GUI
13+
- Linux service support
14+
15+
2.0.0 / 31.5.2022
16+
------------------
17+
18+
- New GUI
19+
- .NET 6.0 support

README.md

Lines changed: 75 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,100 @@
11
# SerialMonitor
2-
Program is simple piece of software designed for serial port data monitoring together with the possibility to automatically reply to sender.
2+
3+
Program is piece of software designed for serial port data monitoring together with the possibility to automatically reply to sender.
34

45
[![Version](https://img.shields.io/github/v/release/docbender/SerialMonitor?include_prereleases)](https://github.com/docbender/SerialMonitor/releases)
56
[![Download](https://img.shields.io/github/downloads/docbender/SerialMonitor/total.svg)](https://github.com/docbender/SerialMonitor/releases)
67
[![Issues](https://img.shields.io/github/issues/docbender/SerialMonitor)](https://github.com/docbender/SerialMonitor/issues)
78

8-
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.
9+
Program isn't serial port sniffer so can't monitor port that is already open by another program.
10+
11+
![Preview](https://github.com/docbender/SerialMonitor/blob/master/img/SM3.png)
12+
13+
## Requirements
14+
15+
* .NET8
16+
17+
## Features
18+
19+
* **GUI mode or classic console mode**
20+
* **Running as service support** - With parameter service console verbosity is eliminated.
21+
* **Display data in the Hex or ASCII format**
22+
* **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.
23+
* **Log communication into the file**
24+
* **Pause data receive** - In GUI mode printing of received data can be temporarily paused for peaceful data analysis.
25+
* **Send files** - Not only manually entered data can be sent, but also a prepared data file.
26+
* **Manually control RTS and DTR pins** - In GUI mode it is possible to see the pins status and also control output pins.
27+
* **Emulate serial device** - Program allows response to sender for specific message. This could be used for simple simulation of some device.
28+
29+
### Device emulation
30+
31+
To use this feature it is necessary to prepare file with pairs request/response. In file first(odd) line represent request and second(even) line response. The file can contain several pairs request/response which can be separated by empty lines (not necessary).
932

10-
Program requires .NET6. Program is running under Windows and Linux. Program isn't serial port sniffer so can't monitor port that is already open by another program.
33+
Comment character is '#'.
1134

12-
![](https://github.com/docbender/SerialMonitor/blob/master/img/SM3.png)
35+
Data in file can be written in hex format (0x leading) or ASCII format. Selected format must be used through the whole file.
1336

14-
Program has also feature that allows answer to sender for specific message. This could be used for simple simulation of some device. To use this feature it is necessary to prepare file with pairs ask/answer. In file first(odd) line represent ask and second(even) line answer. File can contain several pairs ask/answer which can be separated by empty lines (not necessary). File example:
37+
In hex mode variables could be used to copy data between request/response message. The variable is represented by '$' followed by variable number. Each variable represents one data byte in the message.
1538

16-
0x01 0xD0 0x00 0xD1 - ask
17-
0x01 0xE2 0x00 0xE3 - answer
39+
Inside message functions could be used. The function is represented by '@' followed by function name. Following functions are implemented:
40+
41+
| Name | Description | Example |
42+
|-------|-------------|---------|
43+
| Crc16 | Compute CRC16 from all bytes in a packet | 0x00 0xAA @crc16 |
44+
| Rand | Generate random byte between 0-255. Value range can be specified. | 0x00 0xAA @rand[1..100] |
45+
| Sum | Compute checksum from bytes in a packet. The first and the last packet byte can be specified. | 0x03 0x00 0xAA @sum[1..] |
46+
47+
**File example:**
48+
49+
# this is the comment
50+
# the first line is the request
51+
0x01 0xD0 0x00 0xD1
52+
# the second line is the response
53+
0x01 0xE2 0x00 0xE3
1854

19-
0x75 0x55 0x55
20-
0x45 0x44 0x44
55+
# the second byte of the request is copied to the third position of the response
56+
0x75 $1 0x55
57+
0x45 0x44 $1 0x44
2158

22-
0x11 0x54 0x55
23-
0x25 0x26 0x56
59+
# both messages are secured by CRC16
60+
0x11 0x54 0x55 @crc16
61+
0x25 0x26 0x56 @crc16
62+
63+
# both packets have a checksum computed from byte with index 1 (the second byte in the packet) to the position of the function
64+
# in the answer random byte with value between 0 and 99 is generated
65+
0x01 0x02 0x03 @sum[1..]
66+
0x01 0x02 rand[0..99] @sum[1..]
2467

25-
Data in file can be written in hex format (0x leading) or ASCII format. Selected format must be used through whole file.
68+
### Command line
2669

27-
##Usage
2870
Program as commandline program support some parameters. First one (most important) PortName that represent port to open (ex. COM3 or /dev/ttyS1). When program is started without parameters port COM1 with default parameters is used.
2971

3072
**serialmonitor PortName [switch parameter]**
3173

32-
Switches:
33-
* -baudrate {baud rate}: set port baud rate. Default 9600kbps.
34-
* -parity {used parity}: set used port parity. Default none. Available parameters odd, even, mark and space.
35-
* -databits {used databits}: set data bits count. Default 8 data bits.
36-
* -stopbits {used stopbits}: set stop bits count. Default 1 stop bit. Available parameters 0, 1, 1.5 and 2.
37-
* -repeatfile {file name}: enable repeat mode with protocol specified in file.
38-
* -logfile {file name}: set file name for log into that file.
39-
* -logincomingonly: log into file would be only incoming data.
40-
* -showascii: communication would be show in ASCII format (otherwise HEX is used).
41-
* -notime: time information about incoming data would not be printed.
42-
* -gaptolerance {time gap in ms}: messages received within specified time gap will be printed together.
43-
* -nogui: start program in normal console mode (scrolling list). Not with text GUI.
44-
45-
Example:
74+
**Switches:**
75+
76+
* **-baudrate {baud rate}** - Set port baud rate. Default 9600kbps.
77+
* **-parity {used parity}** - Set used port parity. Default none. Available parameters odd, even, mark and space.
78+
* **-databits {used databits}** - Set data bits count. Default 8 data bits.
79+
* **-stopbits {used stopbits}** - Set stop bits count. Default 1 stop bit. Available parameters 0, 1, 1.5 and 2.
80+
* **-repeatfile {file name}** - Enable repeat mode with protocol specified in file.
81+
* **-logfile {file name}** - Set file name for log into that file.
82+
* **-logincomingonly** - Log into file would be only incoming data.
83+
* **-showascii** -Communication would be show in ASCII format (otherwise HEX is used).
84+
* **-notime** - Time information about incoming data would not be printed.
85+
* **-gaptolerance {time gap in ms}** - Messages received within specified time gap will be printed together.
86+
* **-nogui** - Start program in normal console mode (scrolling list). Not with text GUI.
87+
* **-service** - No console output.
88+
89+
**Example:**
4690

4791
serialmonitor COM1
4892
serialmonitor /dev/ttyS1 -baudrate 57600
4993
serialmonitor COM1 -baudrate 57600 -parity odd -databits 7 -stopbits 1.5
5094
serialmonitor COM83 -baudrate 19200 -repeatfile protocol.txt
5195

5296
In program commands can be typed:
53-
* exit: program exit
54-
* send {data to send}: send specified data (in HEX format if data start with 0x otherwise ASCII is send)
97+
98+
* **exit**: program exit
99+
* **send {data to send}**: send specified data (in HEX format if data starts with 0x otherwise ASCII is send)
100+
* **help**: print help

SerialMonitor.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ VisualStudioVersion = 17.0.32112.339
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SerialMonitor", "SerialMonitor\SerialMonitor.csproj", "{019A06E0-C535-488C-AACF-E2A0E1A26861}"
77
EndProject
8+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerialMonitorTests", "SerialMonitorTests\SerialMonitorTests.csproj", "{B256E2BC-363A-4098-A163-8B25464FD95E}"
9+
EndProject
810
Global
911
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1012
Debug|Any CPU = Debug|Any CPU
@@ -15,6 +17,10 @@ Global
1517
{019A06E0-C535-488C-AACF-E2A0E1A26861}.Debug|Any CPU.Build.0 = Debug|Any CPU
1618
{019A06E0-C535-488C-AACF-E2A0E1A26861}.Release|Any CPU.ActiveCfg = Release|Any CPU
1719
{019A06E0-C535-488C-AACF-E2A0E1A26861}.Release|Any CPU.Build.0 = Release|Any CPU
20+
{B256E2BC-363A-4098-A163-8B25464FD95E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21+
{B256E2BC-363A-4098-A163-8B25464FD95E}.Debug|Any CPU.Build.0 = Debug|Any CPU
22+
{B256E2BC-363A-4098-A163-8B25464FD95E}.Release|Any CPU.ActiveCfg = Release|Any CPU
23+
{B256E2BC-363A-4098-A163-8B25464FD95E}.Release|Any CPU.Build.0 = Release|Any CPU
1824
EndGlobalSection
1925
GlobalSection(SolutionProperties) = preSolution
2026
HideSolutionNode = FALSE

SerialMonitor/Config.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Config
2424
const string FILE_LIST_REGEX = "(" + FILE_LIST + "=)([^\n]*)";
2525
public const string SETTING_PORT = "Port";
2626
public const string SETTING_BAUDRATE = "BaudRate";
27+
public const string SETTING_PARITY = "Parity";
2728
public const string SETTING_SHOWTIME = "ShowTime";
2829
public const string SETTING_SHOWTIMEGAP = "ShowTimeGap";
2930
public const string SETTING_SHOWSENTDATA = "ShowSentData";
@@ -77,11 +78,12 @@ public static bool SaveFileList(IEnumerable<string> args)
7778
/// <param name="showSentData"></param>
7879
/// <param name="showAscii"></param>
7980
/// <returns></returns>
80-
internal static bool SaveSetting(string device, int baudrate, bool showTime, bool showTimeGap, bool showSentData, bool showAscii)
81+
internal static bool SaveSetting(string device, int baudrate, System.IO.Ports.Parity parity, bool showTime, bool showTimeGap, bool showSentData, bool showAscii)
8182
{
8283
string? cfg = ReadConfiguration();
8384
cfg = PrepareSave(cfg, SETTING_PORT, device);
8485
cfg = PrepareSave(cfg, SETTING_BAUDRATE, baudrate.ToString());
86+
cfg = PrepareSave(cfg, SETTING_PARITY, parity.ToString());
8587
cfg = PrepareSave(cfg, SETTING_SHOWTIME, showTime ? "1" : "0");
8688
cfg = PrepareSave(cfg, SETTING_SHOWTIMEGAP, showTimeGap ? "1" : "0");
8789
cfg = PrepareSave(cfg, SETTING_SHOWSENTDATA, showSentData ? "1" : "0");
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//---------------------------------------------------------------------------
2+
//
3+
// Name: BuiltInFunctions.cs
4+
// Author: Vita Tucek
5+
// Created: 23.5.2024
6+
// License: MIT
7+
// Description: System functions
8+
//
9+
//---------------------------------------------------------------------------
10+
11+
using System.Text.RegularExpressions;
12+
13+
namespace SerialMonitor.Functions
14+
{
15+
internal class BuiltInFunctions
16+
{
17+
public static readonly string[] Available = [nameof(Crc16), nameof(Sum), nameof(Rand)];
18+
private static readonly Regex functionRegex = new Regex(@"^(\w+)(\[(\d*)\.{2}(\d*)\])?$", RegexOptions.Compiled);
19+
20+
public static bool IsAvailable(string functionName)
21+
{
22+
int i = functionName.IndexOf('[');
23+
if (i >= 0)
24+
return Available.Any(a => a.Equals(functionName[..i], StringComparison.OrdinalIgnoreCase));
25+
else
26+
return Available.Any(a => a.Equals(functionName, StringComparison.OrdinalIgnoreCase));
27+
}
28+
29+
public static IFunction Get(string functionName, int position)
30+
{
31+
var match = functionRegex.Match(functionName);
32+
if (!match.Success)
33+
throw new ArgumentException($"Invalid function definition '{functionName}'.");
34+
35+
var f = Available.First(x => x.Equals(match.Groups[1].Value, StringComparison.OrdinalIgnoreCase));
36+
// group 1 = name
37+
// group 2 = range
38+
// group 3 = start
39+
// group 4 = end
40+
int start = match.Groups[2].Success && match.Groups[3].Success && match.Groups[3].Value.Length > 0 ? int.Parse(match.Groups[3].Value) : 0;
41+
int end = match.Groups[2].Success && match.Groups[4].Success && match.Groups[4].Value.Length > 0 ? int.Parse(match.Groups[4].Value) : int.MaxValue;
42+
43+
if (f.Equals("Crc16"))
44+
return new Crc16(position, start, end);
45+
else if (f.Equals("Sum"))
46+
return new Sum(position, start, end);
47+
else if (f.Equals("Rand"))
48+
return new Rand(position, start, end);
49+
50+
throw new NotImplementedException($"Function {functionName} is not implemented.");
51+
}
52+
}
53+
}

SerialMonitor/Functions/Crc16.cs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//---------------------------------------------------------------------------
2+
//
3+
// Name: Crc16.cs
4+
// Author: Vita Tucek
5+
// Created: 23.5.2024
6+
// License: MIT
7+
// Description: CRC16 (Modbus) calculation
8+
//
9+
//---------------------------------------------------------------------------
10+
11+
namespace SerialMonitor.Functions
12+
{
13+
public class Crc16 : FunctionBase
14+
{
15+
private static readonly ushort[] crc_table = {
16+
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241,
17+
0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440,
18+
0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40,
19+
0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841,
20+
0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40,
21+
0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41,
22+
0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641,
23+
0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040,
24+
0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240,
25+
0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441,
26+
0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41,
27+
0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840,
28+
0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
29+
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40,
30+
0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640,
31+
0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041,
32+
0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240,
33+
0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441,
34+
0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41,
35+
0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840,
36+
0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41,
37+
0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40,
38+
0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640,
39+
0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041,
40+
0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241,
41+
0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
42+
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40,
43+
0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841,
44+
0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40,
45+
0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41,
46+
0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641,
47+
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
48+
};
49+
50+
public Crc16(int position) : base(position)
51+
{
52+
}
53+
54+
public Crc16(int position, int start, int end) : base(position, start, end)
55+
{
56+
}
57+
58+
public override int Size => 2;
59+
60+
/// <summary>
61+
/// Compute over specified length
62+
/// </summary>
63+
/// <param name="data"></param>
64+
/// <param name="length"></param>
65+
/// <returns></returns>
66+
public static ushort ComputeCrc(byte[] data, int position, int start, int end)
67+
{
68+
int dataend = end < position ? end : position - 1;
69+
ushort temp;
70+
ushort crc = 0xFFFF;
71+
72+
for (int i = start; i <= dataend; i++)
73+
{
74+
temp = (ushort)((data[i] ^ crc) & 0x00FF);
75+
crc >>= 8;
76+
crc ^= crc_table[temp];
77+
}
78+
79+
return crc;
80+
}
81+
82+
public override void Compute(byte[] data)
83+
{
84+
var crc = Crc16.ComputeCrc(data, Position, Start, End);
85+
data[Position] = (byte)(crc & 0xFF);
86+
data[Position + 1] = (byte)(crc >> 8 & 0xFF);
87+
}
88+
89+
/// <summary>
90+
/// Verify data with CRC included
91+
/// </summary>
92+
/// <param name="data"></param>
93+
/// <param name="length"></param>
94+
/// <returns></returns>
95+
public static bool Verify(byte[] data)
96+
{
97+
return Verify(data, data.Length);
98+
}
99+
100+
/// <summary>
101+
/// Verify data with CRC included
102+
/// </summary>
103+
/// <param name="data"></param>
104+
/// <param name="length"></param>
105+
/// <returns></returns>
106+
public static bool Verify(byte[] data, int length)
107+
{
108+
if (length < 3)
109+
return false;
110+
ushort computed = ComputeCrc(data, length - 2, 0, length - 2);
111+
ushort received = (ushort)((ushort)(data[length - 1] << 8) + data[length - 2]);
112+
113+
return computed == received;
114+
}
115+
}
116+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Text;
6+
using System.Threading.Tasks;
7+
8+
namespace SerialMonitor.Functions
9+
{
10+
public abstract class FunctionBase : IFunction
11+
{
12+
public abstract int Size { get; }
13+
14+
public int Position { get; }
15+
16+
public int Start { get; }
17+
18+
public int End { get; }
19+
20+
public FunctionBase(int position)
21+
{
22+
Position = position;
23+
Start = 0;
24+
End = int.MaxValue;
25+
}
26+
27+
public FunctionBase(int position, int start, int end)
28+
{
29+
Position = position;
30+
Start = start;
31+
End = end;
32+
}
33+
34+
public abstract void Compute(byte[] data);
35+
}
36+
}

0 commit comments

Comments
 (0)