Hello I’m a student and I’m currently working for a small association and they asked me to create a modbus sniffer for the function code 0x03, 0x06 and 0x10. I’ve never used c# before in my life and they gave me a little code already written. I tried to read and understand it all and it seems correct for me. But when I start the debugging it doesn’t go very well. I will put here part of the code and another part in the first comment.
using Modbus_Connect_4;
using System;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.IO.Ports;
using System.Web;
using System.Windows.Forms;
using System.Collections.Generic;
namespace Modbus_Connect_4
{
public partial class Form1 : Form
{
private DataSet data;
private static SerialPort serialPort;
// private static TimeSpan differenza;
private static DateTime oravecchia = DateTime.Now;
private static DateTime ora;
private static string file;
private static BackgroundWorker backgroundWorker1 = new BackgroundWorker();
// richieste, risposte e il tema inizialmente predefinite a true
private static Boolean richieste = true;
private static Boolean risposte = true;
private Boolean chiaro = true;
// impostazioni necessarie per iniziare il funzionamento dell'app inizializzate a null
private String porta = null;
private String baudrate = null;
private String databits = null;
private String parita = null;
private String stopbits = null;
// seconde impostazioni che i predecessori ci hanno lasciato (non ho capito a cosa servano, credo per la comunicazione tra 2 porte e quindi leggevano il doppio delle informazioni)
// private String porta2;
// private String baudrate2;
// private String databits2;
// private String parita2;
// private String stopbits2;
// enum con i fc richiesti
enum Function_Code
{
SingleWrite = 3,
MultiWrite = 6,
Read = 16
}
public Form1()
{
InitializeComponent();
// file dove verranno registrati i dati raccolti e letti dal programma
file = "ModbusConnect4_LOG_" + DateTime.Now.ToString("dd_MM_yyyy_HH_mm_ss") + ".log";
private CancellationTokenSource cancellationTokenSource;
int marginButton = 35; // Margine dei button
int marginTable = 20; // Margine della tabella
int buttonWidth = 100; // lunghezza dei button
int buttonHeight = 30; // altezza dei button
// Imposta posizione e dimensioni per button1
button1.Anchor = AnchorStyles.Top | AnchorStyles.Left;
button1.Location = new Point(marginButton, marginButton);
button1.Size = new Size(buttonWidth, buttonHeight);
// Imposta posizione e dimensioni per button2 nell'angolo in alto a destra
button2.Anchor = AnchorStyles.Top | AnchorStyles.Right;
button2.Location = new Point(this.ClientSize.Width - buttonWidth - marginButton, marginButton);
button2.Width = buttonWidth;
button2.Height = buttonHeight;
// Imposta posizione e dimensioni per button5 vicino al button 1
button5.Anchor = AnchorStyles.Top | AnchorStyles.Left;
button5.Size = new Size(buttonWidth, buttonHeight);
button5.Location = new Point(button1.Right + marginButton, marginButton);
// Imposta posizione e dimensioni per button3 = request
button3.Anchor = AnchorStyles.Top | AnchorStyles.Left;
button3.Size = new Size(buttonWidth, buttonHeight);
button3.Location = new Point(button5.Right + marginButton, marginButton);
// Imposta posizione e dimensioni per button4 = response
button4.Anchor = AnchorStyles.Top | AnchorStyles.Left;
button4.Size = new Size(buttonWidth, buttonHeight);
button4.Location = new Point(button3.Right + marginButton, marginButton);
// Imposta posizione verticale per dataGridView1 in base alla posizione dei pulsanti
int verticalPosition = button1.Bottom + marginTable;
// Imposta posizione e dimensioni per dataGridView1
dataGridView1.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Bottom;
dataGridView1.Location = new Point(marginTable, verticalPosition);
dataGridView1.Size = new Size(this.ClientSize.Width - 2 * marginTable, this.ClientSize.Height - verticalPosition - marginTable);
string[] portNames = SerialPort.GetPortNames();
string[] baud = { "300", "1200", "9600", "19200", "28800", "57600", "115200" };
string[] databit = { "7 Bit", "8 Bit" };
string[] parity = { "Nessuna", "Odd", "Even" };
string[] stop = { "1 Bit", "2 Bit" };
data = new DataSet("prova");
//settaggio tabella
DataTable table = new DataTable("prova2");
table.Columns.Add("Ora");
table.Columns.Add("Differenza (ms)");
table.Columns.Add("ID");
table.Columns.Add("FC");
table.Columns.Add("N.Byte/Indirizzo iniziale");
table.Columns.Add("N registri");
table.Columns.Add("Contenuto Registri");
table.Columns.Add("CRC");
data.Tables.Add(table);
dataGridView1.DataSource = data.Tables["prova2"];
// divisione in colonne della tabella dove vengono riportati i dati
dataGridView1.Columns["Ora"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns["ID"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns["FC"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns["N.Byte/Indirizzo iniziale"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns["N registri"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns["Contenuto Registri"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
dataGridView1.Columns["CRC"].AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill;
// impostazione del tema chiaro come predefinito
celleChiare();
//settaggio Worker
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
//input informazioni porta seriale
// scelta della porta seriale da dover leggere
foreach (string port in portNames)
{
ToolStripMenuItem portItem = new ToolStripMenuItem(port);
portItem.Click += PortItem_Click;
portItem.CheckOnClick = true;
portaToolStripMenuItem.DropDownItems.Add(portItem);
}
// scelta del baud della porta seriale
foreach (string port in baud)
{
ToolStripMenuItem baudratemenu = new ToolStripMenuItem(port);
baudratemenu.Click += baudItem_Click;
baudratemenu.CheckOnClick = true;
baudrateToolStripMenuItem.DropDownItems.Add(baudratemenu);
}
baudrateToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator());
baudrateToolStripMenuItem.DropDownItems.Add("Personalizzato [WIP]");
// scelta del databit della porta seriale
foreach (string port in databit)
{
ToolStripMenuItem databitsmenu = new ToolStripMenuItem(port);
databitsmenu.Click += databitsItem_Click;
databitsmenu.CheckOnClick = true;
dataBitsToolStripMenuItem.DropDownItems.Add(databitsmenu);
}
// scelta della parity della porta seriale
foreach (string port in parity)
{
ToolStripMenuItem paritamenu = new ToolStripMenuItem(port);
paritamenu.Click += paritaItem_Click;
paritamenu.CheckOnClick = true;
paritaToolStripMenuItem.DropDownItems.Add(paritamenu);
}
foreach (string port in stop)
{
ToolStripMenuItem stopmenu = new ToolStripMenuItem(port);
stopmenu.Click += StopItem_Click;
stopmenu.CheckOnClick = true;
stopBitToolStripMenuItem.DropDownItems.Add(stopmenu);
}
/*
* per la seconda porta non in uso
*
foreach (string port in portNames)
{
ToolStripMenuItem portItem = new ToolStripMenuItem(port);
portItem.Click += PortItem_Click2;
portItem.CheckOnClick = true;
porta2ToolStripMenuItem1.DropDownItems.Add(portItem);
}
foreach (string port in baud)
{
ToolStripMenuItem baudratemenu = new ToolStripMenuItem(port);
baudratemenu.Click += baudItem_Click2;
baudratemenu.CheckOnClick = true;
baudrate2ToolStripMenuItem1.DropDownItems.Add(baudratemenu);
}
baudrate2ToolStripMenuItem1.DropDownItems.Add(new ToolStripSeparator());
baudrate2ToolStripMenuItem1.DropDownItems.Add("Personalizzato [WIP]");
foreach (string port in databit)
{
ToolStripMenuItem databitsmenu = new ToolStripMenuItem(port);
databitsmenu.Click += databitsItem_Click2;
databitsmenu.CheckOnClick = true;
dataBits2ToolStripMenuItem1.DropDownItems.Add(databitsmenu);
}
foreach (string port in parity)
{
ToolStripMenuItem paritamenu = new ToolStripMenuItem(port);
paritamenu.Click += paritaItem_Click2;
paritamenu.CheckOnClick = true;
parita2ToolStripMenuItem1.DropDownItems.Add(paritamenu);
}
foreach (string port in stop)
{
ToolStripMenuItem stopmenu = new ToolStripMenuItem(port);
stopmenu.Click += stopItem_Click2;
stopmenu.CheckOnClick = true;
stopBits2ToolStripMenuItem.DropDownItems.Add(stopmenu);
}*/
}
// lavoro che il backgroundworker deve svolgere quando chiamato
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//imposta i valori della porta seriale al worker
int baudRate = Int32.Parse(baudrate);
Parity parity = CalculateParity();
int dataBits = ParseDataBits();
StopBits stopBits = ParseStopBits();
/*int baudRate2 = Int32.Parse(baudrate2);
var parity2 = CalculateParity2();
var dataBits2 = ParseDataBits2();
var stopBits2 = ParseStopBits2();*/
serialPort = new SerialPort(porta, baudRate, parity, dataBits, stopBits);
serialPort.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
// aprire la porta se possibile, altrimenti messaggio di errore
try
{
serialPort.Open();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
// porta seriale rimane aperta finchè il backgroundworker lavora
while (!backgroundWorker1.CancellationPending)
{
System.Threading.Thread.Sleep(1);
}
serialPort.Close();
}
// Funzione che aggiorna la tabella
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState is string[] values)
{
if (data != null)
{
DataTable table = data.Tables["prova2"];
// se la tabella esiste
if (table != null)
{
// aggiunge la riga con i valori dell'array values
table.Rows.Add(values);
// aggiorna la tabella
dataGridView1.Refresh();
dataGridView1.Update();
}
}
}
}
// Funzione che finisce il funzionamento del backgroundworker
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
MessageBox.Show("An error occurred: " + e.Error.Message);
}
else if (e.Result is Exception ex)
{
MessageBox.Show("An error occurred: " + ex.Message);
}
}
// Codice del button5 o button per fermare la lettura
private void ButtonStop_Click(object sender, EventArgs e)
{
// richiama una funzione che ha la sua stessa funzione
fermaLetturaToolStripMenuItem_Click(sender, e);
}
// Codice del button1 o button per iniziare la lettura
private void Start_Click(object sender, EventArgs e)
{
// richiama una funzione che ha la sua stessa funzione
avviaLetturaToolStripMenuItem_Click(sender, e);
}
// Funzione che gestisce i dati ricevuti
private static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
{
SerialPort sp = (SerialPort)sender;
string hexLine = BitConverter.ToString(ReadBytesFromSerialPort(sp)).Replace("-", " ");
ParseModbusLine(hexLine);
}
// Funzione che legge i bytes dalla porta seriale
private static byte[] ReadBytesFromSerialPort(SerialPort sp)
{
int bytesToRead = sp.BytesToRead;
// creazione array di bytes dove viene contenuto ciò che viene letto
byte[] buffer = new byte[bytesToRead];
sp.Read(buffer, 0, bytesToRead);
return buffer;
}
// Funzione che analizza la stringa di bytes che gli viene inviata
public static void ParseModbusLine(string hexLine)
{
Console.WriteLine(hexLine);
// stringa viene divisa
string[] bytes = hexLine.Split(' ');
// se la lunghezza è minore di 5 non può essere ne una request, ne una response di modbus fc03, fc06, fc16
if (bytes.Length < 5)
{
return;
}
// indirizzo dello slave salvato
string slaveAddress = bytes[0];
// tipo di function code salvato
string functionCode = bytes[1];
// controllo se la stringa passata è una Request o una Response
if (functionCode == Function_Code.Read.ToString("x"))
{
if ((bytes.Length - 5) % 2 == 0)
{
ParseModbusResponse(bytes);
}
else
{
ParseModbusRequest(bytes);
}
}
}
// Funzione in caso la stringa passata precedentemente sia una Request, stringa viene salvata su file e su griglia
public static void ParseModbusRequest(string[] bytes)
{
if (bytes.Length < 8)
{
return;
}
// Aggiornamento orario attuale
ora = DateTime.Now;
// Calcola la differenza tra i due tempi
TimeSpan differenza = ora - oravecchia;
// Aggiorna il tempo precedente per il prossimo calcolo
oravecchia = ora;
// Visualizza la differenza in millisecondi (o in qualsiasi formato desiderato)
// MessageBox.Show("Differenza di tempo: " + differenza.TotalMilliseconds + " millisecondi");
// salvataggio della stringa passata
string slaveAddress = bytes[0];
string functionCode = bytes[1];
string addressFirstRegister = bytes[2] + bytes[3];
string amountRegisters = bytes[4] + bytes[5];
string crc = bytes[6] + bytes[7];
// Preparazione valori da dover inviare alla DataGridView
string[] values = new string[]
{
ora.ToString("o"),
differenza.TotalMilliseconds + " ms",
slaveAddress,
functionCode + " - Read Request",
addressFirstRegister,
amountRegisters,
string.Empty, // Non vengono specificati i registri in caso di Request
crc
};
// Controllo che il backgroundworker sia libero per poter scrivere i dati in tabella e nel file
if (backgroundWorker1.IsBusy)
{
// aspettare che il backgroundworker possa procedere
System.Threading.Thread.Sleep(200);
if (richieste == true)
{
// creazione della cartella se non è già presente nel PC
try
{
Directory.CreateDirectory(@"c:modbus");
}
catch (Exception ex)
{
// non si fa nulla
}
// rootPath per scrivere nel file
string rootPath = @"C:modbus";
string filePath = Path.Combine(rootPath, file);
// scrittura nel file
using (StreamWriter outputFile = new StreamWriter(filePath, true))
{
// ogni riga in values viene scritta nel file
foreach (string line in values)
{
outputFile.WriteLine(line);
}
// Riga vuota per staccare ogni riga
outputFile.WriteLine("n");
}
// riportare i progressi al backgroundworker
backgroundWorker1.ReportProgress(0, values);
}
} else // stessi passaggi se il backgroundworker non è da aspettare
{
if (richieste == true)
{
// creazione della cartella se non è già presente nel PC
try
{
Directory.CreateDirectory(@"c:modbus");
}
catch (Exception ex)
{
// non si fa nulla
}
// rootPath per scrivere nel file
string rootPath = @"C:modbus";
string filePath = Path.Combine(rootPath, file);
// scrittura nel file
using (StreamWriter outputFile = new StreamWriter(filePath, true))
{
// ogni riga in values viene scritta nel file
foreach (string line in values)
{
outputFile.WriteLine(line);
}
// Riga vuota per staccare ogni riga
outputFile.WriteLine("n");
}
// riportare i progressi al backgroundworker
backgroundWorker1.ReportProgress(0, values);
}
}
}
// Funzione in caso la stringa passata precedentemente sia una Response, stringa viene salvata su file e sulla griglia
public static void ParseModbusResponse(string[] bytes)
{
// le Response delle nostre fc possono essere di 5 o 7 bytes
if (bytes.Length <= 5)
{
return;
}
// informazioni della stringa vengono salvate
string slaveAddress = bytes[0];
string functionCode = bytes[1];
// numero di bytes letti
string numberOfBytes = bytes[2];
// prova di conversione in bytes della stringa numberOfBytes, se non possibile la funzione termina
if (!byte.TryParse(numberOfBytes, System.Globalization.NumberStyles.HexNumber, null, out byte byteCount))
{
// in caso la converisone avvenisse il valore è contenuto in byteCount
return;
}
// lunghezza aspettata dalla conversione
int expectedLength = 3 + byteCount + 2;
// se la lunghezza è minore rispetto alla lunghezza aspettata la funzione termina
if (bytes.Length < expectedLength)
{
return;
}
// creazione del CRC
string crc = bytes[bytes.Length - 2] + bytes[bytes.Length - 1];
for (int i = 3; i < 3 + byteCount; i += 2)
{
if (i + 1 < bytes.Length)
{
string registerValue = bytes[i] + bytes[i + 1];
// aggiornamento dell'orario
ora = DateTime.Now;
// Calcola la differenza tra i due tempi
TimeSpan differenza = ora - oravecchia;
// Aggiorna il tempo precedente per il prossimo calcolo
oravecchia = ora;
// Prepara i valori da inviare alla DataGridView
string[] values = new string[]
{
ora.ToString("o"),
differenza.TotalMilliseconds + " ms",
slaveAddress,
functionCode+ " - Read Response",
numberOfBytes,
"1", // Since we are adding one register per row
registerValue,
crc
};
// Riporta i valori al backgroundworker per aggiornare la DataGridView
if (backgroundWorker1.IsBusy)
{
// se bisogna aspettare backgroundworker una sleep
System.Threading.Thread.Sleep(200);
if (risposte==true)
{
try
{
// creazione della directory se non ancora presente sul PC
Directory.CreateDirectory(@"c:modbus");
}
catch (Exception ex)
{
// in caso di eccezione non si fa nulla
}
// rootpath dove è presente il file dove devono essere scritti i dati
string rootPath = @"C:modbus";
string filePath = Path.Combine(rootPath, file);
// scrittura sul file tramite StreamWriter
using (StreamWriter outputFile = new StreamWriter(filePath, true))
{
foreach (string line in values)
{
outputFile.WriteLine(line);
}
// Riga per separare i dati
outputFile.WriteLine("n");
}
// aggiornamenti dei dati inviati al backgroundworker per aggiornare la tabella
backgroundWorker1.ReportProgress(0, values);
}
} else
{
// ripetizione del codice se il backgroundworker non è busy
if (risposte==true)
{
try
{
// creazione cartella se non presente nel PC
Directory.CreateDirectory(@"c:modbus");
}
catch (Exception ex)
{
// nulla in caso di eccezione
}
// rootpath dove è stato creato il file dove verranno scritti i dati letti
string rootPath = @"C:modbus";
string filePath = Path.Combine(rootPath, file);
// StreamWriter per scrivere sui file
using (StreamWriter outputFile = new StreamWriter(filePath, true))
{
// scrittura di ogni riga presente in values sul file
foreach (string line in values)
{
outputFile.WriteLine(line);
}
outputFile.WriteLine("n");
}
backgroundWorker1.ReportProgress(0, values);
}
}
}
}
}
It sometimes read and print data in the output but never in the datagridview