using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; using System.IO.Ports; using System.Text.RegularExpressions; using System.Net.Sockets; using System.IO; using Newtonsoft.Json; using System.Threading; namespace Gidrolock_Modbus_Scanner { public partial class App : Form { public static int[] BaudRate = new int[] { 110, 300, 1200, 2400, 4800, 9600, 14400, 19200, 28800, 38400, 57600, 76800, 115200, 230300, 460800, 921600 }; public static int[] DataBits = new int[] { 7, 8 }; Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp); byte[] message = new byte[255]; public bool isAwaitingResponse = false; public short[] res = new short[12]; SerialPort port = Modbus.port; public int expectedLength = 0; Datasheet datasheet; public SelectedPath selectedPath = SelectedPath.Folder; public static Device device; // Deserialized .json object string path = String.Empty; // Path to selected file/folder string defaultPath = Application.StartupPath + "\\Configs"; // Default folder path OpenFileDialog ofd = new OpenFileDialog(); FolderBrowserDialog fbd = new FolderBrowserDialog(); Dictionary> juju = new Dictionary>(); // dictionary for device identification string[] configs; public byte[] latestMessage; DateTime dateTime; #region Initialization public App() { InitializeComponent(); Modbus.Init(); Modbus.ResponseReceived += OnResponseReceived; ofd.InitialDirectory = Application.StartupPath; ofd.Filter = "JSON files (*.json)|*.json"; ofd.FilterIndex = 2; ofd.RestoreDirectory = true; this.UpDown_ModbusID.Value = 30; TextBox_Log.Text = "Приложение готово к работе."; CBox_Function.Items.Add("01 Read Coil"); CBox_Function.Items.Add("02 Read Discrete Input"); CBox_Function.Items.Add("03 Read Holding Register"); CBox_Function.Items.Add("04 Read Input Register"); CBox_Function.Items.Add("05 Write Single Coil"); CBox_Function.Items.Add("06 Write Single Register"); CBox_Function.Items.Add("0F Write Multiple Coils"); CBox_Function.Items.Add("10 Write Multiple Registers"); CBox_Function.SelectedItem = CBox_Function.Items[0]; CBox_BaudRate.Items.Add("110"); CBox_BaudRate.Items.Add("300"); CBox_BaudRate.Items.Add("1200"); CBox_BaudRate.Items.Add("2400"); CBox_BaudRate.Items.Add("4800"); CBox_BaudRate.Items.Add("9600"); CBox_BaudRate.Items.Add("14400"); CBox_BaudRate.Items.Add("19200"); CBox_BaudRate.Items.Add("28800"); CBox_BaudRate.Items.Add("38400"); CBox_BaudRate.Items.Add("57600"); CBox_BaudRate.Items.Add("76800"); CBox_BaudRate.Items.Add("115200"); CBox_BaudRate.Items.Add("230300"); CBox_BaudRate.Items.Add("460800"); CBox_BaudRate.Items.Add("921600"); CBox_BaudRate.SelectedIndex = 5; CBox_DataBits.Items.Add("7"); CBox_DataBits.Items.Add("8"); CBox_DataBits.SelectedIndex = 1; CBox_StopBits.Items.Add("Нет"); CBox_StopBits.Items.Add("1"); CBox_StopBits.Items.Add("1.5"); CBox_StopBits.Items.Add("2"); CBox_StopBits.SelectedIndex = 1; CBox_Parity.Items.Add("Нет"); CBox_Parity.Items.Add("Четн."); CBox_Parity.Items.Add("Нечетн."); CBox_Parity.SelectedIndex = 0; UpDown_RegAddress.Minimum = 0; UpDown_RegAddress.Maximum = 65535; UpDown_Value.Minimum = 0; UpDown_Value.Maximum = 65535; // 2^16 Radio_SerialPort.Checked = true; GBox_Ethernet.Enabled = false; TBox_IP.Text = "192.168.3.7"; TBox_Port.Text = "8887"; TBox_Timeout.Text = "3"; if (Directory.GetDirectories(Application.StartupPath).Contains(Application.StartupPath + "\\Configs") == false) { Task.Delay(1500).ContinueWith(t => { MessageBox.Show("Приложение не нашло стандартную папку для конфигураций. Была создана папка 'Configs' в папке с приложением."); Directory.CreateDirectory(Application.StartupPath + "\\Configs"); Console.WriteLine("New initial directory for OpenFile: " + ofd.InitialDirectory); }); } ofd.InitialDirectory = Application.StartupPath + "\\Configs"; path = defaultPath; UpdatePathLabel(); } void UpdatePathLabel() { Label_ConfPath.Text = path; } void App_FormClosed(object sender, FormClosedEventArgs e) { port.Close(); if (!port.IsOpen) Application.Exit(); } void Form1_Load(object sender, EventArgs e) { CBox_Ports.Items.AddRange(SerialPort.GetPortNames()); if (CBox_Ports.Items.Count > 0) CBox_Ports.SelectedIndex = 0; Init(); } void Init() { if (UpDown_ModbusID.Value == 0) Button_Connect.Text = "Найти адрес"; else Button_Connect.Text = "Подключиться"; } #endregion // Send a custom message async Task SendMessageAsync(FunctionCode functionCode, ushort address, ushort length) { if (CBox_Ports.Text == "") MessageBox.Show("Необходимо выбрать COM порт.", "Ошибка", MessageBoxButtons.OK); if (UpDown_ModbusID.Value == 0) MessageBox.Show("Глобальное вещание пока не поддерживается"); /* - Port Setup - */ if (port.IsOpen) port.Close(); port.Handshake = Handshake.None; port.PortName = CBox_Ports.Text; port.BaudRate = BaudRate[CBox_BaudRate.SelectedIndex]; port.Parity = Parity.None; port.DataBits = DataBits[CBox_DataBits.SelectedIndex]; port.StopBits = (StopBits)CBox_StopBits.SelectedIndex; port.ReadTimeout = 3000; port.WriteTimeout = 3000; message = new byte[255]; port.Open(); /* - Reading from Registers - */ if (CBox_Function.SelectedIndex < 4) { try { var send = await Modbus.ReadRegAsync(port, (byte)UpDown_ModbusID.Value, functionCode, address, length); isAwaitingResponse = true; await Task.Delay(port.ReadTimeout).ContinueWith(_ => { if (isAwaitingResponse) { MessageBox.Show("Истекло время ожидания ответа.", "Ошибка"); port.Close(); } }); } catch (Exception err) { port.Close(); MessageBox.Show(err.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } /* - Writing to Registers - */ else { try { if (CBox_Function.SelectedIndex < 6) // Single Registers { byte[] request = new byte[8]; Modbus.BuildMessage((byte)UpDown_ModbusID.Value, (byte)(1 + functionCode), address, length, ref request); string messageParsed = Modbus.ByteArrayToString(request); var send = await Modbus.WriteSingle(port, functionCode, (byte)UpDown_ModbusID.Value, address, (ushort)UpDown_Value.Value); } else // Multiple Registers { byte[] request = new byte[(int)UpDown_RegLength.Value * 2 + 6]; // TODO } } catch (Exception err) { port.Close(); MessageBox.Show(err.Message, "Ошибка", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } private async void ButtonConnect_Click(object sender, EventArgs e) { progressBar1.Value = 0; if (path == String.Empty) { MessageBox.Show("Выберите конфигурацию для подключения и опроса устройства."); return; } if (CBox_Ports.SelectedItem.ToString() == "COM1") { DialogResult res = MessageBox.Show("Выбран серийный порт COM1, который обычно является портом PS/2 или RS-232, не подключенным к Modbus устройству. Продолжить?", "Внимание", MessageBoxButtons.OKCancel); if (res == DialogResult.Cancel) return; } /* - Port Setup - */ if (port.IsOpen) port.Close(); port.Handshake = Handshake.None; port.PortName = CBox_Ports.Text; port.BaudRate = BaudRate[CBox_BaudRate.SelectedIndex]; port.Parity = Parity.None; port.DataBits = DataBits[CBox_DataBits.SelectedIndex]; port.StopBits = (StopBits)CBox_StopBits.SelectedIndex; port.ReadTimeout = 3000; port.WriteTimeout = 3000; message = new byte[255]; port.Open(); /* - Checking - */ if (selectedPath == SelectedPath.File) { var fileContent = string.Empty; var filePath = string.Empty; //Read the contents of the file into a stream var fileStream = ofd.OpenFile(); using (StreamReader reader = new StreamReader(fileStream)) { fileContent = reader.ReadToEnd(); } progressBar1.Value = 100; try { device = JsonConvert.DeserializeObject(fileContent); Label_ConfigTip.Text = device.name; } catch (Exception err) { MessageBox.Show(err.Message); } try { AddLog("Попытка подключиться к устройству " + device.name); datasheet = new Datasheet((byte)UpDown_ModbusID.Value); datasheet.Show(); } catch (Exception err) { MessageBox.Show(err.Message); } } else { string[] _configs = Directory.GetFiles(path, "*.json"); if (configs != _configs) { // something changed in the config folder, or we haven't gone through configs, // remake the dictionary configs = _configs; juju = new Dictionary>(); var fileContent = string.Empty; FileStream fileStream; Device _device; foreach (string path in configs) { fileStream = File.OpenRead(path); using (StreamReader reader = new StreamReader(fileStream)) fileContent = reader.ReadToEnd(); // get device object from .json _device = JsonConvert.DeserializeObject(fileContent); // compare device object to key of each dictionary; // add to that dictionary if device's check entry registers match the key bool matched = false; foreach (CheckEntry ce in juju.Keys) { if (_device.checkEntry.address == ce.address && _device.checkEntry.length == ce.length && _device.checkEntry.dataType == ce.dataType) { juju[ce].Add(_device); matched = true; break; } } if (!matched) { juju.Add(_device.checkEntry, new List()); juju[_device.checkEntry].Add(_device); } } // all configs are sorted out, we can poll for each checkEntry } // setup event listener byte[] message = null; Modbus.ResponseReceived += (sndr, msg) => { message = msg.Message; }; foreach (CheckEntry ce in juju.Keys) { await SendMessageAsync((FunctionCode)ce.registerType, ce.address, ce.length); // send read request to device, while (message is null) // wait for response to arrive Thread.Sleep(10); if (message[1] > 0x10) // checking for exception code continue; else { // get pure data byte[] data = new byte[message[2]]; for (int i = 0; i < data.Length; i++) data[i] = message[i + 3]; if (ce.dataType == "string") { List bytes = new List(); for (int i = 0; i < data.Length; i++) { if (data[i] != 0) // clean empty bytes from 16-bit registers bytes.Add(data[i]); } string value = Encoding.UTF8.GetString(bytes.ToArray()); foreach (Device dev in juju[ce]) { if (dev.checkEntry.expectedValue == value) { Console.WriteLine("It's a match!"); device = dev; break; } } } else if (ce.dataType == "bool") { // why would you even do that lmao } else if (ce.dataType == "uint16" || ce.dataType == "uint32") { byte[] _data = data; Array.Reverse(_data); if (ce.dataType == "uint16") { ushort value = BitConverter.ToUInt16(_data, 0); foreach (Device dev in juju[ce]) { short expValue; if (Int16.TryParse(dev.checkEntry.expectedValue, out expValue) && expValue == value) { device = dev; break; } } } else { int value = BitConverter.ToInt32(_data, 0); foreach (Device dev in juju[ce]) { int expValue; if (Int32.TryParse(dev.checkEntry.expectedValue, out expValue) && expValue == value) { device = dev; break; } } } } } if (!(device is null)) // found the correct device, abort loop break; message = null; // clear the array for the next item in case we haven't found the correct value } if (device is /* still */ null) { // none of the configs match the device responses MessageBox.Show("Ни один из файлов конфигурации не подходит для устройства."); return; } else { try { AddLog("Попытка подключиться к устройству " + device.name); datasheet = new Datasheet((byte)UpDown_ModbusID.Value); datasheet.Show(); } catch (Exception err) { MessageBox.Show(err.Message); } } progressBar1.Value = 100; } /* if (Radio_SerialPort.Checked) await SendMessageAsync(FunctionCode.InputRegister, 200, 6); //else EthernetParse(); */ } async void EthernetParse() { string ipText = TBox_IP.Text; string portText = TBox_Port.Text; Regex ip = new Regex(@"\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"); Regex port = new Regex(@"\d"); if (!ip.IsMatch(ipText)) MessageBox.Show("Неправильный формат IP-адреса."); else if (!port.IsMatch(portText)) MessageBox.Show("Неправильный формат TCP-порта."); else { int portParsed = Int32.Parse(portText); await socket.ConnectAsync(ipText, portParsed); byte[] data = new byte[8]; Modbus.BuildMessage(0x1E, 0x03, 128, 1, ref data); AddLog("Sending to " + ipText + ":" + portText + ":" + Modbus.ByteArrayToString(data)); // set up an event listener to receive the response await SocketDataTransfer(data); } } void CBox_Ports_Click(object sender, EventArgs e) { CBox_Ports.Items.Clear(); CBox_Ports.Items.AddRange(SerialPort.GetPortNames()); } void OnResponseReceived(object sender, ModbusResponseEventArgs e) { isAwaitingResponse = false; TextBox_Log.Invoke((MethodInvoker)delegate { AddLog("Получен ответ: " + Modbus.ByteArrayToString(e.Message)); }); TextBox_Log.Invoke((MethodInvoker)delegate { AddLog("UTF-8: " + e.Text); }); } void AddLog(string message) { dateTime = DateTime.Now; TextBox_Log.AppendText(Environment.NewLine + "[" + dateTime.Hour + ":" + dateTime.Minute + ":" + dateTime.Second + "] " + message); } private async void Button_SendCommand_Click(object sender, EventArgs e) { FunctionCode functionCode = (FunctionCode)CBox_Function.SelectedIndex + 1; ushort address = (ushort)UpDown_RegAddress.Value; ushort length = (ushort)UpDown_RegLength.Value; await SendMessageAsync(functionCode, address, length); } private void OnSelectedFunctionChanged(object sender, EventArgs e) { if (CBox_Function.SelectedIndex < 4) UpDown_Value.Enabled = false; else { if (CBox_Function.SelectedIndex == 4 || CBox_Function.SelectedIndex == 6) UpDown_Value.Maximum = 1; else UpDown_Value.Maximum = 65535; UpDown_Value.Enabled = true; } } private void Radio_SerialPort_CheckedChanged(object sender, EventArgs e) { if (Radio_SerialPort.Checked) GBox_Serial.Enabled = true; else GBox_Serial.Enabled = false; } private void Radio_Ethernet_CheckedChanged(object sender, EventArgs e) { if (Radio_Ethernet.Checked) GBox_Ethernet.Enabled = true; else GBox_Ethernet.Enabled = false; } private async Task SocketDataTransfer(byte[] data) { await Task.Run(() => { socket.Send(data); }); byte[] res = new byte[64]; await Task.Run(() => { while (true) { int bytesReceived = socket.Receive(res); if (bytesReceived == 0) break; string resParsed = ""; Modbus.ParseResponse(res, ref resParsed); Console.Out.WriteLine("Received data on TCP socket: " + resParsed); AddLog("Response from TCP Server: " + resParsed); } }); return true; } private void LoadConfig(object sender, EventArgs e) { if (ofd.ShowDialog() == DialogResult.OK) { //Get the path of specified file path = ofd.FileName; Label_ConfPath.Text = ofd.FileName; selectedPath = SelectedPath.File; } UpdatePathLabel(); } private void LoadFolder(object sender, EventArgs e) { fbd.RootFolder = Environment.SpecialFolder.MyComputer; if (fbd.ShowDialog() == DialogResult.OK) { path = fbd.SelectedPath; Label_ConfPath.Text = fbd.SelectedPath; selectedPath = SelectedPath.Folder; } UpdatePathLabel(); } private void CBox_Ports_Click(object sender, MouseEventArgs e) { } } } public enum FunctionCode { ReadCoil = 1, ReadDiscrete = 2, ReadHolding = 3, ReadInput = 4, WriteCoil = 5, WriteRegister = 6, WriteMultCoils = 15, WriteMultRegisters = 16 }; public enum SelectedPath { File, Folder };