Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing PLC Communication for .NET HMI Applications

Tech 1

Create a Windows Forms (.NET Framework) project as the base for the HMI application.

UI Asset and Control Configuration

  1. Create an images directory in the project's Debug output folder to store static PNG and animated GIF assets for equipment state display and button states. Add a PictureBox control to the main form, set its SizeMode property to Zoom, and assign the default static motor idle image to it.
  2. Add two Button controls for PLC connection toggle and equipment operation control respective. Set the FlatStyle property of each button to Flat to enable custom image rendering, then assign default idle state images to their Image property.
  3. Add a ComboBox control named cbbPlcModel, populate its Items collection with two options: S7-1200 and S7-1500, and set the default selected index to 0.
  4. For floating point read/write functionality, add two TextBox controls named txtFloatWriteInput and txtFloatReadOutput, plus two corresponding Button controls named btnWriteFloat and btnReadFloat. Store the target data block offset value in the Tag property of both buttons.

Control FlatStyle Configuration

The FlatStyle property for Windows Forms controls supports two common configurations for inudstrial HMI use cases:

  • Flat: Removes 3D border effects, renders controls as flat surfaces suitable for custom image and theme integration
  • Standard: Default 3D rendered style, used for standard system control appearance.

Equipment Status Entity Definition

Create a Models directory in the project root, add an EquipmentStatus.cs class to store equipment runtime state data:

namespace IndustrialHmiDemo
{
    /// <summary>
    /// Production equipment runtime status entity
    /// </summary>
    internal class EquipmentStatus
    {
        public bool IsRunning { get; set; }
        public bool IsStopped { get; set; }
        public bool Task1Active { get; set; }
        public bool Task2Active { get; set; }
        public bool Task3Active { get; set; }
        public bool HealthState { get; set; } // true = normal operation, false = active alarm
        public ushort OperationMode { get; set; } // 1=standard, 2=accelerated, 3=full load
        public float PositionX { get; set; }
        public float PositionY { get; set; }
        public float PositionZ { get; set; }
        public float FeedRate1 { get; set; }
        public float FeedRate2 { get; set; }
    }
}

PLC Comunication Service Implementation

Create a Services directory, add a PlcControlService.cs class to encapsulate all PLC communication logic, avoiding tight coupling with UI layer code:

using S7NetPlus;
using System;

namespace IndustrialHmiDemo.Services
{
    /// <summary>
    /// PLC connection and data operation management service
    /// </summary>
    internal class PlcControlService
    {
        private Plc _s7PlcClient = null;

        /// <summary>
        /// Establish connection to target S7 series PLC
        /// </summary>
        /// <param name="ipAddress">PLC LAN IP address</param>
        /// <param name="plcModel">PLC model identifier</param>
        /// <returns>Connection operation result message</returns>
        public string EstablishConnection(string ipAddress, string plcModel)
        {
            try
            {
                CpuType parsedCpuType = (CpuType)Enum.Parse(typeof(CpuType), plcModel.Replace("-", ""), ignoreCase: true);
                _s7PlcClient = new Plc(parsedCpuType, ipAddress, rack: 0, slot: 0);
                _s7PlcClient.Open();
                return "PLC connection established successfully";
            }
            catch (Exception ex)
            {
                return $"PLC connection failed: {ex.Message}";
            }
        }

        /// <summary>
        /// Terminate active PLC connection and release resources
        /// </summary>
        public void TerminateConnection()
        {
            if (_s7PlcClient != null && _s7PlcClient.IsConnected)
            {
                _s7PlcClient.Close();
            }
        }

        /// <summary>
        /// Write arbitrary data to specified PLC variable address
        /// </summary>
        /// <param name="fullVariableAddress">Full PLC variable address string</param>
        /// <param name="payload">Data to write</param>
        /// <returns>Write operation result</returns>
        public string WriteToAddress(string fullVariableAddress, object payload)
        {
            lock (this)
            {
                try
                {
                    _s7PlcClient.Write(fullVariableAddress, payload);
                    return "Data write completed successfully";
                }
                catch (Exception ex)
                {
                    return $"Data write failed: {ex.Message}";
                }
            }
        }

        /// <summary>
        /// Overload: Write single-precision float value to specified data block offset
        /// </summary>
        /// <param name="dbNumber">Target data block number</param>
        /// <param name="byteOffset">Byte offset in target data block</param>
        /// <param name="value">Float value to write</param>
        /// <returns>Write operation result</returns>
        public string WriteFloatValue(int dbNumber, int byteOffset, float value)
        {
            lock (this)
            {
                try
                {
                    _s7PlcClient.Write(DataType.DataBlock, dbNumber, byteOffset, value);
                    return "Float value write successful";
                }
                catch (Exception ex)
                {
                    return $"Float value write failed: {ex.Message}";
                }
            }
        }

        /// <summary>
        /// Read single-precision float value from specified data block offset
        /// </summary>
        /// <param name="dbNumber">Target data block number</param>
        /// <param name="byteOffset">Byte offset in target data block</param>
        /// <returns>Read float value or error message</returns>
        public string ReadFloatValue(int dbNumber, int byteOffset)
        {
            try
            {
                var readResult = _s7PlcClient.Read(DataType.DataBlock, dbNumber, byteOffset, VarType.Real, 1);
                return readResult.ToString();
            }
            catch (Exception ex)
            {
                return $"Float value read failed: {ex.Message}";
            }
        }
    }
}

Main Form Event Implementation

Bind event handlers to form controls to implement user interaction logic:

using IndustrialHmiDemo.Services;
using System;
using System.Drawing;
using System.Windows.Forms;

namespace IndustrialHmiDemo
{
    public partial class MainInterface : Form
    {
        private readonly PlcControlService _plcControlService = new PlcControlService();

        public MainInterface()
        {
            InitializeComponent();
            // Initialize control state tags
            btnPlcConnection.Tag = "off";
            btnEquipmentControl.Tag = "off-DB1.DBX0.0";
            btnWriteFloat.Tag = "0";
            btnReadFloat.Tag = "0";
        }

        private void btnPlcConnection_Click(object sender, EventArgs e)
        {
            if (btnPlcConnection.Tag.ToString() == "off")
            {
                string connectionResult = _plcControlService.EstablishConnection("192.168.0.10", cbbPlcModel.Text);
                btnPlcConnection.Tag = "on";
                btnPlcConnection.Image = Image.FromFile(@"images\plc_connected.png");
                MessageBox.Show(connectionResult);
            }
            else
            {
                _plcControlService.TerminateConnection();
                btnPlcConnection.Tag = "off";
                btnPlcConnection.Image = Image.FromFile(@"images\plc_disconnected.png");
            }
        }

        private void btnEquipmentControl_Click(object sender, EventArgs e)
        {
            if (btnPlcConnection.Tag.ToString() == "off")
            {
                MessageBox.Show("Please establish PLC connection first", "Operation Prompt");
                return;
            }

            string[] tagSegments = btnEquipmentControl.Tag.ToString().Split('-');
            if (tagSegments[0] == "off")
            {
                picMotorDisplay.Image = Image.FromFile(@"images\motor_running.gif");
                btnEquipmentControl.Image = Image.FromFile(@"images\btn_active.png");
                btnEquipmentControl.Tag = $"on-{tagSegments[1]}";
                _plcControlService.WriteToAddress(tagSegments[1], true);
            }
            else
            {
                picMotorDisplay.Image = Image.FromFile(@"images\motor_idle.png");
                btnEquipmentControl.Image = Image.FromFile(@"images\btn_inactive.png");
                btnEquipmentControl.Tag = $"off-{tagSegments[1]}";
                _plcControlService.WriteToAddress(tagSegments[1], false);
            }
        }

        private void btnWriteFloat_Click(object sender, EventArgs e)
        {
            if (!float.TryParse(txtFloatWriteInput.Text, out float writeValue))
            {
                MessageBox.Show("Please enter a valid floating point number", "Input Validation Error");
                return;
            }
            int offset = int.Parse(btnWriteFloat.Tag.ToString());
            string writeResult = _plcControlService.WriteFloatValue(1, offset, writeValue);
            txtFloatWriteInput.Clear();
            MessageBox.Show(writeResult);
        }

        private void btnReadFloat_Click(object sender, EventArgs e)
        {
            int offset = int.Parse(btnReadFloat.Tag.ToString());
            string readResult = _plcControlService.ReadFloatValue(1, offset);
            if (readResult.Contains("failed"))
            {
                MessageBox.Show(readResult);
                return;
            }
            txtFloatReadOutput.Text = readResult;
        }
    }
}
Tags: .NET

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.